Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
42 changes: 42 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions packages/compass-components/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@
"@leafygreen-ui/polymorphic": "^3.1.0",
"@leafygreen-ui/popover": "^14.3.1",
"@leafygreen-ui/portal": "^7.1.0",
"@leafygreen-ui/progress-bar": "^1.0.7",
"@leafygreen-ui/radio-box-group": "^15.0.10",
"@leafygreen-ui/radio-group": "^13.0.10",
"@leafygreen-ui/search-input": "^6.1.1",
Expand Down
2 changes: 2 additions & 0 deletions packages/compass-components/src/components/leafygreen.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import {
import { Menu, MenuSeparator, MenuItem } from '@leafygreen-ui/menu';
export type { MenuItemProps } from '@leafygreen-ui/menu';
import { InfoSprinkle } from '@leafygreen-ui/info-sprinkle';
import { ProgressBar } from '@leafygreen-ui/progress-bar';

// If a leafygreen Menu (and therefore MenuItems) makes its way into a <form>,
// clicking on a menu item will submit that form. This is because it uses a button
Expand Down Expand Up @@ -191,6 +192,7 @@ export {
Combobox,
ComboboxGroup,
ComboboxOption,
ProgressBar,
};

export * as Avatar from '@leafygreen-ui/avatar';
Expand Down
58 changes: 52 additions & 6 deletions packages/compass-components/src/components/loader.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,10 @@ import { palette } from '@leafygreen-ui/palette';
import { spacing } from '@leafygreen-ui/tokens';
import { css, cx, keyframes } from '@leafygreen-ui/emotion';
import { useDarkMode } from '../hooks/use-theme';
import { Subtitle, Button } from './leafygreen';
import { Subtitle, Button, ProgressBar } from './leafygreen';
import type { ProgressBarProps } from '@leafygreen-ui/progress-bar';

const containerStyles = css({
const loaderContainerStyles = css({
display: 'flex',
gap: spacing[200],
flexDirection: 'column',
Expand All @@ -14,6 +15,18 @@ const containerStyles = css({
maxWidth: spacing[1600] * 8,
});

const progressContainerStyles = css({
width: '100%',
height: '100%',
display: 'flex',
flexDirection: 'column',
justifyContent: 'center',
alignItems: 'center',
maxWidth: spacing[1600] * 8,
gap: spacing[500],
margin: '0 auto',
});

const textStyles = css({
color: palette.green.dark2,
textAlign: 'center',
Expand All @@ -36,11 +49,16 @@ type SpinLoaderWithLabelProps = Omit<SpinLoaderProps, 'size' | 'title'> & {
['data-testid']?: string;
};

type CancelLoaderProps = Omit<SpinLoaderWithLabelProps, 'children'> & {
type CancelActionProps = {
onCancel(): void;
cancelText: string;
cancelText?: string;
};

type CancelLoaderProps = Omit<SpinLoaderWithLabelProps, 'children'> &
CancelActionProps;

type ProgressLoaderWithCancelProps = CancelActionProps & ProgressBarProps;

const shellLoaderSpin = keyframes`
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
Expand Down Expand Up @@ -94,7 +112,10 @@ function SpinLoaderWithLabel({
const darkMode = useDarkMode(_darkMode);

return (
<div className={cx(containerStyles, className)} data-testid={dataTestId}>
<div
className={cx(loaderContainerStyles, className)}
data-testid={dataTestId}
>
<SpinLoader
size={spacing[600]}
darkMode={darkMode}
Expand Down Expand Up @@ -126,4 +147,29 @@ function CancelLoader({
);
}

export { SpinLoaderWithLabel, SpinLoader, CancelLoader };
function ProgressLoaderWithCancel({
cancelText = 'Cancel',
className,
onCancel,
...props
}: ProgressLoaderWithCancelProps): React.ReactElement {
return (
<div className={cx(progressContainerStyles, className)}>
<ProgressBar {...props} />
<Button
variant="primaryOutline"
onClick={onCancel}
data-testid={`${props['data-testid'] ?? 'spin-loader'}-button`}
>
{cancelText}
</Button>
</div>
);
}

export {
SpinLoaderWithLabel,
SpinLoader,
CancelLoader,
ProgressLoaderWithCancel,
};
1 change: 1 addition & 0 deletions packages/compass-components/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ export {
SpinLoader,
SpinLoaderWithLabel,
CancelLoader,
ProgressLoaderWithCancel,
} from './components/loader';
import { ResizeHandle, ResizeDirection } from './components/resize-handle';
import { Accordion } from './components/accordion';
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
import React from 'react';
import { expect } from 'chai';
import { screen, waitFor } from '@mongodb-js/testing-library-compass';
import AnalysisProgressStatus from './analysis-progress-status';
import { renderWithStore, testConnections } from '../../test/setup-store';
import {
AnalysisProcessActionTypes,
startAnalysis,
} from '../store/analysis-process';
import { createSandboxFromDefaultPreferences } from 'compass-preferences-model';

describe('AnalysisProgressStatus', () => {
async function renderAnalysisProgressStatus({
automaticallyInferRelations = false,
} = {}) {
const preferences = await createSandboxFromDefaultPreferences();
const { store } = renderWithStore(<AnalysisProgressStatus />, {
services: {
preferences,
},
});
void store.dispatch(
startAnalysis(
'My Diagram',
testConnections[0].id,
'testDB',
['coll1', 'coll2', 'coll3'],
{ automaticallyInferRelations }
)
);
return store;
}

it('Allows cancellation', async () => {
const store = await renderAnalysisProgressStatus();
expect(screen.getByText('Sampling collections…')).to.be.visible;
expect(screen.getByText('Cancel')).to.be.visible;
screen.getByText('Cancel').click();
await waitFor(() => {
expect(store.getState().analysisProgress.step).to.equal('IDLE');
expect(store.getState().diagram).to.be.null;
});
});

describe('Keeps showing progress along the way', () => {
it('Without relationship inferring', async () => {
const store = await renderAnalysisProgressStatus({
automaticallyInferRelations: false,
});
expect(screen.getByText('Sampling collections…')).to.be.visible;
expect(screen.getByText('0/3')).to.be.visible;

// 2 out of 3 samples fetched, 1 analyzed
store.dispatch({
type: AnalysisProcessActionTypes.NAMESPACE_SAMPLE_FETCHED,
});
store.dispatch({
type: AnalysisProcessActionTypes.NAMESPACE_SAMPLE_FETCHED,
});
store.dispatch({
type: AnalysisProcessActionTypes.NAMESPACE_SCHEMA_ANALYZED,
});

expect(screen.getByText('Sampling collections…')).to.be.visible;
expect(screen.getByText('2/3')).to.be.visible;

// Last sample fetched
store.dispatch({
type: AnalysisProcessActionTypes.NAMESPACE_SAMPLE_FETCHED,
});

expect(screen.getByText('Analyzing collection schemas…')).to.be.visible;
expect(screen.getByText('1/3')).to.be.visible;

// Finish analyzing
store.dispatch({
type: AnalysisProcessActionTypes.NAMESPACE_SCHEMA_ANALYZED,
});
store.dispatch({
type: AnalysisProcessActionTypes.NAMESPACE_SCHEMA_ANALYZED,
});

expect(screen.queryByText('Inferring relationships between collections…'))
.not.to.exist;
expect(screen.getByText('Preparing diagram…')).to.be.visible;
});

it('With relationship inferring', async () => {
const store = await renderAnalysisProgressStatus({
automaticallyInferRelations: true,
});
expect(screen.getByText('Sampling collections…')).to.be.visible;
expect(screen.getByText('0/3')).to.be.visible;

// Fetch and analyze all samples
store.dispatch({
type: AnalysisProcessActionTypes.NAMESPACE_SAMPLE_FETCHED,
});
store.dispatch({
type: AnalysisProcessActionTypes.NAMESPACE_SAMPLE_FETCHED,
});
store.dispatch({
type: AnalysisProcessActionTypes.NAMESPACE_SAMPLE_FETCHED,
});
store.dispatch({
type: AnalysisProcessActionTypes.NAMESPACE_SCHEMA_ANALYZED,
});
store.dispatch({
type: AnalysisProcessActionTypes.NAMESPACE_SCHEMA_ANALYZED,
});
store.dispatch({
type: AnalysisProcessActionTypes.NAMESPACE_SCHEMA_ANALYZED,
});

expect(screen.getByText('Inferring relationships between collections…'))
.to.be.visible;
expect(screen.queryByText('0/3')).not.to.exist;

// Infer some relationships
store.dispatch({
type: AnalysisProcessActionTypes.NAMESPACE_RELATIONS_INFERRED,
});
store.dispatch({
type: AnalysisProcessActionTypes.NAMESPACE_RELATIONS_INFERRED,
});

expect(screen.getByText('Inferring relationships between collections…'))
.to.be.visible;
expect(screen.queryByText('2/3')).not.to.exist;

// Finish inferring
store.dispatch({
type: AnalysisProcessActionTypes.NAMESPACE_RELATIONS_INFERRED,
});

expect(screen.getByText('Preparing diagram…')).to.be.visible;
});
});
});
Loading
Loading