Skip to content
Open
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
2 changes: 1 addition & 1 deletion packages/ui/src/ui/constants/dashboard2/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ export const defaultDashboardItems = {
x: 0,
y: 0,
},
data: {name: 'Navigation'},
data: {name: 'Navigation', show_navigation_input: true},
},
operations: {
layout: {
Expand Down
7 changes: 7 additions & 0 deletions packages/ui/src/ui/constants/navigation/map-node.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
export const attributesForNodeIcon = [
'treat_as_queue_consumer',
'treat_as_queue_producer',
'type',
'dynamic',
'sorted',
];
4 changes: 4 additions & 0 deletions packages/ui/src/ui/containers/PathEditor/PathEditor.scss
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,10 @@
@include reset-li();
@include ellipsis();

display: flex;
flex-direction: row;
align-items: center;

line-height: 30px;

width: 100%;
Expand Down
43 changes: 35 additions & 8 deletions packages/ui/src/ui/containers/PathEditor/PathEditor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,17 +7,17 @@ import debounce_ from 'lodash/debounce';
import find_ from 'lodash/find';
import isEmpty_ from 'lodash/isEmpty';

import {Popup, TextInput} from '@gravity-ui/uikit';
import {Button, Flex, Icon, Popup, TextInput} from '@gravity-ui/uikit';
import {ArrowRight} from '@gravity-ui/icons';

import thorYPath from '../../common/thor/ypath';

import Icon from '../../components/Icon/Icon';
import ErrorMessage from '../../components/ErrorMessage/ErrorMessage';
import {MapNodeIcon} from '../../components/MapNodeIcon/MapNodeIcon';

import {
filterByCurrentPath,
getCompletedPath,
getIconNameForType,
getNextSelectedIndex,
getPrevSelectedIndex,
} from '../../utils/navigation/path-editor';
Expand All @@ -38,6 +38,7 @@ interface Suggestion {
targetPathBroken?: boolean;
type?: string;
dynamic?: unknown;
attributes?: Record<string, unknown>;
}

type SuggestionFilter = (suggestions: Suggestion[]) => Suggestion[];
Expand Down Expand Up @@ -65,6 +66,7 @@ export interface PathEditorProps {
disabled?: boolean;
autoFocus?: boolean;
hasClear?: boolean;
hasConfirmButton?: boolean;
showErrors?: boolean;
customFilter?: SuggestionFilter;
cluster?: string;
Expand Down Expand Up @@ -96,6 +98,7 @@ export class PathEditor extends Component<PathEditorProps, PathEditorState> {
defaultPath: undefined,
disabled: false,
hasClear: false,
hasConfirmButton: false,
};

static getDerivedStateFromProps(props: PathEditorProps, state: PathEditorState) {
Expand Down Expand Up @@ -289,7 +292,7 @@ export class PathEditor extends Component<PathEditorProps, PathEditorState> {
}
};

renderInput() {
renderBaseInput() {
const {placeholder, autoFocus, hasClear, disabled} = this.props;
const {path} = this.state;

Expand All @@ -309,15 +312,39 @@ export class PathEditor extends Component<PathEditorProps, PathEditorState> {
);
}

renderInput() {
const {hasConfirmButton, onApply} = this.props;
const {path} = this.state;

if (!hasConfirmButton) {
Comment on lines 309 to +319
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion: Consider keyboard accessibility for the confirm button.

Support triggering the onApply action via keyboard events, such as the Enter key, when hasConfirmButton is true.

Suggested implementation:

    handleInputKeyDown = (event: React.KeyboardEvent<HTMLInputElement>) => {
        const {hasConfirmButton, onApply} = this.props;
        const {path} = this.state;
        if (hasConfirmButton && event.key === 'Enter' && typeof onApply === 'function') {
            onApply(path);
        }
    };

    renderInput() {
        const {hasConfirmButton, onApply} = this.props;
        const {path} = this.state;

        if (!hasConfirmButton) {
    renderBaseInput() {
        const {placeholder, autoFocus, hasClear, disabled, hasConfirmButton} = this.props;
        const {path} = this.state;

        return (
            <input
                type="text"
                value={path}
                placeholder={placeholder}
                autoFocus={autoFocus}
                disabled={disabled}
                onChange={this.handleInputChange}
                onKeyDown={hasConfirmButton ? this.handleInputKeyDown : undefined}
            />
        );
    }

return this.renderBaseInput();
}

return (
<Flex gap={2} height={'100%'} alignItems={'center'}>
{this.renderBaseInput()}
<Button
size={'s'}
view={'outlined'}
style={{height: '100%'}}
onClick={() => onApply?.(path)}
>
<Flex height={'100%'} alignItems={'center'}>
<Icon data={ArrowRight} size={'14'} />
</Flex>
</Button>
</Flex>
);
}

renderItem = (index: number, key: Key) => {
const {selectedIndex, actualSuggestions} = this.state;

const item = actualSuggestions[index];
const {type, dynamic} = item;
const iconType = type === 'table' && dynamic ? 'table_dynamic' : type;

const {attributes} = item;
const completedPath = getCompletedPath(item);
const isSelected = index === selectedIndex ? 'yes' : 'no';
const iconName = getIconNameForType(iconType, item.targetPathBroken);

const mouseDownHandler = (event: MouseEvent<HTMLDivElement>) => {
this.handleInputChange(completedPath);
Expand All @@ -336,7 +363,7 @@ export class PathEditor extends Component<PathEditorProps, PathEditorState> {
onMouseDown={mouseDownHandler}
className={b('item', {selected: isSelected})}
>
<Icon awesome={iconName} />
<MapNodeIcon node={{$attributes: attributes}} />

<span className={b('item-path')}>
{lastFragment ? `\u2026/${lastFragment}` : item.path}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
&__list {
height: 100%;
width: 100%;
overflow-y: auto;
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,22 +1,55 @@
import React from 'react';
import React, {FocusEvent} from 'react';
import {useSelector} from 'react-redux';
import {useHistory} from 'react-router';
import {Flex} from '@gravity-ui/uikit';

import {WidgetSkeleton} from '../../../../../../pages/dashboard2/Dashboard/components/WidgetSkeleton/WidgetSkeleton';
import PathEditor from '../../../../../../containers/PathEditor/PathEditor';

import {useNavigationWidget} from '../hooks/use-navigation-widget';
import type {NavigationWidgetProps} from '../types';

import {NavigationWidgetContentBase} from './NavigationWidgetContentBase';
import {getCluster} from '../../../../../../store/selectors/global';

export function NavigationWidgetContent(props: NavigationWidgetProps) {
const {type, items, isLoading, error} = useNavigationWidget(props);
const {type, items, isLoading, error, showNavigationInput} = useNavigationWidget(props);

return (
<>
<Flex width={'100%'} direction={'column'} gap={2}>
{showNavigationInput && <DashboardPathEditor />}
{isLoading ? (
<WidgetSkeleton itemHeight={30} />
) : (
<NavigationWidgetContentBase pathsType={type} items={items || []} error={error} />
)}
</>
</Flex>
);
}

function DashboardPathEditor() {
const history = useHistory();
const cluster = useSelector(getCluster);

const handleApply = (path: string) => {
const normalizedPath = path.endsWith('/') ? path.slice(0, -1) : path;

const navigationUrl = `/${cluster}/navigation?path=${normalizedPath}`;
history.push(navigationUrl);
};

const handleFocus = React.useCallback((event: FocusEvent<HTMLInputElement>) => {
event.target?.select();
}, []);

return (
<PathEditor
hasConfirmButton
autoFocus
defaultPath={''}
placeholder={'Enter the path to navigate...'}
onApply={handleApply}
onFocus={handleFocus}
/>
);
}
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ import {getFavouritePaths} from '../../../../../../store/selectors/favourites';
import {NavigationWidgetProps} from '../types';

export function useNavigationWidget(props: NavigationWidgetProps) {
const showNavigationInput = props.data?.show_navigation_input;

const type = useSelector((state: RootState) => getNavigationTypeFilter(state, props.id));
const cluster = useSelector(getCluster);

Expand All @@ -28,5 +30,6 @@ export function useNavigationWidget(props: NavigationWidgetProps) {
items,
isLoading: isLoading || isFetching,
error,
showNavigationInput: showNavigationInput === undefined || showNavigationInput === true,
};
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,10 @@ export function useNavigationSettings() {
placeholder: 'Navigation',
},
},
{
type: 'tumbler' as const,
name: 'show_navigation_input',
caption: 'Show navigation input',
},
];
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import {PluginWidgetProps} from '@gravity-ui/dashkit';

export type NavigationWidgetData = {
name?: string;
show_navigation_input?: boolean;
};

export type NavigationWidgetProps = PluginWidgetProps & {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import {
SET_TEXT_FILTER,
UPDATE_RESOURCE_USAGE,
} from '../../../../constants/navigation';
import {attributesForNodeIcon} from '../../../../constants/navigation/map-node';
import {
getFilteredNodes,
getLastSelected,
Expand Down Expand Up @@ -72,6 +73,7 @@ function getList(path, transaction, cluster) {
'treat_as_queue_consumer',
'treat_as_queue_producer',
'path',
...attributesForNodeIcon,
...(UIFactory.getNavigationMapNodeSettings()?.additionalAttributes || []),
],
path,
Expand Down
10 changes: 2 additions & 8 deletions packages/ui/src/ui/store/api/dashboard2/navigation/paths.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,14 @@ import {BaseQueryApi} from '@reduxjs/toolkit/query';
import map_ from 'lodash/map';

import {ytApiV3} from '../../../../rum/rum-wrap-api';
import {attributesForNodeIcon} from '../../../../constants/navigation/map-node';

function makePathsAttributesRequests(paths: string[]) {
return map_(paths, (path) => ({
command: 'get' as const,
parameters: {
path: `${path}/@`,
attributes: [
'path',
'treat_as_queue_consumer',
'treat_as_queue_producer',
'type',
'dynamic',
'sorted',
],
attributes: ['path', ...attributesForNodeIcon],
},
}));
}
Expand Down
5 changes: 3 additions & 2 deletions packages/ui/src/ui/utils/navigation/path-editor.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,19 +8,20 @@ import ypath from '../../common/thor/ypath';
import CancelHelper from '../cancel-helper';
import {YTApiId, ytApiV3Id} from '../../rum/rum-wrap-api';
import {getClusterConfigByName, getClusterProxy} from '../../store/selectors/global';
import {attributesForNodeIcon} from '../../constants/navigation/map-node';

export const pathEditorRequests = new CancelHelper();

function prepareSuggestions(initialPath, parentPath, children) {
let suggestions = map_(children, (child) => {
const prepared = {};

prepared.parentPath = parentPath;
prepared.childPath = '/' + unipika.decode(ypath.getValue(child));
prepared.path = prepared.parentPath + prepared.childPath;
prepared.type = ypath.getValue(child, '/@type');
prepared.dynamic = ypath.getValue(child, '/@dynamic');
prepared.targetPathBroken = ypath.getValue(child, '/@broken');
prepared.attributes = ypath.getAttributes(child);

return prepared;
});
Expand All @@ -38,7 +39,7 @@ export function loadSuggestions({path, customFilter, cluster}) {
const parentPath = preparePath(path);

const apiSetup = {
parameters: {path: parentPath, attributes: ['type', 'dynamic']},
parameters: {path: parentPath, attributes: [...attributesForNodeIcon]},
cancellation: pathEditorRequests.saveCancelToken,
};

Expand Down
Loading