Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
1b404e7
feat(aggregations): ux changes
mabaasit Apr 5, 2022
7433878
feat(aggregations): better pagination
mabaasit Apr 5, 2022
45fc654
feat(aggregations): pipeline loader
mabaasit Apr 5, 2022
6eb2562
feat(aggregations): tests
mabaasit Apr 5, 2022
930059f
feat(aggregations): tests
mabaasit Apr 6, 2022
f11d19d
feat(aggregations): use CancelLoader
mabaasit Apr 6, 2022
7dc3378
Merge branch 'main' into COMPASS-5669-aggregate-results
mabaasit Apr 6, 2022
89853bb
feat(aggregations): fix import
mabaasit Apr 6, 2022
4578fa5
Merge branch 'main' into COMPASS-5669-aggregate-results
mabaasit Apr 6, 2022
ec0ba57
feat(aggregations): fix test
mabaasit Apr 6, 2022
ba4792e
Merge branch 'COMPASS-5669-aggregate-results' of github.com:mongodb-j…
mabaasit Apr 6, 2022
13d6df8
feat(aggregations): add empty results page
mabaasit Apr 6, 2022
d633d25
Merge branch 'main' into COMPASS-5669-aggregate-results
mabaasit Apr 7, 2022
a3b55da
Merge branch 'main' into COMPASS-5669-aggregate-results
mabaasit Apr 7, 2022
9e8cbf4
Merge branch 'main' into COMPASS-5669-aggregate-results
mabaasit Apr 7, 2022
2b37e33
feat(aggregations): show pagination text in loading state
mabaasit Apr 11, 2022
5942612
Merge branch 'COMPASS-5669-aggregate-results' of github.com:mongodb-j…
mabaasit Apr 11, 2022
986190f
Merge branch 'main' of github.com:mongodb-js/compass into COMPASS-566…
mabaasit Apr 11, 2022
5f58688
Merge branch 'main' of github.com:mongodb-js/compass into COMPASS-566…
mabaasit Apr 13, 2022
d5012c3
feat(aggregations): UI fixes
mabaasit Apr 14, 2022
c8217e4
Merge branch 'main' into COMPASS-5669-aggregate-results
mabaasit Apr 14, 2022
234d0ed
Merge branch 'main' into COMPASS-5669-aggregate-results
mabaasit Apr 14, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import React from 'react';
import { render, screen, within } from '@testing-library/react';
import { expect } from 'chai';
import { spy } from 'sinon';
import userEvent from '@testing-library/user-event';
import { Provider } from 'react-redux';

import configureStore from '../../stores/store';

import { PipelineResultsWorkspace } from './index';

const renderPipelineResultsWorkspace = (
props: Record<string, unknown> = {}
) => {
render(
<Provider store={configureStore()}>
<PipelineResultsWorkspace
documents={[]}
loading={false}
hasEmptyResults={false}
onCancel={() => {}}
{...props}
/>
</Provider>
);
};

describe('PipelineResultsWorkspace', function () {
it('renders correctly', function () {
renderPipelineResultsWorkspace();
const container = screen.getByTestId('pipeline-results-workspace');
expect(container).to.exist;
});
it('renders loading state', function () {
renderPipelineResultsWorkspace({ loading: true });
const container = screen.getByTestId('pipeline-results-workspace');
expect(container).to.exist;
expect(within(container).getByTestId('pipeline-results-loader')).to.exist;
});
it('renders error state', function () {
renderPipelineResultsWorkspace({ error: 'Some error happened' });
const container = screen.getByTestId('pipeline-results-workspace');
expect(container).to.exist;
expect(within(container).getByText('Some error happened')).to.exist;
});
it('renders empty results state', function () {
renderPipelineResultsWorkspace({ hasEmptyResults: true });
const container = screen.getByTestId('pipeline-results-workspace');
expect(container).to.exist;
expect(within(container).getByTestId('pipeline-empty-results')).to.exist;
});
it('renders documents', function () {
renderPipelineResultsWorkspace({ documents: [{ id: '1' }, { id: '2' }] });
const container = screen.getByTestId('pipeline-results-workspace');
expect(
container.querySelectorAll('[data-test-id="document-list-item"]')
).to.have.lengthOf(2);
});
it('calls cancel when user stop aggregation', function () {
const onCancelSpy = spy();
renderPipelineResultsWorkspace({ loading: true, onCancel: onCancelSpy });
const container = screen.getByTestId('pipeline-results-workspace');
expect(container).to.exist;
userEvent.click(within(container).getByText('Stop'), undefined, {
skipPointerEventsCheck: true,
});
expect(onCancelSpy.calledOnce).to.be.true;
});
});
Original file line number Diff line number Diff line change
@@ -1,148 +1,117 @@
import React from 'react';
import React, { useState } from 'react';
import { connect } from 'react-redux';
import type { Document } from 'mongodb';
import {
IconButton,
css,
cx,
spacing,
Icon,
Button,
Body,
uiColors,
cx,
CancelLoader,
} from '@mongodb-js/compass-components';

