Skip to content
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(Navigation/Queue): add features for queues exports [YTFRONT-4482] #1021

Draft
wants to merge 2 commits into
base: main
Choose a base branch
from
Draft
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
5 changes: 4 additions & 1 deletion packages/ui/src/ui/components/Dialog/Dialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ import {DatePickerControl} from './controls/DatePickerControl/DatePickerControl'
import {RangeInputPickerControl} from './controls/RangeInputPickerControl/RangeInputPickerControl';
import {AclColumnsControl} from '../../containers/ACL/RequestPermissions/AclColumnsControl/AclColumnsControl';
import {useHotkeysScope} from '../../hooks/use-hotkeysjs-scope';
import ExportsEditTabField from '../../pages/navigation/tabs/Queue/views/Exports/ExportsEdit/ExportsEditDialog/ExportsEditTabField/ExportsEditTabField';

const block = cn('yt-dialog');

Expand Down Expand Up @@ -204,10 +205,12 @@ export type DialogField<FormValues = unknown> =
>;

registerDialogTabControl('yt-create-table-tab', CreateTableTabField);
registerDialogTabControl('yt-create-queue-export-tab', ExportsEditTabField);

export type DialogTabField<FieldT> =
| DFDialogTabField<FieldT>
| RegisteredDialogTabField<'yt-create-table-tab', any, FieldT>;
| RegisteredDialogTabField<'yt-create-table-tab', any, FieldT>
| RegisteredDialogTabField<'yt-create-queue-export-tab', any, FieldT>;

