Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
dbf40ab
chore(explain-aggregation): explain aggregation
mabaasit May 16, 2022
08c0ac1
chore(explain-aggregation): explain aggregation
mabaasit May 17, 2022
4eabca7
chore(explain-aggregation): tests
mabaasit May 17, 2022
6346d21
chore(explain-aggregation): e2e tests
mabaasit May 17, 2022
71b2751
Merge branch 'main' of github.com:mongodb-js/compass into COMPASS-578…
mabaasit May 17, 2022
f1fd5b3
chore(explain-aggregation): e2e tests
mabaasit May 17, 2022
89d0ca2
chore(explain-aggregation): log id and depcheck
mabaasit May 17, 2022
c59cb92
chore(explain-aggregation): pretty e2e
mabaasit May 17, 2022
1e2e044
Update packages/compass-aggregations/src/utils/cancellable-aggregatio…
mabaasit May 17, 2022
be6f5b4
chore(explain-aggregation): remove network error
mabaasit May 17, 2022
719fa9e
Merge branch 'COMPASS-5788-show-explain-results' of github.com:mongod…
mabaasit May 17, 2022
e35fa26
chore(explain-aggregation): remove network error
mabaasit May 17, 2022
b91eb1b
Merge branch 'main' into COMPASS-5788-show-explain-results
mabaasit May 17, 2022
1941956
chore(explain-aggregation): clean up react
mabaasit May 17, 2022
8adaaba
Merge branch 'COMPASS-5788-show-explain-results' of github.com:mongod…
mabaasit May 17, 2022
15663f4
Merge branch 'main' into COMPASS-5788-show-explain-results
mabaasit May 17, 2022
0f4532a
chore(explain-aggregation): configurable explain button
mabaasit May 18, 2022
b42c24b
Merge branch 'main' of github.com:mongodb-js/compass into COMPASS-578…
mabaasit May 18, 2022
aa62431
chore(explain-aggregation): tests
mabaasit May 18, 2022
e4216f6
chore(explain-aggregation): doc types
mabaasit May 18, 2022
2900fe9
Merge branch 'COMPASS-5788-show-explain-results' of github.com:mongod…
mabaasit May 18, 2022
f4122d5
Update packages/compass-aggregations/src/modules/explain.ts
mabaasit May 18, 2022
40d6680
chore(explain-aggregation): fix ux issues
mabaasit May 18, 2022
e35201a
chore(explain-aggregation): isDataLake
mabaasit May 18, 2022
f37e9c4
Merge branch 'COMPASS-5788-show-explain-results' of github.com:mongod…
mabaasit May 18, 2022
c463a70
chore(explain-aggregation): explain verbosity
mabaasit May 18, 2022
19e7d97
Merge branch 'main' into COMPASS-5788-show-explain-results
mabaasit May 18, 2022
2e1c598
chore(explain-aggregation): lint
mabaasit May 18, 2022
54431a6
Merge branch 'COMPASS-5788-show-explain-results' of github.com:mongod…
mabaasit May 18, 2022
3845394
chore(explain-aggregation): better instance handling
mabaasit May 19, 2022
6071aaf
Merge branch 'main' into COMPASS-5788-show-explain-results
mabaasit May 19, 2022
78b7448
Update packages/compass-aggregations/src/components/pipeline-explain/…
mabaasit May 19, 2022
8481f21
Merge branch 'main' of github.com:mongodb-js/compass into COMPASS-578…
mabaasit May 19, 2022
3932ed4
Merge branch 'main' into COMPASS-5788-show-explain-results
mabaasit May 19, 2022
dd83d1c
chore(explain-aggregation): ts fix
mabaasit May 19, 2022
dccc229
Merge branch 'COMPASS-5788-show-explain-results' of github.com:mongod…
mabaasit May 19, 2022
4e34ab2
feat(data-service): wait for explain modal
mabaasit May 19, 2022
f407c7a
chore(explain-aggregation): test fix
mabaasit May 19, 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
2 changes: 2 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-aggregations/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,7 @@
},
"dependencies": {
"@mongodb-js/compass-logging": "^0.12.0",
"@mongodb-js/explain-plan-helper": "^0.9.0",
"@mongodb-js/mongodb-redux-common": "^1.12.0",
"acorn-loose": "^8.0.2",
"astring": "^1.7.0",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import React from 'react';
import { Badge, BadgeVariant, Body } from '@mongodb-js/compass-components';
import type { IndexInformation } from '@mongodb-js/explain-plan-helper';

type ExplainIndexesProps = {
indexes: IndexInformation[];
};

export const ExplainIndexes: React.FunctionComponent<ExplainIndexesProps> = ({
indexes,
}) => {
if (indexes.filter(({ index }) => index).length === 0) {
return <Body weight="medium">No index available for this query.</Body>;
}

return (
<div>
{indexes.map((info, idx) => (
<Badge key={idx} variant={BadgeVariant.LightGray}>
{info.index} {info.shard && <>({info.shard})</>}
</Badge>
))}
</div>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import React from 'react';
import { Body, Subtitle, css, spacing } from '@mongodb-js/compass-components';
import type { IndexInformation } from '@mongodb-js/explain-plan-helper';

import { ExplainIndexes } from './explain-indexes';

type ExplainQueryPerformanceProps = {
executionTimeMillis: number;
nReturned: number;
usedIndexes: IndexInformation[];
};

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

const statsStyles = css({
gap: spacing[1],
display: 'flex',
flexDirection: 'column',
});

const statItemStyles = css({
display: 'flex',
gap: spacing[1],
});

const statTitleStyles = css({
whiteSpace: 'nowrap',
});

export const ExplainQueryPerformance: React.FunctionComponent<ExplainQueryPerformanceProps> =
({ nReturned, executionTimeMillis, usedIndexes }) => {
return (
<div
className={containerStyles}
data-testid="pipeline-explain-results-summary"
>
<Subtitle>Query Performance Summary</Subtitle>
<div className={statsStyles}>
{typeof nReturned === 'number' && (
<div className={statItemStyles}>
<Body>Documents returned:</Body>
<Body weight="medium">{nReturned}</Body>
</div>
)}
{executionTimeMillis > 0 && (
<div className={statItemStyles}>
<Body>Actual query execution time(ms):</Body>
<Body weight="medium">{executionTimeMillis}</Body>
</div>
)}
<div className={statItemStyles}>
<Body className={statTitleStyles}>
Query used the following indexes:
</Body>
<ExplainIndexes indexes={usedIndexes} />
</div>
</div>
</div>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import React from 'react';
import { css, spacing, Card } from '@mongodb-js/compass-components';
import { Document } from '@mongodb-js/compass-crud';
import HadronDocument from 'hadron-document';

import type { ExplainData } from '../../modules/explain';
import { ExplainQueryPerformance } from './explain-query-performance';

type ExplainResultsProps = {
plan: ExplainData['plan'];
stats?: ExplainData['stats'];
};

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

const cardStyles = css({
// 170px works with minimum-height of compass
// todo: handle height for bigger sized compass
Copy link
Collaborator

@gribnoysup gribnoysup May 19, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As an addition to this TODO, currently because "copy to clipboard" button is part of the Document component and not the card, the whole thing scrolls including the button:

image

I think we might want to set the height here on the document fields container and not the wrapper card as a way to prevent it, but there is no way to do it now, we would need to change the component interface a bit, and it's a very small thing so totally can be addressed later

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sure :)

height: '170px',
overflowY: 'scroll',
});

export const ExplainResults: React.FunctionComponent<ExplainResultsProps> = ({
plan,
stats,
}) => {
return (
<div className={containerStyles} data-testid="pipeline-explain-results">
{stats && (
<ExplainQueryPerformance
nReturned={stats.nReturned}
executionTimeMillis={stats.executionTimeMillis}
usedIndexes={stats.usedIndexes}
/>
)}
<Card className={cardStyles} data-testid="pipeline-explain-results-json">
<Document
doc={plan}
editable={false}
copyToClipboard={() => {
void navigator.clipboard.writeText(
new HadronDocument(plan).toEJSON()
);
}}
/>
</Card>
</div>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
import React from 'react';
import type { ComponentProps } from 'react';
import { render, screen, within } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { spy } from 'sinon';
import { expect } from 'chai';

import { PipelineExplain } from './index';

const renderPipelineExplain = (
props: Partial<ComponentProps<typeof PipelineExplain>> = {}
) => {
render(
<PipelineExplain
isLoading={false}
isModalOpen={true}
onCancelExplain={() => {}}
onCloseModal={() => {}}
onRunExplain={() => {}}
{...props}
/>
);
};

describe('PipelineExplain', function () {
it('renders loading state', function () {
const onCancelExplainSpy = spy();
renderPipelineExplain({
isLoading: true,
onCancelExplain: onCancelExplainSpy,
});
const modal = screen.getByTestId('pipeline-explain-modal');
expect(within(modal).getByTestId('pipeline-explain-cancel')).to.exist;
expect(onCancelExplainSpy.callCount).to.equal(0);

userEvent.click(within(modal).getByText(/cancel/gi), null, {
skipPointerEventsCheck: true,
});
expect(onCancelExplainSpy.callCount).to.equal(1);

expect(() => {
within(modal).getByTestId('pipeline-explain-footer-close-button');
}, 'does not show footer in loading state').to.throw;
});

it('renders error state', function () {
renderPipelineExplain({
error: 'Error occurred',
});
const modal = screen.getByTestId('pipeline-explain-modal');
expect(within(modal).getByTestId('pipeline-explain-error')).to.exist;
expect(within(modal).findByText('Error occurred')).to.exist;
expect(() => {
within(modal).getByTestId('pipeline-explain-retry-button');
}).to.throw;

expect(within(modal).getByTestId('pipeline-explain-footer-close-button')).to
.exist;
});

it('renders explain results - without stats', function () {
renderPipelineExplain({
explain: {
plan: {
stages: [],
},
},
});
const results = screen.getByTestId('pipeline-explain-results');
expect(within(results).getByTestId('pipeline-explain-results-json')).to
.exist;
expect(() => {
within(results).getByTestId('pipeline-explain-results-summary');
}).to.throw;

expect(screen.getByTestId('pipeline-explain-footer-close-button')).to.exist;
});

it('renders explain results - with stats', function () {
renderPipelineExplain({
explain: {
stats: {
executionTimeMillis: 20,
nReturned: 100,
usedIndexes: [{ index: 'name', shard: 'shard1' }],
},
plan: {
stages: [],
},
},
});
const results = screen.getByTestId('pipeline-explain-results');
expect(results).to.exist;
expect(within(results).getByTestId('pipeline-explain-results-json')).to
.exist;

const summary = within(results).getByTestId(
'pipeline-explain-results-summary'
);
expect(summary).to.exist;

expect(within(summary).getByText(/documents returned/gi)).to.exist;
expect(within(summary).getByText(/actual query execution time/gi)).to.exist;
expect(within(summary).getByText(/query used the following indexes/gi)).to
.exist;

expect(screen.getByTestId('pipeline-explain-footer-close-button')).to.exist;
});
});
Original file line number Diff line number Diff line change
@@ -1,36 +1,103 @@
import React from 'react';
import { Modal, H3, Body } from '@mongodb-js/compass-components';
import {
css,
spacing,
Modal,
CancelLoader,
H3,
ModalFooter,
Button,
ErrorSummary,
} from '@mongodb-js/compass-components';
import { connect } from 'react-redux';

import type { RootState } from '../../modules';
import { closeExplainModal } from '../../modules/explain';
import type { ExplainData } from '../../modules/explain';
import { closeExplainModal, cancelExplain } from '../../modules/explain';
import { ExplainResults } from './explain-results';

type PipelineExplainProps = {
isModalOpen: boolean;
isLoading: boolean;
error?: string;
explain?: ExplainData;
onCloseModal: () => void;
onCancelExplain: () => void;
};

const contentStyles = css({
marginTop: spacing[3],
marginBottom: spacing[3],
});

const footerStyles = css({
paddingRight: 0,
paddingBottom: 0,
});

export const PipelineExplain: React.FunctionComponent<PipelineExplainProps> = ({
isModalOpen,
isLoading,
error,
explain,
onCloseModal,
onCancelExplain,
}) => {
let content = null;
if (isLoading) {
content = (
<CancelLoader
data-testid="pipeline-explain-cancel"
cancelText="Cancel"
onCancel={() => onCancelExplain()}
progressText="Running explain"
/>
);
} else if (error) {
content = (
<ErrorSummary data-testid="pipeline-explain-error" errors={error} />
);
} else if (explain) {
content = <ExplainResults plan={explain.plan} stats={explain.stats} />;
}

if (!content) {
return null;
}

return (
<Modal
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

On a default compass window size this modal height makes the modal scroll even when there is not that much content on the screen, I think it would make sense to fix that

Kapture 2022-05-18 at 10 59 45

setOpen={onCloseModal}
open={isModalOpen}
data-testid="pipeline-explain-modal"
>
<H3>Explain</H3>
<Body>Implementation in progress ...</Body>
<div className={contentStyles}>{content}</div>
{!isLoading && (
<ModalFooter className={footerStyles}>
<Button
onClick={onCloseModal}
data-testid="pipeline-explain-footer-close-button"
>
Close
</Button>
</ModalFooter>
)}
</Modal>
);
};

const mapState = ({ explain: { isModalOpen } }: RootState) => ({
isModalOpen: isModalOpen,
const mapState = ({
explain: { isModalOpen, isLoading, error, explain },
}: RootState) => ({
isModalOpen,
isLoading,
error,
explain,
});

const mapDispatch = {
onCloseModal: closeExplainModal,
onCancelExplain: cancelExplain,
};
export default connect(mapState, mapDispatch)(PipelineExplain);
Loading