import type { RootState } from '../../modules';
import {
fetchNextPage,
fetchPrevPage,
cancelAggregation,
} from '../../modules/aggregation';
import { PipelineResultsList } from './pipeline-results-list';
import { cancelAggregation } from '../../modules/aggregation';

import type { ResultsViewType } from './pipeline-results-list';
import PipelineResultsList from './pipeline-results-list';
import PipelinePagination from './pipeline-pagination';
import PipelineEmptyResults from './pipeline-empty-results';
import PipelineResultsViewControls from './pipeline-results-view-controls';

type PipelineResultsWorkspace = {
type PipelineResultsWorkspaceProps = {
documents: Document[];
page: number;
perPage: number;
loading: boolean;
isPrevDisabled: boolean;
isNextDisabled: boolean;
hasEmptyResults: boolean;
error?: string;
onPrev: () => void;
onNext: () => void;
onCancel: () => void;
};

const containerStyles = css({
flexGrow: 1,
width: '100%',
position: 'relative',
overflowY: 'scroll',
overflow: 'hidden',
height: '100vh',
display: 'grid',
gap: spacing[2],
gridTemplateAreas: `
"header"
"results"
`,
gridTemplateRows: 'min-content',
marginTop: spacing[2],
marginBottom: spacing[3],
});

const topStyles = css({
const headerStyles = css({
paddingLeft: spacing[3] + spacing[1],
paddingRight: spacing[5] + spacing[1],
gridArea: 'header',
display: 'flex',
gap: spacing[2],
justifyContent: 'flex-end',
alignItems: 'center',
gap: spacing[2],
});

const resultsStyles = css({
gridArea: 'results',
overflowY: 'auto',
});

const centeredContentStyles = css({
textAlign: 'center',
marginTop: spacing[4],
height: '100%',
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
});

const errorMessageStyles = css({
color: uiColors.red.base,
});

const PipelineResultsWorkspace: React.FunctionComponent<PipelineResultsWorkspace> =
({
documents,
page,
perPage,
loading,
isPrevDisabled,
isNextDisabled,
error,
onPrev,
onNext,
onCancel,
}) => {
const showingFrom = (page - 1) * perPage;
const showingTo = showingFrom + (documents.length || perPage);
export const PipelineResultsWorkspace: React.FunctionComponent<PipelineResultsWorkspaceProps> =
({ documents, hasEmptyResults, loading, error, onCancel }) => {
const [resultsViewType, setResultsViewType] =
useState<ResultsViewType>('document');

const isResultsListHidden = loading || Boolean(error) || hasEmptyResults;

return (
<div data-testid="pipeline-results-workspace" className={containerStyles}>
<div className={topStyles}>
{/* todo: should be replaced with spinners for counts (showingFrom, showingTo) */}
{loading ? (
<Body>Loading ...</Body>
) : (
<Body>
Showing {showingFrom + 1} – {showingTo}
</Body>
)}
<div>
<IconButton
aria-label="Previous page"
disabled={isPrevDisabled}
onClick={() => onPrev()}
>
<Icon glyph="ChevronLeft" />
</IconButton>
<IconButton
aria-label="Next page"
disabled={isNextDisabled}
onClick={() => onNext()}
>
<Icon glyph="ChevronRight" />
</IconButton>
</div>
<div className={headerStyles}>
<PipelinePagination />
<PipelineResultsViewControls
value={resultsViewType}
onChange={setResultsViewType}
/>
</div>
<PipelineResultsList
documents={documents}
data-testid="pipeline-results-workspace"
></PipelineResultsList>
{documents.length === 0 && !error && !loading && (
<Body className={centeredContentStyles}>No results to show</Body>
)}
{/* todo: on error, add a retry button */}
{error && (
<Body className={cx(centeredContentStyles, errorMessageStyles)}>
{error}
</Body>
)}
{loading && (
<div className={centeredContentStyles}>
<Body>Loading ... </Body>
<Button
onClick={() => onCancel()}
variant="primaryOutline"
size="xsmall"
>
Cancel
</Button>
<div className={resultsStyles}>
<PipelineResultsList documents={documents} view={resultsViewType} />
<div className={cx(isResultsListHidden && centeredContentStyles)}>
{loading && (
<CancelLoader
dataTestId="pipeline-results-loader"
progressText="Running aggregation"
cancelText="Stop"
onCancel={() => onCancel()}
/>
)}
{hasEmptyResults && <PipelineEmptyResults />}
{error && <Body className={errorMessageStyles}>{error}</Body>}
</div>
)}
</div>
</div>
);
};

const mapState = ({
aggregation: { documents, isLast, page, limit, loading, error },
aggregation: { documents, loading, error },
}: RootState) => ({
documents,
page,
perPage: limit,
error,
loading,
isPrevDisabled: page <= 1 || loading || Boolean(error),
isNextDisabled: isLast || loading || Boolean(error),
hasEmptyResults: documents.length === 0 && !error && !loading,
});

const mapDispatch = {
onPrev: fetchPrevPage,
onNext: fetchNextPage,
onCancel: cancelAggregation,
};

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
import React from 'react';
import {
css,
spacing,
uiColors,
H2,
Subtitle,
} from '@mongodb-js/compass-components';

const containerStyles = css({
display: 'flex',
gap: spacing[3],
justifyContent: 'center',
alignItems: 'center',
flexDirection: 'column',
});

const headingStyles = css({
color: uiColors.green.dark2,
fontWeight: 'normal',
});

const bodyStyles = css({
fontWeight: 'normal',
});

export const PipelineEmptyResults: React.FunctionComponent = () => {
return (
<div className={containerStyles} data-testid="pipeline-empty-results">
<svg
className="document-list-zero-state-graphic"
xmlns="http://www.w3.org/2000/svg"
width="38"
height="48"
viewBox="0 0 38 48"
>
<g
fill="none"
fillRule="evenodd"
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="2"
transform="translate(1 1)"
>
<polyline stroke="#116149" points="30 .002 30 6.002 36 6.002" />
<polyline stroke="#116149" points="30 26.002 30 32.002 36 32.002" />
<line y1=".002" y2="2.002" stroke="#116149" />
<line y1="12.002" y2="14.002" stroke="#116149" />
<polyline stroke="#116149" points="0 6.002 0 8.002 2 8.002" />
<line y1="18.002" y2="20.002" stroke="#116149" />
<line y1="24.002" y2="26.002" stroke="#116149" />
<line y1="30.002" y2="32.002" stroke="#116149" />
<polyline stroke="#116149" points="0 36.002 0 38.002 2 38.002" />
<line x1="6" x2="8" y1="8.002" y2="8.002" stroke="#116149" />
<line x1="12" x2="14" y1="8.002" y2="8.002" stroke="#116149" />
<line x1="18" x2="20" y1="8.002" y2="8.002" stroke="#116149" />
<line x1="6" x2="8" y1="38.002" y2="38.002" stroke="#116149" />
<line x1="12" x2="14" y1="38.002" y2="38.002" stroke="#116149" />
<line x1="18" x2="20" y1="38.002" y2="38.002" stroke="#116149" />
<polygon
stroke="#16CC62"
points="36 20.002 20 20.002 20 .002 30 .002 36 6.002"
/>
<polygon
stroke="#16CC62"
points="36 46.002 20 46.002 20 26.002 30 26.002 36 32.002"
/>
</g>
</svg>
<H2 className={headingStyles}>No results</H2>
<Subtitle className={bodyStyles}>
Try to modify your pipeline to get results
</Subtitle>
</div>
);
};

export default PipelineEmptyResults;
Loading