Skip to content

Commit

Permalink
feat(Attributes): add download button [YTFRONT-4310]
Browse files Browse the repository at this point in the history
  • Loading branch information
SimbiozizV committed Dec 12, 2024
1 parent c4bd229 commit 6710c0b
Show file tree
Hide file tree
Showing 19 changed files with 268 additions and 16 deletions.
16 changes: 16 additions & 0 deletions packages/ui/src/ui/components/AttributesModal/AttributesModal.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ import Error from '../../components/Error/Error';
import Yson from '../Yson/Yson';

import {closeAttributesModal} from '../../store/actions/modals/attributes-modal';
import {DownloadFileButton} from '../DownloadAttributesButton';
import {attributesToString} from '../DownloadAttributesButton/helpers/attributesToString';

export class AttributesModal extends Component {
static propTypes = {
Expand Down Expand Up @@ -37,13 +39,27 @@ export class AttributesModal extends Component {
);
}

renderDownloadButton() {
const convertedValue = attributesToString(this.props.attributes, this.props.ysonSettings);
if (!convertedValue) return null;

return (
<DownloadFileButton
content={convertedValue}
name={`${this.props.title}.txt`}
type="application/txt"
/>
);
}

render() {
const {visible, title, loading} = this.props;

return (
visible && (
<SimpleModal
title={<FormattedText text={title} />}
footerContent={this.renderDownloadButton()}
visible={visible}
loading={loading}
onCancel={this.handleClose}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import React, {FC} from 'react';
import {Button} from '@gravity-ui/uikit';
import Icon from '../../components/Icon/Icon';

type Props = {
content: string;
name: string;
type?: string;
};

export const DownloadFileButton: FC<Props> = ({name, type, content}) => {
const handleClick = () => {
const blob = new Blob([content], {type});
const url = URL.createObjectURL(blob);
const link = document.createElement('a');
link.href = url;
link.download = name;
document.body.appendChild(link);
link.click();

document.body.removeChild(link);
URL.revokeObjectURL(url);
};

return (
<Button view="flat" onClick={handleClick}>
<Icon awesome="download" size={16} /> Download
</Button>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import React, {FC, useMemo} from 'react';
import {UnipikaSettings} from '../Yson/StructuredYson/StructuredYsonTypes';
import {DownloadFileButton} from './DownloadFileButton';
import {attributesToString} from './helpers/attributesToString';

type Props = {
name?: string;
value?: unknown;
settings: UnipikaSettings;
};

export const YsonDownloadButton: FC<Props> = ({value, name, settings}) => {
const fileName = name || 'data';

const fileContent: string = useMemo(() => {
return attributesToString(value, settings);
}, [settings, value]);

return (
<DownloadFileButton content={fileContent} name={`${fileName}.txt`} type="application/txt" />
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import unipika from '../../../common/thor/unipika';
import {UnipikaSettings} from '../../Yson/StructuredYson/StructuredYsonTypes';

export const attributesToString = (attributes: unknown, settings: UnipikaSettings) => {
if (attributes === undefined || !settings) return '';

const convertedValue =
settings.format === 'raw-json'
? unipika.converters.raw(attributes, settings)
: unipika.converters.yson(attributes, settings);

return unipika.format(convertedValue, {...settings, asHTML: false});
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export {DownloadFileButton} from './DownloadFileButton';
export {YsonDownloadButton} from './YsonDownloadButton';
8 changes: 8 additions & 0 deletions packages/ui/src/ui/components/Modal/SimpleModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ interface SimpleModalProps {
borderless?: boolean;
onCancel: () => void;
children?: React.ReactNode;
footerContent?: React.ReactNode;
onOutsideClick?: () => void;
className?: string;
wrapperClassName?: string;
Expand Down Expand Up @@ -74,6 +75,12 @@ class SimpleModal extends Component<SimpleModalProps> {
return <div className={contentClassName}>{loading ? this.renderLoader() : children}</div>;
}

renderFooter() {
if (!this.props.footerContent) return null;

return <div className={b('footer')}>{this.props.footerContent}</div>;
}

render() {
const {visible, onOutsideClick, size, className, wrapperClassName} = this.props;
return (
Expand All @@ -82,6 +89,7 @@ class SimpleModal extends Component<SimpleModalProps> {
<div className={b('wrapper', {size}, wrapperClassName)}>
{this.renderHeader()}
{this.renderContent()}
{this.renderFooter()}
</div>
</ModalImpl>
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ import {YTError} from '../../../../@types/types';
import {WaitForDefaultPoolTree} from '../../../hooks/global-pool-trees';

import './ChytPageCliqueSpeclet.scss';
import {YsonDownloadButton} from '../../../components/DownloadAttributesButton';

const block = cn('yt-chyt-clique-speclet');

Expand Down Expand Up @@ -103,6 +104,13 @@ function ChytSpeclet({alias, unipikaSettings}: {alias?: string; unipikaSettings:
value={data}
settings={unipikaSettings}
folding
extraTools={
<YsonDownloadButton
value={data}
settings={unipikaSettings}
name={`chyt_speclet_${alias}`}
/>
}
/>
)}
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import {
import Error from '../../../../../components/Error/Error';
import Yson from '../../../../../components/Yson/Yson';
import {getNodeUnrecognizedOptionsYsonSettings} from '../../../../../store/selectors/thor/unipika';
import {YsonDownloadButton} from '../../../../../components/DownloadAttributesButton';

export function NodeUnrecognizedOptions({host}: {host: string}) {
const dispatch = useDispatch();
Expand All @@ -36,6 +37,13 @@ export function NodeUnrecognizedOptions({host}: {host: string}) {
settings={unipikaSettings}
folding
virtualized
extraTools={
<YsonDownloadButton
value={data}
settings={unipikaSettings}
name={`unrecognized_options_${host}`}
/>
}
/>
);
}
14 changes: 13 additions & 1 deletion packages/ui/src/ui/pages/job/JobGeneral/JobGeneral.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ import {StaleJobIcon} from '../../../pages/operations/OperationDetail/tabs/Jobs/

import './JobGeneral.scss';
import {UI_TAB_SIZE} from '../../../constants/global';
import {YsonDownloadButton} from '../../../components/DownloadAttributesButton';

const block = cn('job-general');

Expand Down Expand Up @@ -276,7 +277,18 @@ export default function JobGeneral() {

<Switch>
<Route path={`${path}/${Tab.ATTRIBUTES}`}>
<Yson value={attributes} settings={settings} folding />
<Yson
value={attributes}
settings={settings}
folding
extraTools={
<YsonDownloadButton
value={attributes}
settings={settings}
name={`attributes_job_${id}`}
/>
}
/>
</Route>
<Route path={`${path}/${Tab.DETAILS}`}>
<Details />
Expand Down
16 changes: 14 additions & 2 deletions packages/ui/src/ui/pages/job/tabs/Specification/Specification.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import {useDispatch, useSelector} from 'react-redux';
import cn from 'bem-cn-lite';

import Yson from '../../../../components/Yson/Yson';
import {Checkbox, Loader} from '@gravity-ui/uikit';
import {Checkbox, Flex, Loader} from '@gravity-ui/uikit';
import ErrorBoundary from '../../../../components/ErrorBoundary/ErrorBoundary';
import LoadDataHandler from '../../../../components/LoadDataHandler/LoadDataHandler';

Expand All @@ -18,6 +18,8 @@ import {

import './Specification.scss';
import {getJobSpecificationYsonSettings} from '../../../../store/selectors/thor/unipika';
import {YsonDownloadButton} from '../../../../components/DownloadAttributesButton';
import {getJob} from '../../../../store/selectors/job/detail';

interface SpecificationProps {
jobID: string;
Expand Down Expand Up @@ -92,6 +94,7 @@ export default function Specification({jobID}: SpecificationProps) {
const {loading, loaded, error, errorData, specification} = useSelector(
(state: RootState) => state.job.specification,
);
const {id} = useSelector(getJob);

useEffect(() => {
dispatch(loadJobSpecification(jobID));
Expand All @@ -116,7 +119,16 @@ export default function Specification({jobID}: SpecificationProps) {
folding={true}
settings={settings}
value={specification}
extraTools={<Toolbar jobID={jobID} />}
extraTools={
<Flex alignItems="center" gap={2}>
<YsonDownloadButton
value={specification}
settings={settings}
name={`specification_job_${id}`}
/>
<Toolbar jobID={jobID} />
</Flex>
}
/>
</LoadDataHandler>
)}
Expand Down
3 changes: 3 additions & 0 deletions packages/ui/src/ui/pages/navigation/helpers/pathToFileName.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export const pathToFileName = (path: string) => {
return path.replace(/\/+/g, '_');
};
27 changes: 24 additions & 3 deletions packages/ui/src/ui/pages/navigation/tabs/Attributes/Attributes.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,24 +5,45 @@ import {connect, useSelector} from 'react-redux';

import Yson from '../../../../components/Yson/Yson';

import {getAttributesWithTypes, getLoadState} from '../../../../store/selectors/navigation';
import {
getAttributesPath,
getAttributesWithTypes,
getLoadState,
} from '../../../../store/selectors/navigation';
import {RumMeasureTypes} from '../../../../rum/rum-measure-types';
import {isFinalLoadingStatus} from '../../../../utils/utils';
import {useRumMeasureStop} from '../../../../rum/RumUiContext';
import {useAppRumMeasureStart} from '../../../../rum/rum-app-measures';
import {YsonDownloadButton} from '../../../../components/DownloadAttributesButton';
import {pathToFileName} from '../../helpers/pathToFileName';

function Attributes({attributes, settings}) {
return <Yson settings={settings} value={attributes} folding />;
function Attributes({attributes, settings, attributesPath}) {
return (
<Yson
settings={settings}
value={attributes}
folding
extraTools={
<YsonDownloadButton
value={attributes}
settings={settings}
name={`attributes_${pathToFileName(attributesPath)}`}
/>
}
/>
);
}

Attributes.propTypes = {
attributes: PropTypes.object.isRequired,
attributesPath: PropTypes.string.isRequired,
settings: Yson.settingsProps.isRequired,
};

const mapStateToProps = (state) => ({
attributes: getAttributesWithTypes(state),
settings: unipika.prepareSettings(),
attributesPath: getAttributesPath(state),
});

const AttributesConnected = connect(mapStateToProps)(Attributes);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@ import Loader from '../../../../../components/Loader/Loader';
import {UnipikaSettings} from '../../../../../components/Yson/StructuredYson/StructuredYsonTypes';

import './PipelineSpec.scss';
import {YsonDownloadButton} from '../../../../../components/DownloadAttributesButton';
import {pathToFileName} from '../../../helpers/pathToFileName';

const block = cn('yt-pipeline-spec');

Expand All @@ -59,6 +61,11 @@ function PipelineSpec({path, data, error, name, onSave}: PipelineSpecProps) {
folding
extraTools={
<React.Fragment>
<YsonDownloadButton
value={data}
settings={settings}
name={`pipeline_spec_${pathToFileName(path)}`}
/>
<Button view="outlined" onClick={() => setShowEdit(true)}>
<Icon awesome="pencil" />
Edit {name}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,16 @@ import {getNavigationTableMountConfig} from '../../../../store/selectors/navigat
import ErrorBlock from '../../../../components/Error/Error';
import Yson from '../../../../components/Yson/Yson';
import {getNavigationMountConfigYsonSettings} from '../../../../store/selectors/thor/unipika';
import {YsonDownloadButton} from '../../../../components/DownloadAttributesButton';
import {UnipikaValue} from '../../../../components/Yson/StructuredYson/StructuredYsonTypes';
import {pathToFileName} from '../../helpers/pathToFileName';
import {getPath} from '../../../../store/selectors/navigation';

const block = cn('table-mount-config');

function TableMountConfig() {
const {data, error} = useSelector(getNavigationTableMountConfig);
const path = useSelector(getPath);

const settings = useSelector(getNavigationMountConfigYsonSettings);

Expand All @@ -18,7 +23,18 @@ function TableMountConfig() {
{error ? (
<ErrorBlock error={error} topMargin={'none'} />
) : (
<Yson value={data} settings={settings} folding />
<Yson
value={data}
settings={settings}
folding
extraTools={
<YsonDownloadButton
value={data as UnipikaValue}
settings={settings}
name={`mount_config_${pathToFileName(path)}`}
/>
}
/>
)}
</div>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ import {RumMeasureTypes} from '../../../../rum/rum-measure-types';
import {calculateLoadingStatus, isFinalLoadingStatus} from '../../../../utils/utils';

import './UserAttributes.scss';
import {YsonDownloadButton} from '../../../../components/DownloadAttributesButton';
import {pathToFileName} from '../../helpers/pathToFileName';

const block = cn('navigation-user-attributes');

Expand All @@ -41,7 +43,18 @@ function UserAttributes(props) {
{initialLoading ? (
<Loader />
) : (
<Yson value={userAttributes} settings={settings} folding />
<Yson
value={userAttributes}
settings={settings}
extraTools={
<YsonDownloadButton
value={userAttributes}
settings={settings}
name={`user_attributes_${pathToFileName(path)}`}
/>
}
folding
/>
)}
</div>
</LoadDataHandler>
Expand Down
Loading

0 comments on commit 6710c0b

Please sign in to comment.