Skip to content

Commit

Permalink
feat: Improve stage wizard discoverability and interaction COMPASS-7350
Browse files Browse the repository at this point in the history
… (#5002)

* feat: Improve stage wizard discoverability and interaction COMPASS-7350

* hide toolbar labels for narrow containers

* addressing more feedback + usage based order
  • Loading branch information
mcasimir authored Oct 26, 2023
1 parent 3f5c9db commit 62adbfc
Show file tree
Hide file tree
Showing 18 changed files with 530 additions and 312 deletions.
302 changes: 128 additions & 174 deletions package-lock.json

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ describe('aggregation side panel', function () {

it('renders usecases filtered by search text matching the title of the usecases', function () {
renderAggregationSidePanel();
const searchBox = screen.getByPlaceholderText(/How can we help\?/i);
const searchBox = screen.getByPlaceholderText(/Search for a Stage/i);
userEvent.type(searchBox, 'Sort');
expect(
screen
Expand All @@ -76,7 +76,7 @@ describe('aggregation side panel', function () {

it('renders usecases filtered by search text matching the stage operator of the usecases', function () {
renderAggregationSidePanel();
const searchBox = screen.getByPlaceholderText(/How can we help\?/i);
const searchBox = screen.getByPlaceholderText(/Search for a Stage/i);
userEvent.type(searchBox, 'lookup');
expect(
screen
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,7 @@ const { track } = createLoggerAndTelemetry('COMPASS-AGGREGATIONS-UI');

const containerStyles = css({
height: '100%',
paddingLeft: spacing[2],
paddingRight: spacing[2],

paddingTop: spacing[1],
borderBottomRightRadius: 0,
borderBottomLeftRadius: 0,
Expand Down Expand Up @@ -58,11 +57,19 @@ const titleStylesLight = css({
color: palette.green.dark2,
});

const headerContainerStyles = css({
paddingLeft: spacing[2],
paddingRight: spacing[2],
});

const contentStyles = css({
display: 'flex',
flexDirection: 'column',
gap: spacing[2],
overflow: 'auto',
paddingTop: spacing[1],
paddingLeft: spacing[2],
paddingRight: spacing[2],
paddingBottom: spacing[3],
});

Expand Down Expand Up @@ -113,28 +120,31 @@ export const AggregationSidePanel = ({
data-testid="aggregation-side-panel"
className={cx(containerStyles, darkMode && darkModeContainerStyles)}
>
<div className={headerStyles}>
<Body
weight="medium"
className={darkMode ? titleStylesDark : titleStylesLight}
>
Stage Wizard
</Body>
<IconButton
className={closeButtonStyles}
title="Hide Stage Wizard"
aria-label="Hide Stage Wizard"
onClick={() => onCloseSidePanel()}
>
<Icon glyph="X" />
</IconButton>
<div className={headerContainerStyles}>
<div className={headerStyles}>
<Body
weight="medium"
className={darkMode ? titleStylesDark : titleStylesLight}
>
Stage Wizard
</Body>
<IconButton
className={closeButtonStyles}
title="Hide Stage Wizard"
aria-label="Hide Stage Wizard"
onClick={() => onCloseSidePanel()}
>
<Icon glyph="X" />
</IconButton>
</div>
<SearchInput
value={searchText}
onChange={handleSearchTextChange}
placeholder="Search for a Stage"
aria-label="Search for a Stage"
/>
</div>
<SearchInput
value={searchText}
onChange={handleSearchTextChange}
placeholder="How can we help?"
aria-label="How can we help?"
/>

<div className={contentStyles} data-testid="side-panel-content">
{filteredUseCases.map((useCase, index) => {
if (index !== 0) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,25 +26,6 @@ export type StageWizardUseCase = {
};

export const STAGE_WIZARD_USE_CASES: StageWizardUseCase[] = [
{
id: 'sort',
title: 'Sort documents based on a single or set of fields',
stageOperator: '$sort',
wizardComponent: SortUseCase,
},
{
id: 'lookup',
title:
'Join documents from different collections to compare their field values',
stageOperator: '$lookup',
wizardComponent: LookupUseCase,
},
{
id: 'project',
title: 'Include or exclude a subset of fields from my documents',
stageOperator: '$project',
wizardComponent: ProjectUseCase,
},
{
id: 'match',
title: 'Find all the documents that match one or more conditions',
Expand All @@ -69,6 +50,25 @@ export const STAGE_WIZARD_USE_CASES: StageWizardUseCase[] = [
stageOperator: '$group',
wizardComponent: GroupWithSubset,
},
{
id: 'project',
title: 'Include or exclude a subset of fields from my documents',
stageOperator: '$project',
wizardComponent: ProjectUseCase,
},
{
id: 'sort',
title: 'Sort documents based on a single or set of fields',
stageOperator: '$sort',
wizardComponent: SortUseCase,
},
{
id: 'lookup',
title:
'Join documents from different collections to compare their field values',
stageOperator: '$lookup',
wizardComponent: LookupUseCase,
},
{
id: 'text-search',
title: 'Search for a text field across all documents in a collection',
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
import React from 'react';
import React, { useCallback } from 'react';
import {
KeylineCard,
Body,
Link,
css,
spacing,
Badge,
KeylineCard,
cx,
} from '@mongodb-js/compass-components';

import { getStageHelpLink } from '../../../utils/stage';
import type { StageWizardUseCase } from '.';
import { useDraggable } from '@dnd-kit/core';

Expand All @@ -20,39 +20,68 @@ type UseCaseCardProps = DraggedUseCase & {
onSelect: () => void;
};

type UseCaseCardLayoutProps = DraggedUseCase & {
onClick?: () => void;
};

const cardStyles = css({
cursor: 'grab',
padding: spacing[3],
':active': {
cursor: 'grabbing',
},
padding: `${spacing[2]}px ${spacing[3]}px`,
textAlign: 'left',
width: '100%',
});

const cardStylesDragging = css({
opacity: 0.5,
});

const cardStylesDropping = css({
cursor: 'grabbing',
});

const cardTitleStyles = css({
const cardBodyStyles = css({
display: 'inline',
marginRight: spacing[2],
});

type UseCaseCardLayoutProps = DraggedUseCase & {
onSelect?: () => void;
isDragging?: boolean;
isDropping?: boolean;
};

export const UseCaseCardLayout = React.forwardRef(function UseCaseCardLayout(
{ id, title, stageOperator, ...props }: UseCaseCardLayoutProps,
{
id,
title,
stageOperator,
isDragging,
isDropping,
onSelect,
...props
}: UseCaseCardLayoutProps,
ref: React.ForwardedRef<HTMLDivElement>
) {
const handleKeyDown = useCallback(
(event) => {
if (event.key === 'Enter' || event.code === 'Enter') {
onSelect?.();
}
},
[onSelect]
);

return (
<KeylineCard ref={ref} className={cardStyles} {...props}>
<Body data-testid={`use-case-${id}`} className={cardTitleStyles}>
{title}
<KeylineCard
ref={ref}
contentStyle="clickable"
className={cx(
cardStyles,
isDragging && cardStylesDragging,
isDropping && cardStylesDropping
)}
onClick={onSelect}
onKeyDown={handleKeyDown}
{...props}
>
<Body data-testid={`use-case-${id}`} className={cardBodyStyles}>
{title} <Badge>{stageOperator}</Badge>
</Body>
<Link
target="_blank"
onClick={(e) => e.stopPropagation()}
href={getStageHelpLink(stageOperator) as string}
>
{stageOperator}
</Link>
</KeylineCard>
);
});
Expand All @@ -63,7 +92,7 @@ const UseCaseCard = ({
stageOperator,
onSelect,
}: UseCaseCardProps) => {
const { setNodeRef, attributes, listeners } = useDraggable({
const { setNodeRef, attributes, listeners, isDragging } = useDraggable({
id,
data: {
type: 'use-case',
Expand All @@ -80,7 +109,8 @@ const UseCaseCard = ({
id={id}
title={title}
stageOperator={stageOperator}
onClick={onSelect}
isDragging={isDragging}
onSelect={onSelect}
ref={setNodeRef}
{...attributes}
{...listeners}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,7 @@ const PipelineBuilderDndWrapper = ({
id={draggedUseCase.id}
title={draggedUseCase.title}
stageOperator={draggedUseCase.stageOperator}
isDropping={true}
/>
</DragOverlay>
) : null}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,19 +17,7 @@ import PipelineAI from './pipeline-ai';
import type { RootState } from '../../modules';
import PipelineResultsHeader from '../pipeline-results-workspace/pipeline-results-header';
import type { PipelineOutputOption } from '../pipeline-output-options-menu';

const containerStyles = css({
padding: spacing[3],
});

const containerDisplayStyles = css({
display: 'grid',
gap: spacing[3],
gridTemplateAreas: `
"headerAndOptionsRow"
"settingsRow"
`,
});
import { PipelineToolbarContainer } from './pipeline-toolbar-container';

const headerAndOptionsRowStyles = css({
gridArea: 'headerAndOptionsRow',
Expand Down Expand Up @@ -77,10 +65,7 @@ export const PipelineToolbar: React.FunctionComponent<PipelineToolbarProps> = ({
const isAIFeatureEnabled = useIsAIFeatureEnabled(React);
const [isOptionsVisible, setIsOptionsVisible] = useState(false);
return (
<div
className={cx(containerStyles, containerDisplayStyles)}
data-testid="pipeline-toolbar"
>
<PipelineToolbarContainer>
<div
className={cx(
headerAndOptionsRowStyles,
Expand Down Expand Up @@ -113,7 +98,7 @@ export const PipelineToolbar: React.FunctionComponent<PipelineToolbarProps> = ({
/>
</div>
)}
</div>
</PipelineToolbarContainer>
);
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import type { RootState } from '../../../modules';
import { getIsPipelineInvalidFromBuilderState } from '../../../modules/pipeline-builder/builder-helpers';
import { confirmNewPipeline } from '../../../modules/is-new-pipeline-confirm';
import { usePreference } from 'compass-preferences-model';
import { hiddenOnNarrowPipelineToolbarStyles } from '../pipeline-toolbar-container';

const containerStyles = css({
display: 'grid',
Expand Down Expand Up @@ -81,7 +82,9 @@ export const PipelineSettings: React.FunctionComponent<
data-testid="pipeline-toolbar-export-button"
disabled={!isExportToLanguageEnabled}
>
Export to language
<span className={hiddenOnNarrowPipelineToolbarStyles}>
Export to language
</span>
</Button>
</div>
<div className={extraSettingsStyles}>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {
SegmentedControl,
SegmentedControlOption,
GuideCue,
Button,
} from '@mongodb-js/compass-components';
import { toggleSettingsIsExpanded } from '../../../modules/settings';
import { toggleAutoPreview } from '../../../modules/auto-preview';
Expand All @@ -19,6 +20,7 @@ import type { PipelineMode } from '../../../modules/pipeline-builder/pipeline-mo
import { getIsPipelineInvalidFromBuilderState } from '../../../modules/pipeline-builder/builder-helpers';
import { toggleSidePanel } from '../../../modules/side-panel';
import { usePreference } from 'compass-preferences-model';
import { hiddenOnNarrowPipelineToolbarStyles } from '../pipeline-toolbar-container';

const containerStyles = css({
display: 'flex',
Expand All @@ -38,6 +40,8 @@ const toggleLabelStyles = css({
textTransform: 'uppercase',
});

const toggleStageWizardStyles = css({ margin: 'auto' });

type PipelineExtraSettingsProps = {
isAutoPreview: boolean;
isPipelineModeDisabled: boolean;
Expand Down Expand Up @@ -117,16 +121,21 @@ export const PipelineExtraSettings: React.FunctionComponent<
'You can quickly build your stages based on your needs. You should try it out.'
}
trigger={({ ref }) => (
<IconButton
<Button
ref={ref}
size="xsmall"
leftGlyph={<Icon glyph="Wizard" />}
onClick={onToggleSidePanel}
title="Toggle Stage Wizard"
aria-label="Toggle Stage Wizard"
onClick={onToggleSidePanel}
data-testid="pipeline-toolbar-side-panel-button"
className={toggleStageWizardStyles}
disabled={pipelineMode === 'as-text'}
>
<Icon glyph="Wizard" />
</IconButton>
<span className={hiddenOnNarrowPipelineToolbarStyles}>
Wizard
</span>
</Button>
)}
/>
)}
Expand Down
Loading

0 comments on commit 62adbfc

Please sign in to comment.