export type YTDialogType = typeof YTDialog;
export function YTDialog<Values, InitialValues = Partial<Values>>(
Expand Down
2 changes: 2 additions & 0 deletions packages/ui/src/ui/constants/navigation/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ export const Tab = {
ANNOTATION: 'annotation',
ACCESS_LOG: 'access_log',
MOUNT_CONFIG: 'mount_config',
ORIGINATING_QUEUE: 'originating_queue',
} as const;

export const ContentMode = {
Expand All @@ -34,6 +35,7 @@ export const SELECT_ALL = PREFIX + 'SELECT_ALL';
export const SET_CONTENT_MODE = PREFIX + 'SET_CONTENT_MODE';
export const SET_TEXT_FILTER = PREFIX + 'SET_TEXT_FILTER';
export const SET_TRANSACTION = PREFIX + 'SET_TRANSACTION';
export const SET_ORIGINATING_QUEUE_PATH = PREFIX + 'SET_ORIGINATING_QUEUE_PATH';
export const CLEAR_TRANSACTION = PREFIX + 'CLEAR_TRANSACTION';
export const UPDATE_PATH = PREFIX + 'UPDATE_PATH';
export const UPDATE_VIEW = createActionTypes(`${PREFIX}UPDATE_VIEW`);
Expand Down
1 change: 1 addition & 0 deletions packages/ui/src/ui/constants/navigation/tabs/queue.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ export enum QUEUE_MODE {
METRICS = 'metrics',
PARTITIONS = 'partitions',
CONSUMERS = 'consumers',
EXPORTS = 'exports',
}

// eslint-disable-next-line @typescript-eslint/naming-convention
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -166,7 +166,9 @@ class Navigation extends Component {

onTabChange = (value) => {
const {setMode} = this.props;
setMode(value);
if (value !== Tab.ORIGINATING_QUEUE) {
setMode(value);
}
};

renderTabs() {
Expand Down
2 changes: 2 additions & 0 deletions packages/ui/src/ui/pages/navigation/tabs/Queue/Queue.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,13 +26,15 @@ import ConsumersExtraControls from './views/Consumers/ConsumersExtraControls';
import Partitions from './views/Partitions/Partitions';
import PartitionsExtraControls from './views/Partitions/PartitionsExtraControls';
import UIFactory from '../../../../UIFactory';
import {Exports} from './views/Exports/Exports';

const emptyView = {ExtraControls: () => null, View: () => null};

const views: Record<QUEUE_MODE, {ExtraControls: ComponentType; View: ComponentType}> = {
[QUEUE_MODE.METRICS]: {ExtraControls: () => null, View: () => null},
[QUEUE_MODE.PARTITIONS]: {ExtraControls: PartitionsExtraControls, View: Partitions},
[QUEUE_MODE.CONSUMERS]: {ExtraControls: ConsumersExtraControls, View: Consumers},
[QUEUE_MODE.EXPORTS]: {ExtraControls: () => null, View: Exports},
};

function useViewByMode(mode: QUEUE_MODE) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,10 @@ const tabItems: React.ComponentProps<typeof RadioButton>['items'] = [
value: QUEUE_MODE.CONSUMERS,
text: 'Consumers',
},
{
value: QUEUE_MODE.EXPORTS,
text: 'Exports',
},
];

const Toolbar: React.VFC<Props> = ({extras: Extras, queueMode, changeQueueMode}) => {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
.exports {
&__configs {
width: 100%;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import React, {useEffect} from 'react';
import {useDispatch, useSelector} from 'react-redux';
import {Flex, Loader} from '@gravity-ui/uikit';
import b from 'bem-cn-lite';

import {getEditJsonYsonSettings} from '../../../../../../store/selectors/thor/unipika';
import {requestExportsConfig} from '../../../../../../store/actions/navigation/tabs/queue/exports';
import {
getExportsConfig,
getExportsConfigRequestInfo,
} from '../../../../../../store/selectors/navigation/tabs/queue';
import {getPath} from '../../../../../../store/selectors/navigation';

import Yson from '../../../../../../components/Yson/Yson';
import {YsonDownloadButton} from '../../../../../../components/DownloadAttributesButton/YsonDownloadButton';
import {ExportsEdit} from './ExportsEdit/ExportsEdit';

import './Exports.scss';

const block = b('exports');

export function Exports() {
const dispatch = useDispatch();

const unipikaSettings = useSelector(getEditJsonYsonSettings);
const config = useSelector(getExportsConfig);
const {loading, loaded} = useSelector(getExportsConfigRequestInfo);
const path = useSelector(getPath);

useEffect(() => {
dispatch(requestExportsConfig());
}, [path]);

return (
<Flex justifyContent={'center'} className={block()}>
{!loading && loaded ? (
<Yson
value={config}
folding
settings={unipikaSettings}
extraTools={<ExportsExtraTools config={config} settings={unipikaSettings} />}
className={block('configs')}
/>
) : (
<Loader />
)}
</Flex>
);
}

function ExportsExtraTools({config, settings}: any) {
return (
<Flex direction={'row'} gap={2}>
<YsonDownloadButton value={config} settings={settings} />
<ExportsEdit />
</Flex>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
.exports-edit {
height: 0;
text-align: right;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import React from 'react';
import {Button} from '@gravity-ui/uikit';
import b from 'bem-cn-lite';

import Icon from '../../../../../../../components/Icon/Icon';
import {QueueExportConfig} from '../../../../../../../types/navigation/queue/queue';

import {ExportsEditDialog} from './ExportsEditDialog/ExportsEditDialog';

import './ExportsEdit.scss';

const block = b('exports-edit');

type ExportConfigUtility = {
id: string;
name: string;
};

export type ExportConfig = ExportConfigUtility & QueueExportConfig<{value: number}>;

export function ExportsEdit() {
const [visible, setVisible] = React.useState(false);

const toggleVisibility = React.useCallback(() => {
setVisible(!visible);
}, [visible, setVisible]);

return (
<>
<div className={block()}>
<Button className={block('button')} view="flat" onClick={toggleVisibility}>
<Icon awesome="pencil" />
Edit config
</Button>
</div>
<ExportsEditDialog visible={visible} onClose={toggleVisibility} />
</>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
import React, {useEffect, useState} from 'react';
import {useDispatch, useSelector} from 'react-redux';

import {getExportsConfig} from '../../../../../../../../store/selectors/navigation/tabs/queue';
import {updateExportsConfig} from '../../../../../../../../store/actions/navigation/tabs/queue/exports';

import {FormApi, YTDFDialog} from '../../../../../../../../components/Dialog';

import {renderControls} from './TabControls';
import {ExportConfig} from '../ExportsEdit';
import {
fields,
prepareInitialValues,
prepareNewExport,
prepareUpdateValues,
validate,
} from './utils';

interface DialogProps {
visible: boolean;
onClose: () => void;
}

export type FormValues = {
exports: ExportConfig[];
};

export function ExportsEditDialog(props: DialogProps) {
const {visible, onClose} = props;
const configs = useSelector(getExportsConfig);
const dispatch = useDispatch();

const [initialValues, setInitialValues] = useState<FormValues>();
const [nextId, setNextId] = useState(1);

useEffect(() => {
setInitialValues(prepareInitialValues(configs));
}, [configs]);

const onCreateTab = () => {
const res = prepareNewExport(nextId);
setNextId((id) => id + 1);
return res;
};

const onAdd = async (form: FormApi<FormValues, Partial<FormValues>>) => {
const {values} = form.getState();
const preparedValues = prepareUpdateValues(values['exports']);
dispatch(updateExportsConfig(preparedValues));
};

return (
<YTDFDialog<FormValues>
onAdd={onAdd}
visible={visible}
onClose={onClose}
size="l"
headerProps={{
title: 'Edit config',
}}
initialValues={initialValues}
validate={validate}
fields={[
{
type: 'yt-create-queue-export-tab',
name: 'exports',
isRemovable: () => false,
getTitle: (values) => values.name,
onCreateTab: onCreateTab,
renderControls: renderControls,
multiple: true,
fields: fields,
},
]}
virtualized
/>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
.export-edit-tab-field {
display: flex;
flex-direction: column;
overflow: hidden;
width: 240px;
min-height: 450px;

&__add-export {
flex-shrink: 0;

display: flex;
align-items: center;

padding: 20px 16px 10px 24px;
font-size: var(--g-text-subheader-3-font-size);

&-label {
flex-grow: 1;
}
}

&__exports {
flex-grow: 1;
flex-shrink: 1;
border-bottom: 1px solid var(--light-divider);
width: 225px;
&-item {
flex-grow: 1;
display: flex;
align-items: center;
overflow: hidden;

&-node {
flex-grow: 1;
flex-shrink: 1;
overflow: hidden;
text-overflow: ellipsis;
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import React from 'react';
import b from 'bem-cn-lite';
import {Button} from '@gravity-ui/uikit';

import Icon from '../../../../../../../../../components/Icon/Icon';
import {
TabFieldVertical,
TabFieldVerticalProps,
} from '../../../../../../../../../components/Dialog';

import './ExportsEditTabField.scss';

const block = b('export-edit-tab-field');

type Props = TabFieldVerticalProps;

export default class ExportsEditTabField extends React.Component<Props> {
static isTabControl = true as const;
static isTabControlVertical = true;

onAddExport(active = true) {
const {onCreateTab = () => {}} = this.props;
onCreateTab('exports', active);
}

render() {
const {activeTab, ...rest} = this.props;

return (
<div className={block()}>
<AddExport onAddExport={this.onAddExport.bind(this)} />
<TabFieldVertical
{...rest}
activeTab={activeTab}
size={'m'}
className={block('exports')}
/>
</div>
);
}
}

function AddExport({onAddExport}: {onAddExport: Function}) {
return (
<div className={block('add-export')}>
<span className={block('add-export-label')}>Exports</span>
<Button onClick={() => onAddExport(true)} qa={'create-config'}>
<Icon awesome={'plus'} />
Add export
</Button>
</div>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import React from 'react';
import {Button} from '@gravity-ui/uikit';

import Icon from '../../../../../../../../components/Icon/Icon';

export function renderControls(
_item: any,
_onCreate: (active?: boolean) => void,
onRemove?: () => void,
) {
return (
<Button
onClick={(e: React.MouseEvent<HTMLButtonElement>) => {
onRemove?.();
e.stopPropagation();
}}
>
<Icon awesome={'trash-alt'} /> Delete column
</Button>
);
}
Loading
Loading