diff --git a/packages/compass-aggregations/src/components/pipeline-results-workspace/index.spec.tsx b/packages/compass-aggregations/src/components/pipeline-results-workspace/index.spec.tsx new file mode 100644 index 00000000000..7ca5bf5ea8d --- /dev/null +++ b/packages/compass-aggregations/src/components/pipeline-results-workspace/index.spec.tsx @@ -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 = {} +) => { + render( + + {}} + {...props} + /> + + ); +}; + +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; + }); +}); diff --git a/packages/compass-aggregations/src/components/pipeline-results-workspace/index.tsx b/packages/compass-aggregations/src/components/pipeline-results-workspace/index.tsx index b573ad05c3d..baca86ed04c 100644 --- a/packages/compass-aggregations/src/components/pipeline-results-workspace/index.tsx +++ b/packages/compass-aggregations/src/components/pipeline-results-workspace/index.tsx @@ -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 = - ({ - 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 = + ({ documents, hasEmptyResults, loading, error, onCancel }) => { + const [resultsViewType, setResultsViewType] = + useState('document'); + + const isResultsListHidden = loading || Boolean(error) || hasEmptyResults; + return (
-
- {/* todo: should be replaced with spinners for counts (showingFrom, showingTo) */} - {loading ? ( - Loading ... - ) : ( - - Showing {showingFrom + 1} – {showingTo} - - )} -
- onPrev()} - > - - - onNext()} - > - - -
+
+ +
- - {documents.length === 0 && !error && !loading && ( - No results to show - )} - {/* todo: on error, add a retry button */} - {error && ( - - {error} - - )} - {loading && ( -
- Loading ... - +
+ +
+ {loading && ( + onCancel()} + /> + )} + {hasEmptyResults && } + {error && {error}}
- )} +
); }; 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, }; diff --git a/packages/compass-aggregations/src/components/pipeline-results-workspace/pipeline-empty-results.tsx b/packages/compass-aggregations/src/components/pipeline-results-workspace/pipeline-empty-results.tsx new file mode 100644 index 00000000000..0d4419a4b48 --- /dev/null +++ b/packages/compass-aggregations/src/components/pipeline-results-workspace/pipeline-empty-results.tsx @@ -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 ( +
+ + + + + + + + + + + + + + + + + + + + + +

No results

+ + Try to modify your pipeline to get results + +
+ ); +}; + +export default PipelineEmptyResults; diff --git a/packages/compass-aggregations/src/components/pipeline-results-workspace/pipeline-pagination.spec.tsx b/packages/compass-aggregations/src/components/pipeline-results-workspace/pipeline-pagination.spec.tsx new file mode 100644 index 00000000000..bb9121f3993 --- /dev/null +++ b/packages/compass-aggregations/src/components/pipeline-results-workspace/pipeline-pagination.spec.tsx @@ -0,0 +1,129 @@ +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 { + PipelinePagination, + calculateShowingFrom, + calculateShowingTo, +} from './pipeline-pagination'; + +const renderPipelinePagination = (props: Record = {}) => { + render( + {}} + onNext={() => {}} + {...props} + /> + ); +}; + +describe('PipelinePagination', function () { + describe('PipelinePagination Component', function () { + it('renders correctly', function () { + renderPipelinePagination(); + const container = screen.getByTestId('pipeline-pagination'); + expect( + within(container).getByTestId('pipeline-pagination-desc').textContent + ).to.equal('Showing 1 – 20'); + expect(within(container).getByTestId('pipeline-pagination-prev-action')) + .to.exist; + expect(within(container).getByTestId('pipeline-pagination-next-action')) + .to.exist; + }); + it('does not render desc when disabled', function () { + renderPipelinePagination({ isCountDisabled: true }); + const container = screen.getByTestId('pipeline-pagination'); + expect(() => { + within(container).getByTestId('pipeline-pagination-desc'); + }).to.throw; + }); + it('renders paginate buttons as disabled when disabled', function () { + renderPipelinePagination({ isPrevDisabled: true, isNextDisabled: true }); + const container = screen.getByTestId('pipeline-pagination'); + expect( + within(container) + .getByTestId('pipeline-pagination-prev-action') + .getAttribute('aria-disabled') + ).to.equal('true'); + expect( + within(container) + .getByTestId('pipeline-pagination-next-action') + .getAttribute('aria-disabled') + ).to.equal('true'); + }); + it('calls onPrev when clicked', function () { + const onPrev = spy(); + renderPipelinePagination({ onPrev }); + const container = screen.getByTestId('pipeline-pagination'); + userEvent.click( + within(container).getByTestId('pipeline-pagination-prev-action') + ); + expect(onPrev.calledOnce).to.be.true; + }); + it('calls onNext when clicked', function () { + const onNext = spy(); + renderPipelinePagination({ onNext }); + const container = screen.getByTestId('pipeline-pagination'); + userEvent.click( + within(container).getByTestId('pipeline-pagination-next-action') + ); + expect(onNext.calledOnce).to.be.true; + }); + }); + + describe('PipelinePagination utils', function () { + it('calculates correct showingFrom', function () { + expect( + calculateShowingFrom({ + limit: 20, + page: 1, + }), + 'calculateShowingFrom(20, 1)' + ).to.equal(1); + + expect( + calculateShowingFrom({ + limit: 20, + page: 2, + }), + 'calculateShowingFrom(20, 2)' + ).to.equal(21); + }); + it('calculates correct showingTo', function () { + expect( + calculateShowingTo({ + documentCount: 20, + limit: 20, + page: 1, + }), + 'calculateShowingTo(20, 20, 1)' + ).to.equal(20); + + expect( + calculateShowingTo({ + documentCount: 20, + limit: 20, + page: 3, + }), + 'calculateShowingTo(20, 20, 3)' + ).to.equal(60); + + expect( + calculateShowingTo({ + documentCount: 10, + limit: 20, + page: 3, + }), + 'calculateShowingTo(20, 20, 3)' + ).to.equal(50); + }); + }); +}); diff --git a/packages/compass-aggregations/src/components/pipeline-results-workspace/pipeline-pagination.tsx b/packages/compass-aggregations/src/components/pipeline-results-workspace/pipeline-pagination.tsx new file mode 100644 index 00000000000..80004bdb593 --- /dev/null +++ b/packages/compass-aggregations/src/components/pipeline-results-workspace/pipeline-pagination.tsx @@ -0,0 +1,105 @@ +import React from 'react'; +import { connect } from 'react-redux'; +import { + IconButton, + css, + spacing, + Icon, + Body, +} from '@mongodb-js/compass-components'; + +import type { RootState } from '../../modules'; +import { fetchNextPage, fetchPrevPage } from '../../modules/aggregation'; + +type PipelinePaginationProps = { + showingFrom: number; + showingTo: number; + isCountDisabled: boolean; + isPrevDisabled: boolean; + isNextDisabled: boolean; + onPrev: () => void; + onNext: () => void; +}; + +const containerStyles = css({ + display: 'flex', + alignItems: 'center', + gap: spacing[2], +}); + +export const PipelinePagination: React.FunctionComponent = + ({ + showingFrom, + showingTo, + isCountDisabled, + isPrevDisabled, + isNextDisabled, + onPrev, + onNext, + }) => { + return ( +
+ {!isCountDisabled && ( + + Showing {showingFrom} – {showingTo} + + )} +
+ onPrev()} + > + + + onNext()} + > + + +
+
+ ); + }; + +export const calculateShowingFrom = ({ + limit, + page, +}: Pick): number => { + return (page - 1) * limit + 1; +}; + +export const calculateShowingTo = ({ + limit, + page, + documentCount, +}: Pick & { + documentCount: number; +}): number => { + return (page - 1) * limit + (documentCount || limit); +}; + +const mapState = ({ + aggregation: { documents, isLast, page, limit, loading, error }, +}: RootState) => ({ + showingFrom: calculateShowingFrom({ limit, page }), + showingTo: calculateShowingTo({ + limit, + page, + documentCount: documents.length, + }), + isCountDisabled: Boolean(error), + isPrevDisabled: page <= 1 || loading || Boolean(error), + isNextDisabled: isLast || loading || Boolean(error), +}); + +const mapDispatch = { + onPrev: fetchPrevPage, + onNext: fetchNextPage, +}; + +export default connect(mapState, mapDispatch)(PipelinePagination); diff --git a/packages/compass-aggregations/src/components/pipeline-results-workspace/pipeline-results-list.spec.tsx b/packages/compass-aggregations/src/components/pipeline-results-workspace/pipeline-results-list.spec.tsx new file mode 100644 index 00000000000..3bdf2f81f6f --- /dev/null +++ b/packages/compass-aggregations/src/components/pipeline-results-workspace/pipeline-results-list.spec.tsx @@ -0,0 +1,35 @@ +import React from 'react'; +import { render, screen } from '@testing-library/react'; +import { expect } from 'chai'; + +import PipelineResultsList from './pipeline-results-list'; + +describe('PipelineResultsList', function () { + it('does not render when documents are empty', function () { + render(); + expect(() => { + screen.getByTestId('document-list-item'); + }).to.throw; + }); + + it('renders list view', function () { + render( + + ); + expect( + document.querySelectorAll('[data-test-id="document-list-item"]') + ).to.have.lengthOf(2); + }); + + it('renders json view', function () { + render( + + ); + expect( + document.querySelectorAll('[data-test-id="document-json-item"]') + ).to.have.lengthOf(3); + }); +}); diff --git a/packages/compass-aggregations/src/components/pipeline-results-workspace/pipeline-results-list.tsx b/packages/compass-aggregations/src/components/pipeline-results-workspace/pipeline-results-list.tsx index 342e49ce684..990547aa44b 100644 --- a/packages/compass-aggregations/src/components/pipeline-results-workspace/pipeline-results-list.tsx +++ b/packages/compass-aggregations/src/components/pipeline-results-workspace/pipeline-results-list.tsx @@ -1,92 +1,23 @@ -import React, { useMemo, useState } from 'react'; -import { - css, - SegmentedControl, - SegmentedControlOption, - Overline, - Icon, - spacing -} from '@mongodb-js/compass-components'; +import React, { useMemo } from 'react'; import { DocumentListView, DocumentJsonView } from '@mongodb-js/compass-crud'; -import { useId } from '@react-aria/utils'; +import type { Document } from 'mongodb'; import HadronDocument from 'hadron-document'; +import { css, spacing } from '@mongodb-js/compass-components'; -const viewTypeContainer = css({ - display: 'flex', - alignItems: 'center', - gap: spacing[2], - flex: 'none', - marginLeft: 'auto' -}); - -const ViewTypeSwitch: React.FunctionComponent<{ - value: 'document' | 'json'; - onChange(viewType: 'document' | 'json'): void; -}> = ({ value, onChange }) => { - const labelId = useId(); - const controlId = useId(); - return ( -
- - View - - void} - > - - - - - - - -
- ); -}; - -const listContainer = css({ - display: 'grid', - flex: 1, - gridTemplateRows: '1fr auto', - gridTemplateColumns: '1fr' -}); - -const listControls = css({ - display: 'flex', - // Unintuitive spacing to match the toolbar, if one changes, don't forget to - // change the other - paddingLeft: spacing[3] + spacing[1], - paddingRight: spacing[5] + spacing[1], - paddingBottom: spacing[3] -}); +export type ResultsViewType = 'document' | 'json'; -const list = css({ - flex: 1, - overflowY: 'scroll', - width: '100%', - // Unintuitive spacing to match the toolbar, if one changes, don't forget to - // change the other - paddingLeft: spacing[1], - paddingRight: spacing[2], - paddingBottom: spacing[3] +const containerStyles = css({ + ol: { + padding: 0, + }, + marginLeft: spacing[3] + spacing[1], + marginRight: spacing[4] + spacing[1], }); -export const PipelineResultsList: React.FunctionComponent< - { - documents: Record[]; - } & React.HTMLProps -> = ({ documents, ...rest }) => { - const [viewType, setViewType] = useState<'document' | 'json'>('document'); - +const PipelineResultsList: React.FunctionComponent<{ + documents: Document[]; + view: ResultsViewType; +}> = ({ documents, view }) => { const listProps: React.ComponentProps = useMemo( () => ({ docs: documents.map((doc) => new HadronDocument(doc)), @@ -99,21 +30,18 @@ export const PipelineResultsList: React.FunctionComponent< [documents] ); + if (documents.length === 0) { + return null; + } + + const DocumentView = + view === 'document' ? DocumentListView : DocumentJsonView; + return ( -
-
- -
-
- {viewType === 'document' ? ( - - ) : ( - - )} -
+
+
); }; + +export default PipelineResultsList; diff --git a/packages/compass-aggregations/src/components/pipeline-results-workspace/pipeline-results-view-controls.spec.tsx b/packages/compass-aggregations/src/components/pipeline-results-workspace/pipeline-results-view-controls.spec.tsx new file mode 100644 index 00000000000..74acda18eec --- /dev/null +++ b/packages/compass-aggregations/src/components/pipeline-results-workspace/pipeline-results-view-controls.spec.tsx @@ -0,0 +1,36 @@ +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 PipelineResultsViewControls from './pipeline-results-view-controls'; + +describe('PipelineResultsViewControls', function () { + it('renders correctly', function () { + render( + {}} /> + ); + const container = screen.getByTestId('pipeline-results-view-controls'); + expect(container).to.exist; + expect(within(container).getByText('View')).to.exist; + expect(within(container).getByLabelText('Document list')).to.exist; + expect(within(container).getByLabelText('JSON list')).to.exist; + }); + + it('calls onChange', function () { + const onChangeSpy = spy(); + render( + + ); + const container = screen.getByTestId('pipeline-results-view-controls'); + userEvent.click( + within(container).getByRole('tab', { name: /curly braces icon/i }) + ); + userEvent.click(within(container).getByRole('tab', { name: /menu icon/i })); + + expect(onChangeSpy.calledTwice).to.be.true; + expect(onChangeSpy.firstCall.args).to.deep.equal(['json']); + expect(onChangeSpy.secondCall.args).to.deep.equal(['document']); + }); +}); diff --git a/packages/compass-aggregations/src/components/pipeline-results-workspace/pipeline-results-view-controls.tsx b/packages/compass-aggregations/src/components/pipeline-results-workspace/pipeline-results-view-controls.tsx new file mode 100644 index 00000000000..6929e82a1c4 --- /dev/null +++ b/packages/compass-aggregations/src/components/pipeline-results-workspace/pipeline-results-view-controls.tsx @@ -0,0 +1,58 @@ +import React from 'react'; +import { + css, + SegmentedControl, + SegmentedControlOption, + Overline, + Icon, + spacing, +} from '@mongodb-js/compass-components'; +import { useId } from '@react-aria/utils'; + +import type { ResultsViewType } from './pipeline-results-list'; + +const containerStyles = css({ + display: 'flex', + alignItems: 'center', + gap: spacing[2], + flex: 'none', +}); + +const PipelineResultsViewControls: React.FunctionComponent<{ + value: ResultsViewType; + onChange(viewType: ResultsViewType): void; +}> = ({ value, onChange }) => { + const labelId = useId(); + const controlId = useId(); + return ( +
+ + View + + void} + > + + + + + + + +
+ ); +}; + +export default PipelineResultsViewControls; diff --git a/packages/compass-aggregations/src/components/pipeline-toolbar/pipeline-settings/index.tsx b/packages/compass-aggregations/src/components/pipeline-toolbar/pipeline-settings/index.tsx index 420d6551cf9..bcc337f39e2 100644 --- a/packages/compass-aggregations/src/components/pipeline-toolbar/pipeline-settings/index.tsx +++ b/packages/compass-aggregations/src/components/pipeline-toolbar/pipeline-settings/index.tsx @@ -12,6 +12,7 @@ const containerStyles = css({ gap: spacing[2], gridTemplateAreas: '"settings extraSettings"', alignItems: 'center', + whiteSpace: 'nowrap', }); const settingsStyles = css({ display: 'flex', diff --git a/packages/compass-aggregations/src/components/pipeline-toolbar/pipeline-settings/pipeline-name.spec.tsx b/packages/compass-aggregations/src/components/pipeline-toolbar/pipeline-settings/pipeline-name.spec.tsx index 838e8cff854..5fbb1b88fbc 100644 --- a/packages/compass-aggregations/src/components/pipeline-toolbar/pipeline-settings/pipeline-name.spec.tsx +++ b/packages/compass-aggregations/src/components/pipeline-toolbar/pipeline-settings/pipeline-name.spec.tsx @@ -14,9 +14,12 @@ describe('PipelineName', function () { it('renders Untitled as default name with modified text - when modified', function () { render(); - expect(screen.getByTestId('pipeline-name').textContent.trim()).to.equal( - 'Untitled - modified' - ); + expect( + screen + .getByTestId('pipeline-name') + .textContent.trim() + .replace(/\u00a0/g, ' ') + ).to.equal('Untitled – modified'); }); it('renders pipeline name', function () { @@ -28,8 +31,11 @@ describe('PipelineName', function () { it('renders pipeline name with modified text - when modified', function () { render(); - expect(screen.getByTestId('pipeline-name').textContent.trim()).to.equal( - 'Name changed - modified' - ); + expect( + screen + .getByTestId('pipeline-name') + .textContent.trim() + .replace(/\u00a0/g, ' ') + ).to.equal('Name changed – modified'); }); }); diff --git a/packages/compass-aggregations/src/components/pipeline-toolbar/pipeline-settings/pipeline-name.tsx b/packages/compass-aggregations/src/components/pipeline-toolbar/pipeline-settings/pipeline-name.tsx index 415209df283..2b903318fda 100644 --- a/packages/compass-aggregations/src/components/pipeline-toolbar/pipeline-settings/pipeline-name.tsx +++ b/packages/compass-aggregations/src/components/pipeline-toolbar/pipeline-settings/pipeline-name.tsx @@ -17,7 +17,7 @@ const nameStyles = css({ const modifiedStyles = css({ fontStyle: 'italic', - whiteSpace: 'pre-wrap', + whiteSpace: 'nowrap', }); type PipelineNameProps = { @@ -45,7 +45,7 @@ export const PipelineName: React.FunctionComponent = ({ {name} )} - {isModified && - modified} + {isModified &&  – modified} ); }; diff --git a/packages/compass-aggregations/src/modules/aggregation.spec.ts b/packages/compass-aggregations/src/modules/aggregation.spec.ts index 0b3cc386ce0..223a49b5e19 100644 --- a/packages/compass-aggregations/src/modules/aggregation.spec.ts +++ b/packages/compass-aggregations/src/modules/aggregation.spec.ts @@ -50,7 +50,7 @@ describe('aggregation module', function () { it('should return the initial state', function () { expect(reducer(undefined, {} as any)).to.deep.equal({ documents: [], - page: 0, + page: 1, limit: 20, isLast: false, loading: false, diff --git a/packages/compass-aggregations/src/modules/aggregation.ts b/packages/compass-aggregations/src/modules/aggregation.ts index 591045d39e3..f57382bde58 100644 --- a/packages/compass-aggregations/src/modules/aggregation.ts +++ b/packages/compass-aggregations/src/modules/aggregation.ts @@ -5,6 +5,8 @@ import type { RootState } from '.'; import { DEFAULT_MAX_TIME_MS } from '../constants'; import { generateStage } from './stage'; import { aggregatePipeline } from '../utils/cancellable-aggregation'; +import { ActionTypes as WorkspaceActionTypes } from './workspace'; +import type { Actions as WorkspaceActions } from './workspace'; import createLogger from '@mongodb-js/compass-logging'; const { log, mongoLogId } = createLogger('compass-aggregations'); @@ -56,14 +58,20 @@ export type State = { export const INITIAL_STATE: State = { documents: [], - page: 0, + page: 1, limit: 20, isLast: false, loading: false, }; -const reducer: Reducer = (state = INITIAL_STATE, action) => { +const reducer: Reducer = (state = INITIAL_STATE, action) => { switch (action.type) { + case WorkspaceActionTypes.WorkspaceChanged: + return { + ...INITIAL_STATE, + page: 1, + limit: 20, + }; case ActionTypes.AggregationStarted: return { ...state, diff --git a/packages/compass-aggregations/src/modules/workspace.ts b/packages/compass-aggregations/src/modules/workspace.ts index 4b7405bc0f7..26486d6fcf0 100644 --- a/packages/compass-aggregations/src/modules/workspace.ts +++ b/packages/compass-aggregations/src/modules/workspace.ts @@ -5,23 +5,23 @@ import type { Actions as AggregationActions } from './aggregation'; export type Workspace = 'builder' | 'results'; -enum ActionTypes { - ChangeWorkspace = 'compass-aggregations/changeWorkspace', +export enum ActionTypes { + WorkspaceChanged = 'compass-aggregations/workspaceChanged', } -type ChangeWorkspaceAction = { - type: ActionTypes.ChangeWorkspace; +type WorkspaceChangedAction = { + type: ActionTypes.WorkspaceChanged; view: Workspace; }; -export type Actions = ChangeWorkspaceAction; +export type Actions = WorkspaceChangedAction; export type State = Workspace; export const INITIAL_STATE: State = 'builder'; const reducer: Reducer = (state = INITIAL_STATE, action) => { switch (action.type) { - case ActionTypes.ChangeWorkspace: + case ActionTypes.WorkspaceChanged: return action.view; case AggregationActionTypes.AggregationStarted: return 'results'; @@ -30,8 +30,8 @@ const reducer: Reducer = (state = INITIAL_S } }; -export const changeWorkspace = (view: Workspace): ChangeWorkspaceAction => ({ - type: ActionTypes.ChangeWorkspace, +export const changeWorkspace = (view: Workspace): WorkspaceChangedAction => ({ + type: ActionTypes.WorkspaceChanged, view, }); diff --git a/packages/compass-components/src/components/cancel-loader.tsx b/packages/compass-components/src/components/cancel-loader.tsx index 23532ebb117..395ee733393 100644 --- a/packages/compass-components/src/components/cancel-loader.tsx +++ b/packages/compass-components/src/components/cancel-loader.tsx @@ -1,55 +1,21 @@ -import { css } from '@leafygreen-ui/emotion'; import React from 'react'; +import { spacing } from '@leafygreen-ui/tokens'; +import { css } from '@leafygreen-ui/emotion'; +import { uiColors } from '@leafygreen-ui/palette'; + +import { Subtitle, Button } from './leafygreen'; +import { SpinLoader } from './spin-loader'; -const cancelLoaderStyle = css({ - height: '100%', +const containerStyles = css({ display: 'flex', + gap: spacing[2], flexDirection: 'column', - alignItems: 'center', justifyContent: 'center', - padding: '10px 0', -}); - -const progressTextStyle = css({ - margin: '0 auto 30px', - textAlign: 'left', - fontSize: '24px', - color: '#807f7f', - fontWeight: 200, -}); - -const spinnerStyle = css({ - animation: 'fa-spin 2s infinite linear', - width: '40px', - textAlign: 'center', - display: 'inline-block', - textRendering: 'auto', - font: 'normal normal normal 20px/1 FontAwesome', - '&::before': { - boxSizing: 'border-box', - content: '"\\f1ce"', - }, + alignItems: 'center', }); -const buttonStyle = css({ - padding: '0 10px 0 10px', - height: '28px', - fontWeight: 'bold', - fontSize: '13px', - lineHeight: '26px', - textTransform: 'uppercase', - backgroundColor: 'transparent', - border: '1px solid #13AA52', - borderRadius: '3px', - boxShadow: 'none', - color: '#13AA52', - fontFamily: 'Akzidenz', - WebkitAppearance: 'button', - cursor: 'pointer', - overflow: 'visible', - margin: 0, - boxSizing: 'border-box', - outline: 'none', +const textStyles = css({ + color: uiColors.green.dark2, }); function CancelLoader({ @@ -64,16 +30,12 @@ function CancelLoader({ onCancel: () => void; }): React.ReactElement { return ( -
-
- - {progressText} -
-
- -
+
+ + {progressText} +
); } diff --git a/packages/compass-crud/src/components/document-list.jsx b/packages/compass-crud/src/components/document-list.jsx index 939cddffb36..b6497b7c855 100644 --- a/packages/compass-crud/src/components/document-list.jsx +++ b/packages/compass-crud/src/components/document-list.jsx @@ -98,12 +98,16 @@ class DocumentList extends React.Component { * Render the fetching indicator with cancel button */ renderFetching() { - return (); + return ( +
+ +
+ ); } /** diff --git a/packages/compass-crud/src/components/document-list.less b/packages/compass-crud/src/components/document-list.less index 09d78f5b20e..4fa98a27bcc 100644 --- a/packages/compass-crud/src/components/document-list.less +++ b/packages/compass-crud/src/components/document-list.less @@ -155,3 +155,9 @@ } } } + +.loader { + height: 100%; + display: flex; + justify-content: center; +} diff --git a/packages/compass-schema/src/components/compass-schema/compass-schema.jsx b/packages/compass-schema/src/components/compass-schema/compass-schema.jsx index be5896ee3a9..91efa0c7ed2 100644 --- a/packages/compass-schema/src/components/compass-schema/compass-schema.jsx +++ b/packages/compass-schema/src/components/compass-schema/compass-schema.jsx @@ -163,12 +163,16 @@ class Schema extends Component { } renderAnalyzing() { - return (); + return ( +
+ +
+ ); } /** diff --git a/packages/compass-schema/src/components/compass-schema/compass-schema.module.less b/packages/compass-schema/src/components/compass-schema/compass-schema.module.less index 8d245a00128..a47e835db39 100644 --- a/packages/compass-schema/src/components/compass-schema/compass-schema.module.less +++ b/packages/compass-schema/src/components/compass-schema/compass-schema.module.less @@ -23,6 +23,12 @@ flex-shrink: 1; } +.loader { + height: 100%; + display: flex; + justify-content: center; +} + .schema { width: 100%; padding: 15px;