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/Document): allow YQL query button if /@_yql_type == "view" [YTFRONT-4463] #849

Merged
merged 10 commits into from
Dec 24, 2024
Merged
2 changes: 2 additions & 0 deletions packages/ui/src/@types/types.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,3 +37,5 @@ export type Pick2<
? T[L1]
: R;
};

export type KeysByType<T, V> = {[K in keyof T]: T[K] extends V ? K : never}[keyof T];
1 change: 1 addition & 0 deletions packages/ui/src/shared/constants/settings-types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ interface NavigationSettings {
'global::navigation::enableTableSimilarity': boolean;
'global::navigation::clusterPagePaneSizes': number;
'global::navigation::defaultChytAlias': string;
'global::navigation::sqlService': Array<'qtkit' | 'yqlkit'>;
}

interface SystemSettings {
Expand Down
12 changes: 10 additions & 2 deletions packages/ui/src/ui/UIFactory/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,14 @@ export type QueryResultChartTab = {
renderContent: (params: {query: QueryItem}) => React.ReactNode;
};

export type YQLButtonProps = {
className?: string;
disabled?: boolean;
opened?: boolean;
onOpen(): void;
onClose(): void;
};

export interface UIFactory {
getClusterAppearance(cluster?: string): undefined | ClusterAppearance;

Expand Down Expand Up @@ -404,8 +412,8 @@ export interface UIFactory {
renderUserSuggest(props: UserSuggestProps): React.ReactNode;

yqlWidgetSetup?: {
renderButton(props: {className?: string; isSplit?: string}): React.ReactNode;
renderWidget(props: {cluster: string; path: string; attributes: unknown}): React.ReactNode;
renderButton(props: YQLButtonProps): React.ReactNode;
renderWidget(props?: {visible?: boolean; onClose: () => void}): React.ReactNode;
renderYqlOperationLink(yqlOperationId: string): React.ReactNode;
};

Expand Down
18 changes: 11 additions & 7 deletions packages/ui/src/ui/components/Select/Select.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,20 +13,23 @@ import './Select.scss';

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

export interface YTSelectProps extends Omit<SelectProps, 'options' | 'filter' | 'onChange'> {
export interface YTSelectProps<T extends string = string>
extends Omit<SelectProps, 'options' | 'filter' | 'onChange' | 'onUpdate' | 'value'> {
className?: string;
items: Array<Item>;
value?: Array<T>;
items: Array<Item<T>>;
maxVisibleValues?: number;
maxVisibleValuesTextLength?: number;
hideClear?: boolean;
hideFilter?: boolean;
onChange?: (v: Required<YTSelectProps>['value']) => void;
onUpdate?: (v: Array<T>) => void;

renderItem?: (item: Item, useNoValue?: boolean) => React.ReactNode;
}

export interface Item {
value: string;
export interface Item<T extends string = string> {
value: T;
text?: React.ReactNode;
count?: number;
icon?: React.ReactNode;
Expand Down Expand Up @@ -70,12 +73,13 @@ SelectFacade.getDefaultValue = () => {
return undefined;
};

interface SelectSingleProps extends Omit<YTSelectProps, 'value' | 'onUpdate' | 'onChange'> {
interface SelectSingleProps<T extends string>
extends Omit<YTSelectProps<T>, 'value' | 'onUpdate' | 'onChange'> {
value?: string;
onChange?: (v?: string) => void;
}

export function SelectSingle(props: SelectSingleProps) {
export function SelectSingle<T extends string = string>(props: SelectSingleProps<T>) {
const {onChange, value, ...rest} = props;
const handleChange = React.useCallback(
(vals?: Array<string>) => {
Expand All @@ -93,7 +97,7 @@ export function SelectSingle(props: SelectSingleProps) {
);
}

SelectSingle.isEmpty = (value: SelectSingleProps['value']) => {
SelectSingle.isEmpty = (value: SelectSingleProps<string>['value']) => {
return !value;
};

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
.yt-open-query-buttons {
display: flex;
flex-wrap: nowrap;

&__query {
display: flex;
flex-wrap: nowrap;

&:not(:last-child) {
margin-right: 10px;
}
}

&__btn + &__btn {
margin-left: 1px;
}
}
107 changes: 107 additions & 0 deletions packages/ui/src/ui/containers/OpenQueryButtons/OpenQueryButtons.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
import React from 'react';
import cn from 'bem-cn-lite';
import {Button} from '@gravity-ui/uikit';
import {useDispatch, useSelector} from 'react-redux';

import Icon from '../../components/Icon/Icon';
import {YQLKitButton} from '../../containers/YQLKitButton/YQLKitButton';
import {QueryWidgetLazy} from '../../pages/query-tracker/QueryWidget/side-panel';
import {QueryEngine} from '../../pages/query-tracker/module/engines';
import {createQueryFromTablePath} from '../../pages/query-tracker/module/query/actions';
import {createNewQueryUrl} from '../../pages/query-tracker/utils/navigation';
import {getNavigationSqlService} from '../../store/selectors/settings/navigation';
import UIFactory from '../../UIFactory';
import {useSidePanel} from '../../hooks/use-side-panel';

import './OpenQueryButtons.scss';

const b = cn('yt-open-query-buttons');

export type OpenQueryButtonProps = {
className?: string;
path: string;
cluster: string;

autoOpen?: boolean;
};

export function OpenQueryButtons({className, path, cluster, autoOpen}: OpenQueryButtonProps) {
const dispatch = useDispatch();
const [panelMode, setPanelMode] = React.useState<'qt' | 'yqlkit' | undefined>();

const onOpenYqlKit = React.useCallback(() => setPanelMode('yqlkit'), []);
const onClose = React.useCallback(() => setPanelMode(undefined), []);

const {openWidget, closeWidget, widgetContent} = useSidePanel(panelMode + '_widget', {
renderContent({visible}) {
return panelMode === 'qt' ? (
<QueryWidgetLazy onClose={onClose} />
) : (
UIFactory.yqlWidgetSetup?.renderWidget({visible, onClose})
);
},
});

const {isQtKitEnabled, isYqlKitEnabled} = useSelector(getNavigationSqlService);

React.useEffect(() => {
if (panelMode === undefined) {
closeWidget();
return;
}

if (panelMode === 'qt') {
dispatch(createQueryFromTablePath(QueryEngine.YQL, cluster, path));
}
openWidget();
}, [panelMode, openWidget, closeWidget]);

const allowQtAutoOpen = autoOpen && isQtKitEnabled;

React.useEffect(() => {
if (autoOpen) {
setPanelMode(allowQtAutoOpen ? 'qt' : 'yqlkit');
}
}, [autoOpen, allowQtAutoOpen]);

return (
<div className={b(null, className)}>
{isQtKitEnabled && (
<div className={b('query')}>
<Button
onClick={() => {
setPanelMode(panelMode === 'qt' ? undefined : 'qt');
}}
pin="round-clear"
view="action"
className={b('btn')}
selected={panelMode === 'qt'}
disabled={panelMode === 'yqlkit'}
title="Open Queries widget"
>
QT Kit
</Button>
<Button
className={b('btn')}
pin="clear-round"
view="action"
href={createNewQueryUrl(cluster, QueryEngine.YQL, {path})}
target="_blank"
title="Open Queries page"
>
<Icon awesome="table" />
</Button>
</div>
)}
{isYqlKitEnabled && (
<YQLKitButton
disabled={panelMode === 'qt'}
opened={panelMode === 'yqlkit'}
onOpen={onOpenYqlKit}
onClose={onClose}
/>
)}
{widgetContent}
</div>
);
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
import React, {useEffect, useState} from 'react';
import {useDispatch, useSelector} from 'react-redux';
import block from 'bem-cn-lite';
import {SelectSingle} from '../../components/Select/Select';

import {KeysByType} from '../../../@types/types';
import {DescribedSettings} from '../../../shared/constants/settings-types';
import SelectFacade, {Item, SelectSingle, YTSelectProps} from '../../components/Select/Select';
import {setSettingByKey} from '../../store/actions/settings';
import {getSettingsData} from '../../store/selectors/settings/settings-base';

import './SettingsMenu.scss';

Expand Down Expand Up @@ -35,7 +41,80 @@ export const SettingsMenuSelect = (props: SettingsMenuSelectProps) => {
items={items}
onChange={(value) => props.setSetting(value)}
placeholder={props.placeholder}
width="max"
/>
</div>
);
};

type SettingMenuSelectByKeyProps<K extends KeysByType<DescribedSettings, string>> = {
settingKey: K;
options: Array<Item<DescribedSettings[K]>>;
description?: React.ReactNode;
};

export function SettingMenuSelectByKey<K extends KeysByType<DescribedSettings, string>>({
settingKey,
options,
description,
}: SettingMenuSelectByKeyProps<K>) {
const dispatch = useDispatch();
const value = useSelector(getSettingsData)[settingKey];

return (
<div className={b('settings-item', {select: true})}>
<SelectSingle
value={value}
items={options}
onChange={(v) => {
dispatch(setSettingByKey(settingKey, v as typeof value));
}}
width="max"
/>
{Boolean(description) && (
<div className="elements-page__settings-description elements-secondary-text">
{description}
</div>
)}
</div>
);
}

type SettingMenuMultiSelectByKeyProps<K extends KeysByType<DescribedSettings, Array<string>>> = {
settingKey: K;
options: Array<Item<DescribedSettings[K][number]>>;
description?: React.ReactNode;
};

export function SettingMenuMultiSelectByKey<
K extends KeysByType<DescribedSettings, Array<string>>,
>({
settingKey,
options,
description,
...rest
}: SettingMenuMultiSelectByKeyProps<K> &
Omit<YTSelectProps<string>, 'value' | 'items' | 'onChange' | 'onUpdate'>) {
const dispatch = useDispatch();
const value = useSelector(getSettingsData)[settingKey];

return (
<div className={b('settings-item', {select: true})}>
<SelectFacade
multiple
value={value}
items={options}
onChange={(v) => {
dispatch(setSettingByKey(settingKey, v as typeof value));
}}
width="max"
{...rest}
/>
{Boolean(description) && (
<div className="elements-page__settings-description elements-secondary-text">
{description}
</div>
)}
</div>
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ import ypath from '../../common/thor/ypath';
import {AGGREGATOR_RADIO_ITEMS} from '../../constants/operations/statistics';
import {NAMESPACES, SettingName} from '../../../shared/constants/settings';
import {getRecentPagesInfo} from '../../store/selectors/slideoutMenu';
import {getCurrentClusterNS} from '../../store/selectors/settings-ts';
import {getCurrentClusterNS} from '../../store/selectors/settings/settings-ts';
import SettingsMenuItem from '../../containers/SettingsMenu/SettingsMenuItem';
import SettingsMenuRadio from '../../containers/SettingsMenu/SettingsMenuRadio';
import SettingsMenuInput from '../SettingsMenu/SettingsMenuInput';
Expand Down Expand Up @@ -519,7 +519,7 @@ function useSettings(cluster: string, isAdmin: boolean): Array<SettingsPage> {
),

makePage(
'Query ACO',
'Queries',
undefined,
compact_([
makeItem(
Expand Down
18 changes: 18 additions & 0 deletions packages/ui/src/ui/containers/YQLKitButton/YQLKitButton.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import React from 'react';
import {useDispatch} from 'react-redux';

import {mergeScreen} from '../../store/actions/global';

import UIFactory, {YQLButtonProps} from '../../UIFactory';

export function YQLKitButton(props: YQLButtonProps) {
const dispatch = useDispatch();

React.useEffect(() => {
return () => {
dispatch(mergeScreen());
};
}, [dispatch]);

return <React.Fragment>{UIFactory.yqlWidgetSetup?.renderButton(props)}</React.Fragment>;
}
2 changes: 1 addition & 1 deletion packages/ui/src/ui/hocs/withSplit.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import ReactDOM from 'react-dom';
import {getDisplayName} from '../utils';
import {SPLIT_PANE_ID} from '../constants/index';

export default function withSplit<P>(Component: React.ComponentType<P>) {
export default function withSplit<P>(Component: React.ComponentType<P>): React.ComponentType<P> {
return class WithSplit extends React.Component<P> {
static displayName = `WithSplit(${getDisplayName(Component)})`;

Expand Down
Loading
Loading