-
Notifications
You must be signed in to change notification settings - Fork 190
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat(crud): Bulk update modal COMPASS-7325 #5007
Changes from 16 commits
b499507
5df60d9
0439e13
7784da7
c35d41e
e60ffc1
986a421
704e31a
b135bea
4846afc
779f70d
e93d849
5ec0a68
3d21975
3fa1bc9
faea096
bf87b03
0b348d3
e22de96
dbf1b0d
588f86d
a01c56f
2e38636
b3c89bc
f551e33
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,298 @@ | ||
import React, { useMemo, useState } from 'react'; | ||
import Document from './document'; | ||
import HadronDocument from 'hadron-document'; | ||
|
||
import { toJSString } from 'mongodb-query-parser'; | ||
|
||
import { | ||
FormModal, | ||
css, | ||
cx, | ||
spacing, | ||
palette, | ||
Label, | ||
Banner, | ||
BannerVariant, | ||
KeylineCard, | ||
Description, | ||
Link, | ||
InfoSprinkle, | ||
useDarkMode, | ||
} from '@mongodb-js/compass-components'; | ||
|
||
import type { Annotation } from '@mongodb-js/compass-editor'; | ||
import { CodemirrorMultilineEditor } from '@mongodb-js/compass-editor'; | ||
|
||
import type { BSONObject } from '../stores/crud-store'; | ||
import type { UpdatePreview } from 'mongodb-data-service'; | ||
|
||
const columnsStyles = css({ | ||
marginTop: spacing[4], | ||
display: 'grid', | ||
width: '100%', | ||
gap: spacing[4], | ||
gridTemplateColumns: 'repeat(2, minmax(0, 1fr))', | ||
}); | ||
|
||
const queryStyles = css({ | ||
display: 'flex', | ||
gap: spacing[4], | ||
flexDirection: 'column', | ||
}); | ||
|
||
const filterLabelContainerStyles = css({ | ||
display: 'flex', | ||
gap: spacing[1], | ||
alignItems: 'center', | ||
}); | ||
|
||
const queryFieldStyles = css({}); | ||
|
||
const updateFieldStyles = css({ | ||
flex: 1, | ||
}); | ||
|
||
const descriptionStyles = css({ | ||
marginBottom: spacing[2], | ||
}); | ||
|
||
const previewStyles = css({ | ||
contain: 'size', | ||
overflow: 'scroll', | ||
}); | ||
|
||
const previewDescriptionStyles = css({ | ||
display: 'inline', | ||
}); | ||
|
||
const codeContainerStyles = css({ | ||
paddingTop: spacing[2], | ||
paddingBottom: spacing[2], | ||
}); | ||
|
||
const codeDarkContainerStyles = css({ | ||
background: palette.gray.dark4, | ||
}); | ||
|
||
const codeLightContainerStyles = css({ | ||
backgroundColor: palette.gray.light3, | ||
}); | ||
|
||
const inlineContainerStyles = css({ | ||
paddingLeft: spacing[2], | ||
pre: { | ||
margin: 0, | ||
}, | ||
}); | ||
|
||
const multilineContainerStyles = css({ | ||
maxHeight: spacing[4] * 20, | ||
}); | ||
|
||
// We use custom color here so need to disable default one that we use | ||
// everywhere else | ||
const codeEditorStyles = css({ | ||
background: 'transparent !important', | ||
'& .cm-gutters': { | ||
background: 'transparent !important', | ||
}, | ||
'& .cm-editor': { | ||
background: 'transparent !important', | ||
}, | ||
}); | ||
|
||
const bannerContainerStyles = css({ | ||
// don't jump when an error appears | ||
minHeight: spacing[4] * 2 + 2, | ||
overflow: 'hidden', | ||
}); | ||
|
||
const bannerStyles = css({ | ||
flex: 'none', | ||
marginTop: spacing[2], | ||
marginLeft: spacing[2], | ||
marginRight: spacing[2], | ||
textAlign: 'left', | ||
}); | ||
|
||
const updatePreviewStyles = css({ | ||
display: 'flex', | ||
flexDirection: 'column', | ||
gap: spacing[3], | ||
}); | ||
|
||
export type BulkUpdateDialogProps = { | ||
isOpen: boolean; | ||
ns: string; | ||
filter: BSONObject; | ||
count?: number; | ||
updateText: string; | ||
preview: UpdatePreview; | ||
syntaxError?: Error & { loc?: { index: number } }; | ||
serverError?: Error; | ||
closeBulkUpdateDialog: () => void; | ||
updateBulkUpdatePreview: (updateText: string) => void; | ||
runBulkUpdate: () => void; | ||
}; | ||
|
||
export default function BulkUpdateDialog({ | ||
isOpen, | ||
ns, | ||
filter, | ||
count, | ||
updateText, | ||
preview, | ||
syntaxError, | ||
serverError, | ||
closeBulkUpdateDialog, | ||
updateBulkUpdatePreview, | ||
runBulkUpdate, | ||
}: BulkUpdateDialogProps) { | ||
const darkMode = useDarkMode(); | ||
|
||
const [text, setText] = useState(updateText); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Doesn't look like we reset this when modal is re-opened, is this expected? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yup. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Maybe worth leaving a comment that it's on purpose, we have a bunch of modal forms where we reset forms on modal open, I'm worried we can loose this behavior by accident |
||
|
||
const previewDocuments = useMemo(() => { | ||
return preview.changes.map( | ||
(change) => new HadronDocument(change.after as Record<string, unknown>) | ||
lerouxb marked this conversation as resolved.
Show resolved
Hide resolved
|
||
); | ||
}, [preview]); | ||
|
||
const onChangeText = (value: string) => { | ||
setText(value); | ||
updateBulkUpdatePreview(value); | ||
}; | ||
|
||
const title = | ||
count === undefined ? 'Update documents' : `Update documents (${count})`; | ||
|
||
const annotations = useMemo<Annotation[]>(() => { | ||
if (syntaxError?.loc?.index) { | ||
return [ | ||
{ | ||
message: syntaxError.message, | ||
severity: 'error', | ||
from: syntaxError.loc.index, | ||
to: syntaxError.loc.index, | ||
}, | ||
]; | ||
} | ||
|
||
return []; | ||
}, [syntaxError]); | ||
|
||
return ( | ||
<FormModal | ||
title={title} | ||
subtitle={ns} | ||
size="large" | ||
open={isOpen} | ||
onSubmit={runBulkUpdate} | ||
onCancel={closeBulkUpdateDialog} | ||
cancelButtonText="Close" | ||
submitButtonText="Update documents" | ||
submitDisabled={!!(syntaxError || serverError)} | ||
> | ||
<div className={columnsStyles}> | ||
<div className={queryStyles}> | ||
<div className={queryFieldStyles}> | ||
<div className={filterLabelContainerStyles}> | ||
<Label htmlFor="bulk-update-filter">Filter</Label> | ||
<InfoSprinkle> | ||
Return to the Documents tab to edit this query | ||
</InfoSprinkle> | ||
</div> | ||
<KeylineCard | ||
className={cx( | ||
codeContainerStyles, | ||
inlineContainerStyles, | ||
darkMode ? codeDarkContainerStyles : codeLightContainerStyles | ||
)} | ||
> | ||
<pre>{(toJSString(filter) ?? '').replace(/\s+/g, ' ')}</pre> | ||
lerouxb marked this conversation as resolved.
Show resolved
Hide resolved
|
||
</KeylineCard> | ||
</div> | ||
|
||
<div className={cx(queryFieldStyles, updateFieldStyles)}> | ||
<Label htmlFor="bulk-update-update">Update</Label> | ||
<Description className={descriptionStyles}> | ||
<Link href="https://www.mongodb.com/docs/manual/reference/method/db.collection.updateMany/#std-label-update-many-update"> | ||
Learn more about Update syntax | ||
</Link> | ||
</Description> | ||
<KeylineCard | ||
className={cx( | ||
codeContainerStyles, | ||
multilineContainerStyles, | ||
darkMode ? codeDarkContainerStyles : codeLightContainerStyles | ||
)} | ||
> | ||
<CodemirrorMultilineEditor | ||
text={text} | ||
onChangeText={onChangeText} | ||
id="bulk-update-update" | ||
data-testid="bulk-update-update" | ||
onBlur={() => ({})} | ||
className={codeEditorStyles} | ||
annotations={annotations} | ||
/> | ||
|
||
<div className={bannerContainerStyles}> | ||
{syntaxError && ( | ||
<Banner | ||
variant={BannerVariant.Warning} | ||
className={bannerStyles} | ||
> | ||
{syntaxError.message} | ||
</Banner> | ||
)} | ||
{serverError && !syntaxError && ( | ||
<Banner | ||
variant={BannerVariant.Danger} | ||
className={bannerStyles} | ||
> | ||
{serverError.message} | ||
</Banner> | ||
)} | ||
</div> | ||
</KeylineCard> | ||
</div> | ||
</div> | ||
<div className={previewStyles}> | ||
<Label htmlFor="bulk-update-preview"> | ||
lerouxb marked this conversation as resolved.
Show resolved
Hide resolved
|
||
Preview{' '} | ||
<Description className={previewDescriptionStyles}> | ||
(sample of {preview.changes.length} document | ||
{preview.changes.length !== 1 && 's'}) | ||
</Description> | ||
</Label> | ||
<div className={updatePreviewStyles}> | ||
{previewDocuments.map((doc: HadronDocument, index: number) => { | ||
return ( | ||
<UpdatePreviewDocument | ||
key={`change=${index}`} | ||
data-testid="bulk-update-preview-document" | ||
doc={doc} | ||
/> | ||
); | ||
})} | ||
</div> | ||
</div> | ||
</div> | ||
</FormModal> | ||
); | ||
} | ||
|
||
function UpdatePreviewDocument({ | ||
doc, | ||
...props | ||
}: { | ||
'data-testid': string; | ||
doc: HadronDocument; | ||
}) { | ||
return ( | ||
<KeylineCard data-testid={props['data-testid']}> | ||
<Document doc={doc} editable={false} /> | ||
</KeylineCard> | ||
); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not sure how this correlates with the styles below: this is exactly the same color we use in the editor? Is it only the dark mode that's different? Why?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
To match the design. Otherwise it is white. We do the exact same thing in the aggregation stage editor:
compass/packages/compass-aggregations/src/components/stage-editor/stage-editor.tsx
Line 43 in dbf1b0d
There's a ticket to refactor that so we don't keep duplicating it along with the banner area at the bottom.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not sure I follow, default multiline background is the same gray color:
compass/packages/compass-editor/src/json-editor.tsx
Line 141 in c3a174e
Sounds good if we're planning to do a follow-up clean up for this 👍