From 1fcb3df02f7384667a74cd0886086a2fd1bb3855 Mon Sep 17 00:00:00 2001 From: Wroud Date: Wed, 1 Sep 2021 13:47:46 +0300 Subject: [PATCH 1/9] refactor: CB-971 dialog styles --- .../SessionExpiredDialog.tsx | 6 +- .../SessionExpireWarningDialog.tsx | 6 +- .../ConnectionAuthenticationDialog.tsx | 5 +- .../DatabaseAuthDialog/DatabaseAuthDialog.tsx | 5 +- .../CommonDialog/CommonDialogWrapper.tsx | 6 +- .../src/CommonDialog/CommonDialog/styles.ts | 59 +++++++-- .../src/CommonDialog/ConfirmationDialog.tsx | 4 +- .../src/CommonDialog/DialogsPortal.tsx | 119 +++++++++--------- .../core-dialogs/src/CommonDialog/styles.ts | 11 +- .../ErrorDetailsDialog/ErrorDetailsDialog.tsx | 1 + .../ErrorDetailsDialog/styles.ts | 3 +- .../src/Dialog/AuthDialog.tsx | 7 +- .../ChangeUserPasswordDialog.tsx | 4 +- .../DriverSelectorDialog.tsx | 6 +- .../src/ConnectionDialog/ConnectionDialog.tsx | 8 +- .../src/Dialog/ProcessorConfigureDialog.tsx | 6 +- .../src/Dialog/ProcessorSelectDialog.tsx | 6 +- .../FilterCustomValueDialog.tsx | 4 +- .../src/ScriptPreview/ScriptPreviewDialog.tsx | 5 +- .../plugin-product/src/ProductInfoDialog.tsx | 4 +- 20 files changed, 144 insertions(+), 131 deletions(-) diff --git a/webapp/packages/core-app/src/shared/SessionExpireDialog/SessionExpiredDialog.tsx b/webapp/packages/core-app/src/shared/SessionExpireDialog/SessionExpiredDialog.tsx index 17e400fc10..840668d049 100644 --- a/webapp/packages/core-app/src/shared/SessionExpireDialog/SessionExpiredDialog.tsx +++ b/webapp/packages/core-app/src/shared/SessionExpireDialog/SessionExpiredDialog.tsx @@ -16,10 +16,6 @@ import { useTranslate } from '@cloudbeaver/core-localization'; import { useStyles } from '@cloudbeaver/core-theming'; const styles = css` - CommonDialogWrapper { - width: 400px; - min-width: auto; - } controls { display: flex; flex: 1; @@ -42,6 +38,7 @@ export const SessionExpiredDialog: DialogComponent = observer(functi return styled(useStyles(styles))( @@ -54,6 +51,7 @@ export const SessionExpiredDialog: DialogComponent = observer(functi )} + fixedSize noOverflow onReject={rejectDialog} > diff --git a/webapp/packages/core-app/src/shared/SessionExpireWarningDialog/SessionExpireWarningDialog.tsx b/webapp/packages/core-app/src/shared/SessionExpireWarningDialog/SessionExpireWarningDialog.tsx index 2cd4b952c5..6d0b65b5d5 100644 --- a/webapp/packages/core-app/src/shared/SessionExpireWarningDialog/SessionExpireWarningDialog.tsx +++ b/webapp/packages/core-app/src/shared/SessionExpireWarningDialog/SessionExpireWarningDialog.tsx @@ -15,10 +15,6 @@ import { useTranslate } from '@cloudbeaver/core-localization'; import { useStyles } from '@cloudbeaver/core-theming'; const styles = css` - CommonDialogWrapper { - width: 400px; - min-width: auto; - } controls { display: flex; flex: 1; @@ -39,6 +35,7 @@ export const SessionExpireWarningDialog: DialogComponent = observer( return styled(useStyles(styles))( @@ -51,6 +48,7 @@ export const SessionExpireWarningDialog: DialogComponent = observer( )} + fixedSize noOverflow onReject={rejectDialog} > diff --git a/webapp/packages/core-connections/src/ConnectionAuthentication/ConnectionAuthenticationDialog.tsx b/webapp/packages/core-connections/src/ConnectionAuthentication/ConnectionAuthenticationDialog.tsx index 194f3acd7d..47c01b4d4a 100644 --- a/webapp/packages/core-connections/src/ConnectionAuthentication/ConnectionAuthenticationDialog.tsx +++ b/webapp/packages/core-connections/src/ConnectionAuthentication/ConnectionAuthenticationDialog.tsx @@ -19,10 +19,6 @@ import { useDBDriver } from '../useDBDriver'; import { ConnectionAuthenticationForm } from './ConnectionAuthenticationForm'; const styles = css` - CommonDialogWrapper { - min-height: 400px; - min-width: 500px; - } SubmittingForm { overflow: auto; margin: auto; @@ -56,6 +52,7 @@ export const ConnectionAuthenticationDialog: DialogComponent = observer return styled(useStyles(styles))( = observer(function Da return styled(useStyles(styles))( = function CommonDialogWrapper({ + size = 'medium', + fixedSize, title, subTitle, icon, @@ -41,7 +45,7 @@ export const CommonDialogWrapper: React.FC = function children, }) { return styled(useStyles(commonDialogThemeStyle, commonDialogBaseStyle))( - +
{icon && } diff --git a/webapp/packages/core-dialogs/src/CommonDialog/CommonDialog/styles.ts b/webapp/packages/core-dialogs/src/CommonDialog/CommonDialog/styles.ts index f303fb8548..450c2dcb49 100644 --- a/webapp/packages/core-dialogs/src/CommonDialog/CommonDialog/styles.ts +++ b/webapp/packages/core-dialogs/src/CommonDialog/CommonDialog/styles.ts @@ -44,12 +44,43 @@ export const commonDialogBaseStyle = composes( margin: 0; border: none; height: auto; - min-width: 748px; max-height: 100%; border-radius: 0.25rem; padding: 0px; + + &[|size=small] { + min-width: 404px; + min-height: 262px; + max-height: max(100vh - 48px, 262px); + + &[|fixedSize] { + width: 404px; + height: 262px; + } + } + &[|size=medium] { + min-width: 576px; + min-height: 374px; + max-height: max(100vh - 48px, 374px); + + &[|fixedSize] { + width: 576px; + height: 374px; + } + } + &[|size=large] { + min-width: 720px; + min-height: 468px; + max-height: max(100vh - 48px, 468px); + + &[|fixedSize] { + width: 720px; + height: 468px; + } + } } header, dialog-body, footer { + flex-shrink: 0; padding: 24px; &[|no-padding] { @@ -58,6 +89,7 @@ export const commonDialogBaseStyle = composes( } dialog-body { padding-top: 0px; + padding-right: 0px; } footer { padding-top: 0px; @@ -89,41 +121,42 @@ export const commonDialogBaseStyle = composes( } } dialog-body { - position: relative; - box-sizing: border-box; - display: flex; - flex-direction: row; flex: 1; + box-sizing: content-box; + display: flex; max-width: 748px; max-height: 100%; overflow: auto; } dialog-body-overflow-box { position: relative; + flex: 1; box-sizing: border-box; display: flex; - flex-direction: row; - flex: 1; + flex-direction: column; + overflow: auto; + padding-right: 24px; } dialog-body-content { - position: relative; flex: 1; + position: relative; display: flex; flex-direction: column; box-sizing: border-box; - overflow: auto; - padding-bottom: 24px; } dialog-body[|no-padding] + footer { padding-top: 24px; } + dialog-body[|no-padding] dialog-body-overflow-box { + padding-right: 0; + } dialog-body[|no-overflow] dialog-body-content { - padding-bottom: 0; + overflow: auto; } dialog-body-overflow { - position: absolute; - flex-shrink: 0; + position: sticky; bottom: 0; + flex-shrink: 0; width: 100%; height: 24px; pointer-events: none; diff --git a/webapp/packages/core-dialogs/src/CommonDialog/ConfirmationDialog.tsx b/webapp/packages/core-dialogs/src/CommonDialog/ConfirmationDialog.tsx index 0d00b8909b..fc65568ad2 100644 --- a/webapp/packages/core-dialogs/src/CommonDialog/ConfirmationDialog.tsx +++ b/webapp/packages/core-dialogs/src/CommonDialog/ConfirmationDialog.tsx @@ -50,7 +50,7 @@ export const ConfirmationDialog: DialogComponent = fu const { icon, title, subTitle, bigIcon, viewBox, message, confirmActionText } = payload; return styled(useStyles(commonDialogThemeStyle, commonDialogBaseStyle, style))( - +
{icon && } @@ -65,7 +65,7 @@ export const ConfirmationDialog: DialogComponent = fu {subTitle && {subTitle}}
- + diff --git a/webapp/packages/core-dialogs/src/CommonDialog/DialogsPortal.tsx b/webapp/packages/core-dialogs/src/CommonDialog/DialogsPortal.tsx index a95276c019..d26592187f 100644 --- a/webapp/packages/core-dialogs/src/CommonDialog/DialogsPortal.tsx +++ b/webapp/packages/core-dialogs/src/CommonDialog/DialogsPortal.tsx @@ -7,7 +7,7 @@ */ import { observer } from 'mobx-react-lite'; -import { useCallback, useEffect, useRef } from 'react'; +import { useEffect } from 'react'; import { Dialog, DialogBackdrop, @@ -15,6 +15,7 @@ import { } from 'reakit/Dialog'; import styled from 'reshadow'; +import { useObjectRef } from '@cloudbeaver/core-blocks'; import { useService } from '@cloudbeaver/core-di'; import { useStyles } from '@cloudbeaver/core-theming'; @@ -22,87 +23,85 @@ import { CommonDialogService, DialogInternal } from './CommonDialogService'; import { dialogStyles } from './styles'; export const DialogsPortal = observer(function DialogsPortal() { + const styles = useStyles(dialogStyles); const commonDialogService = useService(CommonDialogService); - const handleReject = useCallback( - (dialog: DialogInternal) => commonDialogService.rejectDialog(dialog.promise), - [commonDialogService] - ); - const handleResolve = useCallback( - (dialog: DialogInternal, result: any) => commonDialogService.resolveDialog(dialog.promise, result), - [commonDialogService] - ); - return ( - <> - {commonDialogService.dialogs.map((dialog, i, arr) => ( - - ))} - + let activeDialog: DialogInternal | undefined; + + if (commonDialogService.dialogs.length > 0) { + activeDialog = commonDialogService.dialogs[commonDialogService.dialogs.length - 1]; + } + + const state = useObjectRef(() => ({ + reject() { + if (this.dialog) { + commonDialogService.rejectDialog(this.dialog.promise); + } + }, + resolve(result: any) { + if (this.dialog) { + commonDialogService.resolveDialog(this.dialog.promise, result); + } + }, + backdropClick(e: React.MouseEvent) { + if (!this.dialog?.options?.persistent && e.currentTarget.isEqualNode(e.target as HTMLElement)) { + this.reject(); + } + }, + }), { + dialog: activeDialog, + }, ['reject', 'resolve', 'backdropClick']); + + return styled(styles)( + + + {commonDialogService.dialogs.map((dialog, i, arr) => ( + + ))} + + ); }); interface NestedDialogType { dialog: DialogInternal; - resolveDialog: (dialog: DialogInternal, result: any) => void; - rejectDialog: (dialog: DialogInternal) => void; + resolveDialog: (result: any) => void; + rejectDialog: () => void; visible: boolean; } -function NestedDialog({ +const NestedDialog: React.FC = function NestedDialog({ dialog, resolveDialog, rejectDialog, visible, -}: NestedDialogType) { - const lastVisibility = useRef(visible); - const dialogState = useDialogState({ visible }); +}) { + const dialogState = useDialogState({ visible: true }); const styles = useStyles(dialogStyles); useEffect(() => { - if (!dialogState.visible - && dialogState.visible !== lastVisibility.current - && !dialog.options?.persistent - ) { - rejectDialog(dialog); - } else { - lastVisibility.current = visible; - dialogState.setVisible(visible); + if (!dialogState.visible && !dialog.options?.persistent) { + rejectDialog(); } }); - const handleReject = useCallback(() => rejectDialog(dialog), [dialog, rejectDialog]); - const handleResolve = useCallback( - (result: any) => resolveDialog(dialog, result), - [dialog, resolveDialog] - ); - const DialogComponent = dialog.component; - const backdropClickHandler = (e: React.MouseEvent) => { - if (!dialog.options?.persistent && e.currentTarget.isEqualNode(e.target as HTMLElement)) { - rejectDialog(dialog); - } - }; - // TODO: place Dialog inside CommonDialogWrapper, so we can pass aria-label return styled(styles)( - <> - - - - - - + + + ); -} +}; diff --git a/webapp/packages/core-dialogs/src/CommonDialog/styles.ts b/webapp/packages/core-dialogs/src/CommonDialog/styles.ts index 489aee7be2..a3139d499b 100644 --- a/webapp/packages/core-dialogs/src/CommonDialog/styles.ts +++ b/webapp/packages/core-dialogs/src/CommonDialog/styles.ts @@ -21,15 +21,20 @@ export const dialogStyles = css` left: 0px; z-index: 999; display: flex; + overflow: auto; + } + + inner-box { + display: flex; + margin: auto; + padding: 24px; + flex-direction: column; align-items: center; - justify-content: center; } Dialog { box-sizing: border-box; display: flex; - max-height: calc(100vh); - z-index: 999; outline: none; } `; diff --git a/webapp/packages/core-notifications/src/NotificationsItem/ErrorDetailsDialog/ErrorDetailsDialog.tsx b/webapp/packages/core-notifications/src/NotificationsItem/ErrorDetailsDialog/ErrorDetailsDialog.tsx index 2d7d410b55..b6ea253e0f 100644 --- a/webapp/packages/core-notifications/src/NotificationsItem/ErrorDetailsDialog/ErrorDetailsDialog.tsx +++ b/webapp/packages/core-notifications/src/NotificationsItem/ErrorDetailsDialog/ErrorDetailsDialog.tsx @@ -54,6 +54,7 @@ export const ErrorDetailsDialog: DialogComponent = observe return styled(useStyles(styles))( = observer(function return styled(useStyles(styles, UNDERLINE_TAB_STYLES))( = observer(function )} )} + noBodyPadding onReject={options?.persistent ? undefined : rejectDialog} > {showTabs && ( diff --git a/webapp/packages/plugin-authentication/src/UserMenu/ChangeUserPasswordDialog/ChangeUserPasswordDialog.tsx b/webapp/packages/plugin-authentication/src/UserMenu/ChangeUserPasswordDialog/ChangeUserPasswordDialog.tsx index 0a67f499f3..44eedc90de 100644 --- a/webapp/packages/plugin-authentication/src/UserMenu/ChangeUserPasswordDialog/ChangeUserPasswordDialog.tsx +++ b/webapp/packages/plugin-authentication/src/UserMenu/ChangeUserPasswordDialog/ChangeUserPasswordDialog.tsx @@ -29,9 +29,6 @@ const styles = composes( } `, css` - CommonDialogWrapper { - min-width: 500px; - } ErrorMessage { flex: 1; } @@ -93,6 +90,7 @@ export const ChangeUserPasswordDialog: DialogComponent = observer( return styled(style)( (function DriverSelectorDial }) { return styled(styles)( = observer(function C return styled(useStyles(styles))( = observer(function C onConnect={controller.onConnect} /> )} - noBodyPadding + noBodyPadding={controller.step === ConnectionStep.ConnectionTemplateSelect} + fixedSize noOverflow onReject={rejectDialog} > diff --git a/webapp/packages/plugin-data-export/src/Dialog/ProcessorConfigureDialog.tsx b/webapp/packages/plugin-data-export/src/Dialog/ProcessorConfigureDialog.tsx index 6229425d8a..d311f9a8c9 100644 --- a/webapp/packages/plugin-data-export/src/Dialog/ProcessorConfigureDialog.tsx +++ b/webapp/packages/plugin-data-export/src/Dialog/ProcessorConfigureDialog.tsx @@ -27,10 +27,6 @@ const styles = composes( } `, css` - CommonDialogWrapper { - max-height: 500px; - min-height: 500px; - } PropertiesTable { flex: 1; overflow: hidden; @@ -74,6 +70,7 @@ export const ProcessorConfigureDialog = observer(function ProcessorConfig return styled(useStyles(styles))( (function ProcessorConfig onCancel={onClose} /> )} + fixedSize noOverflow noBodyPadding onReject={onClose} diff --git a/webapp/packages/plugin-data-export/src/Dialog/ProcessorSelectDialog.tsx b/webapp/packages/plugin-data-export/src/Dialog/ProcessorSelectDialog.tsx index f2a220cba4..075e2d7a3e 100644 --- a/webapp/packages/plugin-data-export/src/Dialog/ProcessorSelectDialog.tsx +++ b/webapp/packages/plugin-data-export/src/Dialog/ProcessorSelectDialog.tsx @@ -19,10 +19,6 @@ import type { IExportContext } from '../IExportContext'; import { ExportProcessorList } from './ExportProcessorList/ExportProcessorList'; const styles = css` - CommonDialogWrapper { - max-height: 500px; - min-height: 500px; - } ExportProcessorList { flex: 1; } @@ -61,7 +57,9 @@ export const ProcessorSelectDialog = observer(function ProcessorSelectDia return styled(styles)( return styled(styles)( diff --git a/webapp/packages/plugin-data-viewer/src/ScriptPreview/ScriptPreviewDialog.tsx b/webapp/packages/plugin-data-viewer/src/ScriptPreview/ScriptPreviewDialog.tsx index f5bdf2dbe0..9d4eccdd2b 100644 --- a/webapp/packages/plugin-data-viewer/src/ScriptPreview/ScriptPreviewDialog.tsx +++ b/webapp/packages/plugin-data-viewer/src/ScriptPreview/ScriptPreviewDialog.tsx @@ -22,10 +22,6 @@ import type { IDatabaseDataModel } from '../DatabaseDataModel/IDatabaseDataModel import type { IDatabaseDataResult } from '../DatabaseDataModel/IDatabaseDataResult'; const styles = css` - CommonDialogWrapper { - min-height: 400px; - min-width: 650px; - } wrapper { display: flex; align-items: center; @@ -94,6 +90,7 @@ export const ScriptPreviewDialog = observer>(funct return styled(styles)( >( return styled(styles)( From c0fa7ff1f6d631c1314016fb7cd042ffdebdd529 Mon Sep 17 00:00:00 2001 From: Wroud Date: Wed, 1 Sep 2021 13:48:19 +0300 Subject: [PATCH 2/9] chore(plugin-data-viewer): data action icons --- .../DataGridContextMenuCellEditingService.ts | 2 +- .../plugin-data-viewer/public/icons/data_add_sm.svg | 6 +++--- .../plugin-data-viewer/public/icons/data_delete_sm.svg | 6 +++--- .../plugin-data-viewer/public/icons/data_revert_sm.svg | 7 +++++++ 4 files changed, 14 insertions(+), 7 deletions(-) create mode 100644 webapp/packages/plugin-data-viewer/public/icons/data_revert_sm.svg diff --git a/webapp/packages/plugin-data-spreadsheet-new/src/DataGrid/DataGridContextMenu/DataGridContextMenuCellEditingService.ts b/webapp/packages/plugin-data-spreadsheet-new/src/DataGrid/DataGridContextMenu/DataGridContextMenuCellEditingService.ts index 74cb63de38..14f994b54c 100644 --- a/webapp/packages/plugin-data-spreadsheet-new/src/DataGrid/DataGridContextMenu/DataGridContextMenuCellEditingService.ts +++ b/webapp/packages/plugin-data-spreadsheet-new/src/DataGrid/DataGridContextMenu/DataGridContextMenuCellEditingService.ts @@ -141,7 +141,7 @@ export class DataGridContextMenuCellEditingService { { id: 'row_revert', order: 7, - icon: '/icons/data_revert.svg', + icon: '/icons/data_revert_sm.svg', title: 'data_grid_table_editing_row_revert', isPresent(context) { return context.contextType === DataGridContextMenuService.cellContext; diff --git a/webapp/packages/plugin-data-viewer/public/icons/data_add_sm.svg b/webapp/packages/plugin-data-viewer/public/icons/data_add_sm.svg index 5161824273..1ffe959f08 100644 --- a/webapp/packages/plugin-data-viewer/public/icons/data_add_sm.svg +++ b/webapp/packages/plugin-data-viewer/public/icons/data_add_sm.svg @@ -1,5 +1,5 @@ - - - + + + diff --git a/webapp/packages/plugin-data-viewer/public/icons/data_delete_sm.svg b/webapp/packages/plugin-data-viewer/public/icons/data_delete_sm.svg index 1778f0a5ae..bc41b4f7df 100644 --- a/webapp/packages/plugin-data-viewer/public/icons/data_delete_sm.svg +++ b/webapp/packages/plugin-data-viewer/public/icons/data_delete_sm.svg @@ -1,5 +1,5 @@ - - - + + + diff --git a/webapp/packages/plugin-data-viewer/public/icons/data_revert_sm.svg b/webapp/packages/plugin-data-viewer/public/icons/data_revert_sm.svg new file mode 100644 index 0000000000..c0837739d7 --- /dev/null +++ b/webapp/packages/plugin-data-viewer/public/icons/data_revert_sm.svg @@ -0,0 +1,7 @@ + + + + + + + From 0786c8be092e26c7cd940081d3fc964d6f901011 Mon Sep 17 00:00:00 2001 From: Wroud Date: Wed, 1 Sep 2021 14:32:18 +0300 Subject: [PATCH 3/9] build(core-cli): CB-1177 extend browsers list --- webapp/packages/core-cli/configs/babel.config.js | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/webapp/packages/core-cli/configs/babel.config.js b/webapp/packages/core-cli/configs/babel.config.js index d39731082d..41de01381b 100644 --- a/webapp/packages/core-cli/configs/babel.config.js +++ b/webapp/packages/core-cli/configs/babel.config.js @@ -30,10 +30,12 @@ module.exports = { modules: false, targets: { browsers: [ - "last 1 chrome version", - "last 1 firefox version", - "last 1 edge version", - "last 1 safari version" + "defaults", + "not IE 11", + // "last 1 chrome version", + // "last 1 firefox version", + // "last 1 edge version", + // "last 1 safari version" ] }, exclude: ["transform-async-to-generator", "transform-regenerator"], From dd24a05bb27276710a90e60d52910acb3afcfb1d Mon Sep 17 00:00:00 2001 From: Wroud Date: Wed, 1 Sep 2021 14:51:22 +0300 Subject: [PATCH 4/9] fix(core-app): CB-1211 empty groups in nav-tree --- .../navigationTreeConnectionGroupFilter.ts | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/webapp/packages/core-app/src/NavigationTree/navigationTreeConnectionGroupFilter.ts b/webapp/packages/core-app/src/NavigationTree/navigationTreeConnectionGroupFilter.ts index 1b2d7758cb..3f17190b74 100644 --- a/webapp/packages/core-app/src/NavigationTree/navigationTreeConnectionGroupFilter.ts +++ b/webapp/packages/core-app/src/NavigationTree/navigationTreeConnectionGroupFilter.ts @@ -40,6 +40,13 @@ function sortManageable(connectionInfoResource: ConnectionInfoResource): (nodeA: const connectionA = connectionInfoResource.get(NodeManagerUtils.connectionNodeIdToConnectionId(nodeA.id)); const connectionB = connectionInfoResource.get(NodeManagerUtils.connectionNodeIdToConnectionId(nodeB.id)); + // if (!connectionA || !connectionB) { + // if (connectionA === connectionB) { + // return 0; + // } + // return connectionA ? 1 : -1; + // } + const nodeAManageable = connectionA?.features.includes(EConnectionFeature.manageable); const nodeBManageable = connectionB?.features.includes(EConnectionFeature.manageable); @@ -71,7 +78,9 @@ export function navigationTreeConnectionGroupFilter( for (const node of nodes) { const connection = connectionInfoResource.get(NodeManagerUtils.connectionNodeIdToConnectionId(node.id)); - const manageable = connection?.features.includes(EConnectionFeature.manageable); + const manageable = !!connection?.features.includes(EConnectionFeature.manageable); + + console.log(!!connection, manageable); let nextGroup = NAVIGATION_TREE_CONNECTION_GROUPS.unsorted; @@ -101,6 +110,8 @@ export function navigationTreeConnectionGroupFilter( ); } + console.log(groupedChildren); + return groupedChildren; }; } From 6f076f1936ab6059e5b8f24296c2ea412ffdeea4 Mon Sep 17 00:00:00 2001 From: Wroud Date: Wed, 1 Sep 2021 14:51:43 +0300 Subject: [PATCH 5/9] fix(core-app): CB-1211 empty groups in nav-tree --- .../navigationTreeConnectionGroupFilter.ts | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/webapp/packages/core-app/src/NavigationTree/navigationTreeConnectionGroupFilter.ts b/webapp/packages/core-app/src/NavigationTree/navigationTreeConnectionGroupFilter.ts index 1b2d7758cb..3f17190b74 100644 --- a/webapp/packages/core-app/src/NavigationTree/navigationTreeConnectionGroupFilter.ts +++ b/webapp/packages/core-app/src/NavigationTree/navigationTreeConnectionGroupFilter.ts @@ -40,6 +40,13 @@ function sortManageable(connectionInfoResource: ConnectionInfoResource): (nodeA: const connectionA = connectionInfoResource.get(NodeManagerUtils.connectionNodeIdToConnectionId(nodeA.id)); const connectionB = connectionInfoResource.get(NodeManagerUtils.connectionNodeIdToConnectionId(nodeB.id)); + // if (!connectionA || !connectionB) { + // if (connectionA === connectionB) { + // return 0; + // } + // return connectionA ? 1 : -1; + // } + const nodeAManageable = connectionA?.features.includes(EConnectionFeature.manageable); const nodeBManageable = connectionB?.features.includes(EConnectionFeature.manageable); @@ -71,7 +78,9 @@ export function navigationTreeConnectionGroupFilter( for (const node of nodes) { const connection = connectionInfoResource.get(NodeManagerUtils.connectionNodeIdToConnectionId(node.id)); - const manageable = connection?.features.includes(EConnectionFeature.manageable); + const manageable = !!connection?.features.includes(EConnectionFeature.manageable); + + console.log(!!connection, manageable); let nextGroup = NAVIGATION_TREE_CONNECTION_GROUPS.unsorted; @@ -101,6 +110,8 @@ export function navigationTreeConnectionGroupFilter( ); } + console.log(groupedChildren); + return groupedChildren; }; } From 719c523d122ad8d1137a2d6b1d57f24429b028f6 Mon Sep 17 00:00:00 2001 From: Wroud Date: Wed, 1 Sep 2021 14:53:46 +0300 Subject: [PATCH 6/9] fix(core-app): CB-1211 empty groups in nav-tree --- .../navigationTreeConnectionGroupFilter.ts | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/webapp/packages/core-app/src/NavigationTree/navigationTreeConnectionGroupFilter.ts b/webapp/packages/core-app/src/NavigationTree/navigationTreeConnectionGroupFilter.ts index 3f17190b74..f5bc6fcad7 100644 --- a/webapp/packages/core-app/src/NavigationTree/navigationTreeConnectionGroupFilter.ts +++ b/webapp/packages/core-app/src/NavigationTree/navigationTreeConnectionGroupFilter.ts @@ -40,12 +40,12 @@ function sortManageable(connectionInfoResource: ConnectionInfoResource): (nodeA: const connectionA = connectionInfoResource.get(NodeManagerUtils.connectionNodeIdToConnectionId(nodeA.id)); const connectionB = connectionInfoResource.get(NodeManagerUtils.connectionNodeIdToConnectionId(nodeB.id)); - // if (!connectionA || !connectionB) { - // if (connectionA === connectionB) { - // return 0; - // } - // return connectionA ? 1 : -1; - // } + if (!connectionA || !connectionB) { + if (connectionA === connectionB) { + return 0; + } + return connectionA ? 1 : -1; + } const nodeAManageable = connectionA?.features.includes(EConnectionFeature.manageable); const nodeBManageable = connectionB?.features.includes(EConnectionFeature.manageable); @@ -80,8 +80,6 @@ export function navigationTreeConnectionGroupFilter( const connection = connectionInfoResource.get(NodeManagerUtils.connectionNodeIdToConnectionId(node.id)); const manageable = !!connection?.features.includes(EConnectionFeature.manageable); - console.log(!!connection, manageable); - let nextGroup = NAVIGATION_TREE_CONNECTION_GROUPS.unsorted; if (manageable) { @@ -110,8 +108,6 @@ export function navigationTreeConnectionGroupFilter( ); } - console.log(groupedChildren); - return groupedChildren; }; } From f791acb6bfbe71733c167be6b3e8093a3f56e380 Mon Sep 17 00:00:00 2001 From: Naumov Alexey Date: Wed, 1 Sep 2021 17:46:51 +0300 Subject: [PATCH 7/9] fix(plugin-authentication): CB-1063 add support categories --- .../Options/AuthConfigurationOptions.tsx | 44 ++++++++++++++----- 1 file changed, 34 insertions(+), 10 deletions(-) diff --git a/webapp/packages/plugin-authentication/src/Administration/IdentityProviders/Options/AuthConfigurationOptions.tsx b/webapp/packages/plugin-authentication/src/Administration/IdentityProviders/Options/AuthConfigurationOptions.tsx index 147a77e643..8f298285fa 100644 --- a/webapp/packages/plugin-authentication/src/Administration/IdentityProviders/Options/AuthConfigurationOptions.tsx +++ b/webapp/packages/plugin-authentication/src/Administration/IdentityProviders/Options/AuthConfigurationOptions.tsx @@ -13,7 +13,8 @@ import styled, { css } from 'reshadow'; import { AuthConfigurationParametersResource, AuthProvidersResource } from '@cloudbeaver/core-authentication'; import { BASE_CONTAINERS_STYLES, ColoredContainer, ComboboxNew, FieldCheckboxNew, Group, GroupTitle, - InputFieldNew, ObjectPropertyInfoFormNew, SubmittingForm, TabContainerPanelComponent, TextareaNew, useMapResource + InputFieldNew, ObjectPropertyInfoFormNew, SubmittingForm, TabContainerPanelComponent, + TextareaNew, useMapResource, useObjectPropertyCategories } from '@cloudbeaver/core-blocks'; import { useTranslate } from '@cloudbeaver/core-localization'; import type { AuthProviderConfigurationParametersFragment } from '@cloudbeaver/core-sdk'; @@ -28,6 +29,8 @@ const styles = css` } `; +const emptyArray: AuthProviderConfigurationParametersFragment[] = []; + export const AuthConfigurationOptions: TabContainerPanelComponent = observer(function AuthConfigurationOptions({ state, }) { @@ -39,6 +42,10 @@ export const AuthConfigurationOptions: TabContainerPanelComponent provider.configurable); const edit = state.mode === 'edit'; @@ -111,15 +118,32 @@ export const AuthConfigurationOptions: TabContainerPanelComponent {parameters.isLoaded() && parameters.data && ( - - {translate('administration_identity_providers_provider_configuration_parameters')} - - + <> + {isUncategorizedExists && ( + + {translate('administration_identity_providers_provider_configuration_parameters')} + + + )} + {categories.map(category => ( + + {category} + + + ))} + )} From 6676521f423d291f0554534cda95c28cd1dbde94 Mon Sep 17 00:00:00 2001 From: Wroud Date: Thu, 2 Sep 2021 10:27:59 +0300 Subject: [PATCH 8/9] refactor: CB-1094 contexts --- .../NavigationNode/NavigationNodeControl.tsx | 8 +-- .../packages/core-blocks/src/DisplayError.tsx | 4 +- .../src/Tree/TreeNode/TreeNodeContext.ts | 2 +- .../src/Tree/TreeNode/TreeNodeControl.tsx | 6 +- .../src/Tree/TreeNode/TreeNodeExpand.tsx | 8 +-- .../packages/core-blocks/src/getComputed.ts | 13 ++++ webapp/packages/core-blocks/src/index.ts | 1 + .../packages/core-blocks/src/useObjectRef.ts | 5 +- .../src/DataGrid/CellRenderer/CellContext.ts | 2 +- .../src/DataGrid/DataGridContext.ts | 2 +- .../DataGridSelectionContext.ts | 2 +- .../src/DataGrid/Formatters/CellFormatter.tsx | 14 ++-- .../Formatters/CellFormatterFactory.tsx | 4 +- .../CellFormatters/TextFormatter.tsx | 14 ++-- .../TableColumnHeader/TableColumnHeader.tsx | 8 +-- .../src/DataGrid/TableDataContext.ts | 2 +- .../src/Editing/EditingContext.tsx | 2 +- .../DatabaseDataModel/DatabaseDataActions.ts | 72 ++++++++++++++----- .../DatabaseDataModel/DatabaseDataSource.ts | 3 +- 19 files changed, 103 insertions(+), 69 deletions(-) create mode 100644 webapp/packages/core-blocks/src/getComputed.ts diff --git a/webapp/packages/core-app/src/NavigationTree/NavigationTreeNode/NavigationNode/NavigationNodeControl.tsx b/webapp/packages/core-app/src/NavigationTree/NavigationTreeNode/NavigationNode/NavigationNodeControl.tsx index c6428a4b79..fce9735aac 100644 --- a/webapp/packages/core-app/src/NavigationTree/NavigationTreeNode/NavigationNode/NavigationNodeControl.tsx +++ b/webapp/packages/core-app/src/NavigationTree/NavigationTreeNode/NavigationNode/NavigationNodeControl.tsx @@ -10,7 +10,7 @@ import { observer } from 'mobx-react-lite'; import { useCallback, useContext } from 'react'; import styled, { css, use } from 'reshadow'; -import { TreeNodeContext, TreeNodeControl, TreeNodeExpand, TreeNodeIcon, TreeNodeName, TREE_NODE_STYLES } from '@cloudbeaver/core-blocks'; +import { getComputed, TreeNodeContext, TreeNodeControl, TreeNodeExpand, TreeNodeIcon, TreeNodeName, TREE_NODE_STYLES } from '@cloudbeaver/core-blocks'; import { useService } from '@cloudbeaver/core-di'; import { composes, useStyles } from '@cloudbeaver/core-theming'; @@ -75,12 +75,12 @@ export const NavigationNodeControl = observer(function NavigationNodeCont }) { const context = useContext(TreeNodeContext); const navNodeInfoResource = useService(NavNodeInfoResource); - const outdated = navNodeInfoResource.isOutdated(node.id) && !context?.loading; + const outdated = getComputed(() => navNodeInfoResource.isOutdated(node.id) && !context.loading); const connected = node.objectFeatures.includes(EObjectFeature.dataSourceConnected); const onClickHandler = useCallback((event: React.MouseEvent) => { - context?.select(event.ctrlKey || event.metaKey); + context.select(event.ctrlKey || event.metaKey); }, [context]); return styled(useStyles(TREE_NODE_STYLES, styles))( @@ -91,7 +91,7 @@ export const NavigationNodeControl = observer(function NavigationNodeCont {node.name} - + ); diff --git a/webapp/packages/core-blocks/src/DisplayError.tsx b/webapp/packages/core-blocks/src/DisplayError.tsx index 0c3760692d..c398cb16ad 100644 --- a/webapp/packages/core-blocks/src/DisplayError.tsx +++ b/webapp/packages/core-blocks/src/DisplayError.tsx @@ -9,7 +9,8 @@ import type React from 'react'; import styled, { css, use } from 'reshadow'; -import { ENotificationType } from '../../core-events/src'; +import { ENotificationType } from '@cloudbeaver/core-events'; + import { NotificationMark } from './Snackbars/NotificationMark'; const style = css` @@ -17,7 +18,6 @@ const style = css` width: 100%; height: 100%; display: flex; - justify-content: center; overflow: auto; &[|root] { diff --git a/webapp/packages/core-blocks/src/Tree/TreeNode/TreeNodeContext.ts b/webapp/packages/core-blocks/src/Tree/TreeNode/TreeNodeContext.ts index 8ea6ad5daa..2316f729cc 100644 --- a/webapp/packages/core-blocks/src/Tree/TreeNode/TreeNodeContext.ts +++ b/webapp/packages/core-blocks/src/Tree/TreeNode/TreeNodeContext.ts @@ -21,4 +21,4 @@ export interface ITreeNodeContext { open: () => Promise; } -export const TreeNodeContext = createContext(null); +export const TreeNodeContext = createContext(undefined as any); diff --git a/webapp/packages/core-blocks/src/Tree/TreeNode/TreeNodeControl.tsx b/webapp/packages/core-blocks/src/Tree/TreeNode/TreeNodeControl.tsx index 25de2ac4ab..57792e1f86 100644 --- a/webapp/packages/core-blocks/src/Tree/TreeNode/TreeNodeControl.tsx +++ b/webapp/packages/core-blocks/src/Tree/TreeNode/TreeNodeControl.tsx @@ -32,10 +32,6 @@ export const TreeNodeControl = observer(function TreeNodeControl({ }) { const context = useContext(TreeNodeContext); - if (!context) { - throw new Error('Context not provided'); - } - const handleEnter = (event: React.KeyboardEvent) => { if (EventContext.has(event, EventTreeNodeExpandFlag, EventTreeNodeSelectFlag)) { return; @@ -44,7 +40,7 @@ export const TreeNodeControl = observer(function TreeNodeControl({ EventContext.set(event, EventTreeNodeSelectFlag); switch ((event as unknown as KeyboardEvent).code) { case KEY.ENTER: - context?.select(event.ctrlKey || event.metaKey); + context.select(event.ctrlKey || event.metaKey); break; } return true; diff --git a/webapp/packages/core-blocks/src/Tree/TreeNode/TreeNodeExpand.tsx b/webapp/packages/core-blocks/src/Tree/TreeNode/TreeNodeExpand.tsx index d1ac27ad42..84e150a464 100644 --- a/webapp/packages/core-blocks/src/Tree/TreeNode/TreeNodeExpand.tsx +++ b/webapp/packages/core-blocks/src/Tree/TreeNode/TreeNodeExpand.tsx @@ -11,7 +11,7 @@ import { useContext } from 'react'; import styled from 'reshadow'; import { css } from 'reshadow'; -import { Icon, Loader } from '@cloudbeaver/core-blocks'; +import { getComputed, Icon, Loader } from '@cloudbeaver/core-blocks'; import { EventContext } from '@cloudbeaver/core-events'; import { useStateDelay } from '../../useStateDelay'; @@ -39,10 +39,6 @@ export const TreeNodeExpand = observer(function TreeNodeExpand({ }) { const context = useContext(TreeNodeContext); - if (!context) { - throw new Error('Context not provided'); - } - const handleExpand = (event: React.MouseEvent) => { EventContext.set(event, EventTreeNodeExpandFlag); @@ -55,7 +51,7 @@ export const TreeNodeExpand = observer(function TreeNodeExpand({ EventContext.set(event, EventTreeNodeExpandFlag); }; - const loading = useStateDelay(context.loading || context.processing, 300); + const loading = useStateDelay(getComputed(() => context.loading || context.processing), 300); return styled(styles)( diff --git a/webapp/packages/core-blocks/src/getComputed.ts b/webapp/packages/core-blocks/src/getComputed.ts new file mode 100644 index 0000000000..e10bfc6998 --- /dev/null +++ b/webapp/packages/core-blocks/src/getComputed.ts @@ -0,0 +1,13 @@ +/* + * CloudBeaver - Cloud Database Manager + * Copyright (C) 2020-2021 DBeaver Corp and others + * + * Licensed under the Apache License, Version 2.0. + * you may not use this file except in compliance with the License. + */ + +import { computed } from 'mobx'; + +export function getComputed(action: () => T): T { + return computed(action).get(); +} diff --git a/webapp/packages/core-blocks/src/index.ts b/webapp/packages/core-blocks/src/index.ts index 0b116b11c8..876be9fdd0 100644 --- a/webapp/packages/core-blocks/src/index.ts +++ b/webapp/packages/core-blocks/src/index.ts @@ -133,6 +133,7 @@ export * from './Cell'; export * from './ErrorMessage'; export * from './StatusMessage'; export * from './ExceptionMessage'; +export * from './getComputed'; export * from './IconButton'; export * from './IconOrImage'; export * from './StaticImage'; diff --git a/webapp/packages/core-blocks/src/useObjectRef.ts b/webapp/packages/core-blocks/src/useObjectRef.ts index a9f64bea8c..4ae57dbed9 100644 --- a/webapp/packages/core-blocks/src/useObjectRef.ts +++ b/webapp/packages/core-blocks/src/useObjectRef.ts @@ -13,6 +13,9 @@ export function useObjectRef>( update: false, bind?: Array ): T; +export function useObjectRef>( + init: () => T & ThisType +): T; export function useObjectRef, U extends Record>( init: () => (T & ThisType), update: U & ThisType, @@ -37,7 +40,7 @@ export function useObjectRef, U extends Record(null); +export const CellContext = createContext(undefined as any); diff --git a/webapp/packages/plugin-data-spreadsheet-new/src/DataGrid/DataGridContext.ts b/webapp/packages/plugin-data-spreadsheet-new/src/DataGrid/DataGridContext.ts index 73e81c7f55..fb9de9b3dd 100644 --- a/webapp/packages/plugin-data-spreadsheet-new/src/DataGrid/DataGridContext.ts +++ b/webapp/packages/plugin-data-spreadsheet-new/src/DataGrid/DataGridContext.ts @@ -27,4 +27,4 @@ export interface IDataGridContext { getDataGridApi: () => DataGridHandle | null; } -export const DataGridContext = createContext(null); +export const DataGridContext = createContext(undefined as any); diff --git a/webapp/packages/plugin-data-spreadsheet-new/src/DataGrid/DataGridSelection/DataGridSelectionContext.ts b/webapp/packages/plugin-data-spreadsheet-new/src/DataGrid/DataGridSelection/DataGridSelectionContext.ts index d05d6573de..a33f6ead1b 100644 --- a/webapp/packages/plugin-data-spreadsheet-new/src/DataGrid/DataGridSelection/DataGridSelectionContext.ts +++ b/webapp/packages/plugin-data-spreadsheet-new/src/DataGrid/DataGridSelection/DataGridSelectionContext.ts @@ -26,4 +26,4 @@ export interface IDataGridSelectionContext { ) => void; } -export const DataGridSelectionContext = createContext(null); +export const DataGridSelectionContext = createContext(undefined as any); diff --git a/webapp/packages/plugin-data-spreadsheet-new/src/DataGrid/Formatters/CellFormatter.tsx b/webapp/packages/plugin-data-spreadsheet-new/src/DataGrid/Formatters/CellFormatter.tsx index 0a78c74c11..4d0ddba972 100644 --- a/webapp/packages/plugin-data-spreadsheet-new/src/DataGrid/Formatters/CellFormatter.tsx +++ b/webapp/packages/plugin-data-spreadsheet-new/src/DataGrid/Formatters/CellFormatter.tsx @@ -11,7 +11,7 @@ import { useContext, useState } from 'react'; import type { FormatterProps } from 'react-data-grid'; import styled, { css } from 'reshadow'; -import { useObjectRef } from '@cloudbeaver/core-blocks'; +import { getComputed, useObjectRef } from '@cloudbeaver/core-blocks'; import type { IDataPresentationActions, IResultSetElementKey, IResultSetRowKey } from '@cloudbeaver/plugin-data-viewer'; import { EditingContext } from '../../Editing/EditingContext'; @@ -44,19 +44,15 @@ export const CellFormatter = observer(function CellFormatter({ className, const cellContext = useContext(CellContext); const editingContext = useContext(EditingContext); const [menuVisible, setMenuVisible] = useState(false); - const isEditing = cellContext?.isEditing ?? false; - const showCellMenu = !isEditing && ( + const isEditing = cellContext.isEditing; + const showCellMenu = getComputed(() => !isEditing && ( rest.isCellSelected || cellContext?.mouse.state.mouseEnter || menuVisible - ); + )); const spreadsheetActions = useObjectRef>({ edit(position) { - if (!tableDataContext || !editingContext) { - return; - } - const idx = tableDataContext.getColumnIndexFromColumnKey(position.column); const rowIdx = tableDataContext.getRowIndexFromKey(position.row); @@ -71,7 +67,7 @@ export const CellFormatter = observer(function CellFormatter({ className, - {showCellMenu && context && cellContext?.cell && ( + {showCellMenu && cellContext.cell && ( { export const CellFormatterFactory = observer(function CellFormatterFactory(props) { const formatterRef = useRef> | null>(null); - const context = useContext(DataGridContext); const tableDataContext = useContext(TableDataContext); const cellContext = useContext(CellContext); if (!props.isEditing || formatterRef.current === null) { formatterRef.current = TextFormatter; - if (tableDataContext && context && cellContext?.cell) { + if (cellContext.cell) { const resultColumn = tableDataContext.getColumnInfo(cellContext.cell.column); const value = tableDataContext.getCellValue(cellContext.cell); diff --git a/webapp/packages/plugin-data-spreadsheet-new/src/DataGrid/Formatters/CellFormatters/TextFormatter.tsx b/webapp/packages/plugin-data-spreadsheet-new/src/DataGrid/Formatters/CellFormatters/TextFormatter.tsx index 0999742dde..4fb6f8e837 100644 --- a/webapp/packages/plugin-data-spreadsheet-new/src/DataGrid/Formatters/CellFormatters/TextFormatter.tsx +++ b/webapp/packages/plugin-data-spreadsheet-new/src/DataGrid/Formatters/CellFormatters/TextFormatter.tsx @@ -6,36 +6,30 @@ * you may not use this file except in compliance with the License. */ -import { computed } from 'mobx'; import { observer } from 'mobx-react-lite'; -import { useCallback, useContext, useEffect, useMemo, useRef } from 'react'; +import { useCallback, useContext, useEffect, useRef } from 'react'; import type { FormatterProps } from 'react-data-grid'; -import { IconOrImage } from '@cloudbeaver/core-blocks'; +import { getComputed, IconOrImage } from '@cloudbeaver/core-blocks'; import { isValidUrl } from '@cloudbeaver/core-utils'; import { EditingContext } from '../../../Editing/EditingContext'; import { CellEditor, IEditorRef } from '../../CellEditor/CellEditor'; import { CellContext } from '../../CellRenderer/CellContext'; -import { DataGridContext } from '../../DataGridContext'; import { TableDataContext } from '../../TableDataContext'; export const TextFormatter = observer(function TextFormatter({ row, column, isCellSelected }) { const editorRef = useRef(null); - const context = useContext(DataGridContext); const editingContext = useContext(EditingContext); const tableDataContext = useContext(TableDataContext); const cellContext = useContext(CellContext); - if (!context || !tableDataContext || !editingContext || !cellContext?.cell) { + if (!cellContext.cell) { throw new Error('Contexts required'); } const formatter = tableDataContext.format; - const rawValue = useMemo( - () => computed(() => formatter.get(tableDataContext.getCellValue(cellContext!.cell!)!)), - [tableDataContext, cellContext.cell, formatter] - ).get(); + const rawValue = getComputed(() => formatter.get(tableDataContext.getCellValue(cellContext.cell!)!)); let classes = 'text-formatter'; if (rawValue === null) { diff --git a/webapp/packages/plugin-data-spreadsheet-new/src/DataGrid/TableColumnHeader/TableColumnHeader.tsx b/webapp/packages/plugin-data-spreadsheet-new/src/DataGrid/TableColumnHeader/TableColumnHeader.tsx index 6af69fe437..131e939ede 100644 --- a/webapp/packages/plugin-data-spreadsheet-new/src/DataGrid/TableColumnHeader/TableColumnHeader.tsx +++ b/webapp/packages/plugin-data-spreadsheet-new/src/DataGrid/TableColumnHeader/TableColumnHeader.tsx @@ -11,7 +11,7 @@ import { useContext } from 'react'; import type { HeaderRendererProps } from 'react-data-grid'; import styled, { css } from 'reshadow'; -import { StaticImage } from '@cloudbeaver/core-blocks'; +import { getComputed, StaticImage } from '@cloudbeaver/core-blocks'; import { useStyles } from '@cloudbeaver/core-theming'; import { DataGridContext } from '../DataGridContext'; @@ -66,15 +66,11 @@ export const TableColumnHeader = observer>(function Tab const tableDataContext = useContext(TableDataContext); const gridSelectionContext = useContext(DataGridSelectionContext); - if (!tableDataContext || !dataGridContext || !gridSelectionContext) { - throw new Error('One of the following contexts are missed(data grid context, grid selection context, table data context)'); - } - const resultIndex = dataGridContext.resultIndex; const model = dataGridContext.model; let icon = calculatedColumn.icon; let columnName = calculatedColumn.name as string; - const dataReadonly = tableDataContext.isReadOnly() || model.isReadonly(); + const dataReadonly = getComputed(() => tableDataContext.isReadOnly() || model.isReadonly()); let columnReadOnly = !calculatedColumn.editable; let columnTooltip: string = columnName; diff --git a/webapp/packages/plugin-data-spreadsheet-new/src/DataGrid/TableDataContext.ts b/webapp/packages/plugin-data-spreadsheet-new/src/DataGrid/TableDataContext.ts index 349717081a..659df20b5c 100644 --- a/webapp/packages/plugin-data-spreadsheet-new/src/DataGrid/TableDataContext.ts +++ b/webapp/packages/plugin-data-spreadsheet-new/src/DataGrid/TableDataContext.ts @@ -51,4 +51,4 @@ export interface ITableData { isReadOnly: () => boolean; } -export const TableDataContext = createContext(null); +export const TableDataContext = createContext(undefined as any); diff --git a/webapp/packages/plugin-data-spreadsheet-new/src/Editing/EditingContext.tsx b/webapp/packages/plugin-data-spreadsheet-new/src/Editing/EditingContext.tsx index e69818e802..1126fd493e 100644 --- a/webapp/packages/plugin-data-spreadsheet-new/src/Editing/EditingContext.tsx +++ b/webapp/packages/plugin-data-spreadsheet-new/src/Editing/EditingContext.tsx @@ -22,4 +22,4 @@ export interface CellPosition { rowIdx: number; } -export const EditingContext = createContext(null); +export const EditingContext = createContext(undefined as any); diff --git a/webapp/packages/plugin-data-viewer/src/DatabaseDataModel/DatabaseDataActions.ts b/webapp/packages/plugin-data-viewer/src/DatabaseDataModel/DatabaseDataActions.ts index 8e19b00e7b..a915a9cd78 100644 --- a/webapp/packages/plugin-data-viewer/src/DatabaseDataModel/DatabaseDataActions.ts +++ b/webapp/packages/plugin-data-viewer/src/DatabaseDataModel/DatabaseDataActions.ts @@ -6,7 +6,7 @@ * you may not use this file except in compliance with the License. */ -import { makeObservable, observable } from 'mobx'; +import { action, makeObservable, observable } from 'mobx'; import type { IDatabaseDataAction, IDatabaseDataActionClass, IDatabaseDataActionInterface } from './IDatabaseDataAction'; import type { IDatabaseDataActions } from './IDatabaseDataActions'; @@ -19,13 +19,16 @@ export class DatabaseDataActions implements IDatabaseDataActions { private actions: Map>; private source: IDatabaseDataSource; + private unCommittedActions: Map>; constructor(source: IDatabaseDataSource) { this.actions = new Map(); + this.unCommittedActions = new Map(); this.source = source; makeObservable, 'actions'>(this, { actions: observable.shallow, + updateResults: action, }); } @@ -48,17 +51,12 @@ implements IDatabaseDataActions { throw new Error('DataFormat unsupported'); } - if (!this.actions.has(result.id)) { - this.actions.set(result.id, observable.array(undefined, { deep: false })); - } - - const actionsMap = this.actions.get(result.id)!; - - let action = actionsMap.find(action => action instanceof Action); + const actions = this.getOrCreateActionsList(result.id); + let action = actions.find(action => action instanceof Action); if (!action) { action = new Action(this.source, result); - actionsMap.push(action); + this.addActionToList(result.id, actions, action); } return action as T; @@ -68,13 +66,8 @@ implements IDatabaseDataActions { result: TResult, Action: IDatabaseDataActionInterface ): T | undefined { - if (!this.actions.has(result.id)) { - this.actions.set(result.id, observable.array(undefined, { deep: false })); - } - - const actionsMap = this.actions.get(result.id)!; - - const action = actionsMap.find(action => action instanceof Action); + const actions = this.getOrCreateActionsList(result.id); + const action = actions.find(action => action instanceof Action); return action as T | undefined; } @@ -97,4 +90,51 @@ implements IDatabaseDataActions { } } } + + private addActionToList( + resultId: string, + actions: ActionsList, + action: IDatabaseDataAction + ) { + actions = observable.array([...actions, action]); + + this.unCommittedActions.set(resultId, actions); + this.scheduleCommit(resultId); + } + + private getOrCreateActionsList(resultId: string): ActionsList { + let actions = this.getActionsList(resultId); + + if (!actions) { + actions = this.createActionsList(resultId); + } + + return actions; + } + + private createActionsList(resultId: string): ActionsList { + const actions: ActionsList = observable.array(undefined, { deep: false }); + + this.unCommittedActions.set(resultId, actions); + this.scheduleCommit(resultId); + + return actions; + } + + private getActionsList(resultId: string): ActionsList | undefined { + const committed = this.actions.get(resultId); + const unCommitted = this.unCommittedActions.get(resultId); + return unCommitted ?? committed; + } + + private scheduleCommit(resultId: string) { + setTimeout(() => { + const actions = this.unCommittedActions.get(resultId); + + if (actions) { + this.actions.set(resultId, actions); + this.unCommittedActions.delete(resultId); + } + }, 1); + } } diff --git a/webapp/packages/plugin-data-viewer/src/DatabaseDataModel/DatabaseDataSource.ts b/webapp/packages/plugin-data-viewer/src/DatabaseDataModel/DatabaseDataSource.ts index 848f6e1e3c..ad35412c59 100644 --- a/webapp/packages/plugin-data-viewer/src/DatabaseDataModel/DatabaseDataSource.ts +++ b/webapp/packages/plugin-data-viewer/src/DatabaseDataModel/DatabaseDataSource.ts @@ -6,7 +6,7 @@ * you may not use this file except in compliance with the License. */ -import { observable, makeObservable } from 'mobx'; +import { observable, makeObservable, action } from 'mobx'; import type { IConnectionExecutionContext } from '@cloudbeaver/core-connections'; import { ResultDataFormat } from '@cloudbeaver/core-sdk'; @@ -76,6 +76,7 @@ implements IDatabaseDataSource { activeRequest: observable.ref, activeSave: observable.ref, activeTask: observable.ref, + setSupportedDataFormats: action, }); } From e59ec00ef5e6bd0899031fe88164bef482df0150 Mon Sep 17 00:00:00 2001 From: Serge Rider Date: Thu, 2 Sep 2021 12:31:13 +0300 Subject: [PATCH 9/9] CB-1001 Role users and connections API --- .../io/cloudbeaver/DBWSecurityController.java | 3 +++ .../server/CBSecurityController.java | 20 +++++++++++++++++++ .../schema/service.admin.graphqls | 3 +++ .../service/admin/AdminRoleInfo.java | 15 ++++++++++++++ 4 files changed, 41 insertions(+) diff --git a/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/DBWSecurityController.java b/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/DBWSecurityController.java index 9f6340e471..53d5c62fdf 100644 --- a/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/DBWSecurityController.java +++ b/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/DBWSecurityController.java @@ -83,6 +83,9 @@ public interface DBWSecurityController { WebRole[] findRoles(String roleName) throws DBCException; + @NotNull + String[] getRoleSubjects(String roleId) throws DBCException; + void createRole(WebRole role) throws DBCException; void updateRole(WebRole role) throws DBCException; diff --git a/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/server/CBSecurityController.java b/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/server/CBSecurityController.java index d635f5564f..5967e70817 100644 --- a/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/server/CBSecurityController.java +++ b/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/server/CBSecurityController.java @@ -433,6 +433,26 @@ class CBSecurityController implements DBWSecurityController { return readAllRoles(); } + @NotNull + @Override + public String[] getRoleSubjects(String roleId) throws DBCException { + try (Connection dbCon = database.openConnection()) { + try (PreparedStatement dbStat = dbCon.prepareStatement( + "SELECT USER_ID FROM CB_USER_ROLE WHERE ROLE_ID")) { + dbStat.setString(1, roleId); + List subjects = new ArrayList<>(); + try (ResultSet dbResult = dbStat.executeQuery()) { + while (dbResult.next()) { + subjects.add(dbResult.getString(1)); + } + } + return subjects.toArray(new String[0]); + } + } catch (SQLException e) { + throw new DBCException("Error while reading role subjects", e); + } + } + @NotNull private WebRole fetchRole(ResultSet dbResult) throws SQLException { WebRole role = new WebRole(dbResult.getString("ROLE_ID")); diff --git a/server/bundles/io.cloudbeaver.service.admin/schema/service.admin.graphqls b/server/bundles/io.cloudbeaver.service.admin/schema/service.admin.graphqls index b620d3c4a7..6e26ec7218 100644 --- a/server/bundles/io.cloudbeaver.service.admin/schema/service.admin.graphqls +++ b/server/bundles/io.cloudbeaver.service.admin/schema/service.admin.graphqls @@ -36,6 +36,9 @@ type AdminRoleInfo { roleName: String description: String + grantedUsers: [ID!]! + grantedConnections: [AdminConnectionGrantInfo!]! + rolePermissions: [ID]! } diff --git a/server/bundles/io.cloudbeaver.service.admin/src/io/cloudbeaver/service/admin/AdminRoleInfo.java b/server/bundles/io.cloudbeaver.service.admin/src/io/cloudbeaver/service/admin/AdminRoleInfo.java index 1d6257df20..bbb17970ce 100644 --- a/server/bundles/io.cloudbeaver.service.admin/src/io/cloudbeaver/service/admin/AdminRoleInfo.java +++ b/server/bundles/io.cloudbeaver.service.admin/src/io/cloudbeaver/service/admin/AdminRoleInfo.java @@ -16,7 +16,11 @@ */ package io.cloudbeaver.service.admin; +import io.cloudbeaver.DBWConnectionGrant; import io.cloudbeaver.model.user.WebRole; +import io.cloudbeaver.server.CBPlatform; +import org.jkiss.dbeaver.model.exec.DBCException; +import org.jkiss.dbeaver.model.meta.Property; import java.util.List; @@ -51,4 +55,15 @@ public class AdminRoleInfo { public void setRolePermissions(List rolePermissions) { this.rolePermissions = rolePermissions; } + + @Property + public DBWConnectionGrant[] getGrantedConnections() throws DBCException { + return CBPlatform.getInstance().getApplication().getSecurityController().getSubjectConnectionAccess(new String[] { getRoleId()} ); + } + + @Property + public String[] getGrantedUsers() throws DBCException { + return CBPlatform.getInstance().getApplication().getSecurityController().getRoleSubjects(getRoleId()); + } + }