Skip to content

Commit e2c269a

Browse files
authored
feat: side panel aka refrigerator for query text in top queries (#2134)
1 parent 974775f commit e2c269a

14 files changed

+636
-147
lines changed

src/components/Drawer/Drawer.tsx

+9-7
Original file line numberDiff line numberDiff line change
@@ -10,13 +10,13 @@ import {CopyLinkButton} from '../CopyLinkButton/CopyLinkButton';
1010

1111
import {useDrawerContext} from './DrawerContext';
1212

13+
import './Drawer.scss';
14+
1315
const DEFAULT_DRAWER_WIDTH_PERCENTS = 60;
1416
const DEFAULT_DRAWER_WIDTH = 600;
1517
const DRAWER_WIDTH_KEY = 'drawer-width';
1618
const b = cn('ydb-drawer');
1719

18-
import './Drawer.scss';
19-
2020
type DrawerEvent = MouseEvent & {
2121
_capturedInsideDrawer?: boolean;
2222
};
@@ -129,7 +129,7 @@ const DrawerPaneContentWrapper = ({
129129
);
130130
};
131131

132-
type DrawerControl =
132+
export type DrawerControl =
133133
| {type: 'close'}
134134
| {type: 'copyLink'; link: string}
135135
| {type: 'custom'; node: React.ReactNode; key: string};
@@ -223,10 +223,12 @@ export const DrawerWrapper = ({
223223
detectClickOutside={detectClickOutside}
224224
isPercentageWidth={isPercentageWidth}
225225
>
226-
<div className={b('content-wrapper')}>
227-
{renderDrawerHeader()}
228-
{renderDrawerContent()}
229-
</div>
226+
{isDrawerVisible ? (
227+
<div className={b('content-wrapper')}>
228+
{renderDrawerHeader()}
229+
{renderDrawerContent()}
230+
</div>
231+
) : null}
230232
</DrawerPaneContentWrapper>
231233
</React.Fragment>
232234
);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
import {Button, Flex, Icon, Text} from '@gravity-ui/uikit';
2+
3+
import {cn} from '../../../../../utils/cn';
4+
import i18n from '../i18n';
5+
6+
import CryCatIcon from '../../../../../assets/icons/cry-cat.svg';
7+
8+
const b = cn('kv-top-queries');
9+
10+
interface NotFoundContainerProps {
11+
onClose: () => void;
12+
}
13+
14+
export const NotFoundContainer = ({onClose}: NotFoundContainerProps) => {
15+
return (
16+
<Flex
17+
justifyContent="center"
18+
alignItems="center"
19+
direction="column"
20+
className={b('not-found-container')}
21+
>
22+
<Icon data={CryCatIcon} size={100} />
23+
<Text variant="subheader-2" className={b('not-found-title')}>
24+
{i18n('query-details.not-found.title')}
25+
</Text>
26+
<Text variant="body-1" color="complementary" className={b('not-found-description')}>
27+
{i18n('query-details.not-found.description')}
28+
</Text>
29+
<Button size="m" view="normal" className={b('not-found-close')} onClick={onClose}>
30+
{i18n('query-details.close')}
31+
</Button>
32+
</Flex>
33+
);
34+
};
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
@import '../../../../../styles/mixins.scss';
2+
3+
.ydb-query-details {
4+
flex: 1;
5+
6+
color: var(--g-color-text-primary);
7+
background-color: var(--g-color-base-background-dark);
8+
9+
&__content {
10+
overflow: auto;
11+
flex: 1;
12+
13+
padding: var(--g-spacing-5) var(--g-spacing-4) var(--g-spacing-5) var(--g-spacing-4);
14+
}
15+
16+
&__query-header {
17+
display: flex;
18+
justify-content: space-between;
19+
align-items: center;
20+
21+
padding: var(--g-spacing-2) var(--g-spacing-3);
22+
23+
border-bottom: 1px solid var(--g-color-line-generic);
24+
}
25+
26+
&__query-title {
27+
font-size: 14px;
28+
font-weight: 500;
29+
}
30+
31+
&__query-content {
32+
position: relative;
33+
34+
display: flex;
35+
flex: 1;
36+
flex-direction: column;
37+
38+
margin-top: var(--g-spacing-5);
39+
40+
border-radius: 4px;
41+
background-color: var(--code-background-color);
42+
}
43+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
import {Code} from '@gravity-ui/icons';
2+
import {Button, Flex, Icon} from '@gravity-ui/uikit';
3+
4+
import type {InfoViewerItem} from '../../../../../components/InfoViewer';
5+
import {InfoViewer} from '../../../../../components/InfoViewer';
6+
import {YDBSyntaxHighlighter} from '../../../../../components/SyntaxHighlighter/YDBSyntaxHighlighter';
7+
import {cn} from '../../../../../utils/cn';
8+
import i18n from '../i18n';
9+
10+
import './QueryDetails.scss';
11+
12+
const b = cn('ydb-query-details');
13+
14+
interface QueryDetailsProps {
15+
queryText: string;
16+
infoItems: InfoViewerItem[];
17+
onOpenInEditor: () => void;
18+
}
19+
20+
export const QueryDetails = ({queryText, infoItems, onOpenInEditor}: QueryDetailsProps) => {
21+
return (
22+
<Flex direction="column" className={b()}>
23+
<Flex direction="column" className={b('content')}>
24+
<InfoViewer info={infoItems} />
25+
26+
<div className={b('query-content')}>
27+
<div className={b('query-header')}>
28+
<div className={b('query-title')}>{i18n('query-details.query.title')}</div>
29+
<Button
30+
view="flat-secondary"
31+
size="m"
32+
onClick={onOpenInEditor}
33+
className={b('editor-button')}
34+
>
35+
<Icon data={Code} size={16} />
36+
{i18n('query-details.open-in-editor')}
37+
</Button>
38+
</div>
39+
<YDBSyntaxHighlighter
40+
language="yql"
41+
text={queryText}
42+
withClipboardButton={{alwaysVisible: true, withLabel: false}}
43+
/>
44+
</div>
45+
</Flex>
46+
</Flex>
47+
);
48+
};
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
import React from 'react';
2+
3+
import {useHistory, useLocation} from 'react-router-dom';
4+
5+
import {parseQuery} from '../../../../../routes';
6+
import {changeUserInput, setIsDirty} from '../../../../../store/reducers/query/query';
7+
import {
8+
TENANT_PAGE,
9+
TENANT_PAGES_IDS,
10+
TENANT_QUERY_TABS_ID,
11+
} from '../../../../../store/reducers/tenant/constants';
12+
import type {KeyValueRow} from '../../../../../types/api/query';
13+
import {useTypedDispatch} from '../../../../../utils/hooks';
14+
import {TenantTabsGroups, getTenantPath} from '../../../TenantPages';
15+
import {createQueryInfoItems} from '../utils';
16+
17+
import {NotFoundContainer} from './NotFoundContainer';
18+
import {QueryDetails} from './QueryDetails';
19+
20+
interface QueryDetailsDrawerContentProps {
21+
// Three cases:
22+
// 1. row is KeyValueRow and we can show it.
23+
// 2. row is null and we can show not found container.
24+
// 3. row is undefined and we can show nothing.
25+
row?: KeyValueRow | null;
26+
onClose: () => void;
27+
}
28+
29+
export const QueryDetailsDrawerContent = ({row, onClose}: QueryDetailsDrawerContentProps) => {
30+
const dispatch = useTypedDispatch();
31+
const location = useLocation();
32+
const history = useHistory();
33+
34+
const handleOpenInEditor = React.useCallback(() => {
35+
if (row) {
36+
const input = row.QueryText as string;
37+
dispatch(changeUserInput({input}));
38+
dispatch(setIsDirty(false));
39+
40+
const queryParams = parseQuery(location);
41+
42+
const queryPath = getTenantPath({
43+
...queryParams,
44+
[TENANT_PAGE]: TENANT_PAGES_IDS.query,
45+
[TenantTabsGroups.queryTab]: TENANT_QUERY_TABS_ID.newQuery,
46+
});
47+
48+
history.push(queryPath);
49+
}
50+
}, [dispatch, history, location, row]);
51+
52+
if (row) {
53+
return (
54+
<QueryDetails
55+
queryText={row.QueryText as string}
56+
infoItems={createQueryInfoItems(row)}
57+
onOpenInEditor={handleOpenInEditor}
58+
/>
59+
);
60+
}
61+
62+
return <NotFoundContainer onClose={onClose} />;
63+
};

0 commit comments

Comments
 (0)