mirror of
https://github.com/hyperdxio/hyperdx
synced 2026-04-21 13:37:15 +00:00
feat: Enhance data source select with context-aware icons and inline actions (#1948)
## Summary Closes [HDX-3784](https://linear.app/clickhouse/issue/HDX-3784/increase-discoverability-of-trace-sources) - **Context-aware icons for all source kinds**: The data source dropdown now shows icons for every source type — `IconLogs` for logs, `IconConnection` for traces, `IconDeviceLaptop` for sessions, and `IconChartLine` for metrics. Falls back to `IconStack` when no source is selected. - **Inline source actions**: "Create New Source" and "Edit Sources" actions are now part of the dropdown itself under an "Actions" group with a labeled separator, replacing the separate gear icon menu (`SourceEditMenu`). - **Dependency update**: Updated `@tabler/icons-react` from v3.5.0 to v3.40.0 to get `IconConnection`. - **Fix: source management regression when `HDX_LOCAL_DEFAULT_SOURCES` is set**: Before this PR, there were two ways to create/edit sources: (1) options inside the dropdown, which were hidden when `HDX_LOCAL_DEFAULT_SOURCES` is set, and (2) a gear icon button next to the dropdown, which was always visible. This PR removed the gear icon and kept only the dropdown options, but they were still configured to hide when `HDX_LOCAL_DEFAULT_SOURCES` is set — leaving users with no way to manage sources. Fixed by removing that guard so the dropdown options always appear. <img width="1236" height="492" alt="image" src="https://github.com/user-attachments/assets/6999626b-685b-4037-a003-b09018cfbadf" /> <img width="426" height="240" alt="Screenshot 2026-03-20 at 17 49 30" src="https://github.com/user-attachments/assets/28aaef44-7574-4c54-b721-b2a3a79b3507" /> ## Changes - `packages/app/src/components/SourceSelect.tsx` -- Dynamic left icon based on selected source kind (all 4 kinds: log, trace, session, metric), `onEdit` prop, grouped action items with icons, `renderOption` for source kind and action item icons. Removed `hasLocalDefaultSources` guard so source management actions are always available. - `packages/app/src/components/SelectControlled.tsx` -- Added `onEdit` callback support, fixed `selected` check to handle grouped data. - `packages/app/src/DBSearchPage.tsx` -- Removed `SourceEditMenu` component, added `onEditSources` callback, wired `onEdit` to `SourceSelectControlled`. - `packages/app/styles/SourceSelectControlled.module.scss` -- Group label separator styling with semantic `--color-border` token. - `packages/app/package.json` -- Updated `@tabler/icons-react` to `^3.39.0`. ## Test plan - [ ] Select a log source and verify `IconLogs` appears as the left icon - [ ] Select a trace source and verify `IconConnection` appears as the left icon - [ ] Select a session source and verify `IconDeviceLaptop` appears as the left icon - [ ] Select a metric source and verify `IconChartLine` appears as the left icon - [ ] Verify each source in the dropdown shows its corresponding kind icon - [ ] Open the dropdown and verify "Create New Source" and "Edit Sources" appear under the "Actions" group with icons - [ ] Click "Create New Source" and verify the modal opens - [ ] Click "Edit Sources" and verify navigation to edit (local mode: modal, cloud mode: /team) - [ ] Verify the gear icon menu is no longer present next to the select - [ ] **With `NEXT_PUBLIC_HDX_LOCAL_DEFAULT_SOURCES` set**: verify "Create New Source" and "Edit Sources" still appear in the dropdown and work correctly
This commit is contained in:
parent
e1cf4bca56
commit
3d15b3de93
10 changed files with 197 additions and 157 deletions
5
.changeset/enhance-data-source-select.md
Normal file
5
.changeset/enhance-data-source-select.md
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
"@hyperdx/app": patch
|
||||
---
|
||||
|
||||
feat: Enhance data source select with context-aware icons and inline actions
|
||||
|
|
@ -42,7 +42,7 @@
|
|||
"@mantine/notifications": "^7.17.8",
|
||||
"@mantine/spotlight": "^7.17.8",
|
||||
"@microsoft/fetch-event-source": "^2.0.1",
|
||||
"@tabler/icons-react": "^3.5.0",
|
||||
"@tabler/icons-react": "^3.39.0",
|
||||
"@tanstack/react-query": "^5.56.2",
|
||||
"@tanstack/react-query-devtools": "^5.56.2",
|
||||
"@tanstack/react-table": "^8.7.9",
|
||||
|
|
|
|||
|
|
@ -11,7 +11,6 @@ import {
|
|||
} from 'react';
|
||||
import dynamic from 'next/dynamic';
|
||||
import Head from 'next/head';
|
||||
import Link from 'next/link';
|
||||
import router from 'next/router';
|
||||
import {
|
||||
parseAsBoolean,
|
||||
|
|
@ -51,7 +50,6 @@ import {
|
|||
Flex,
|
||||
Grid,
|
||||
Group,
|
||||
Menu,
|
||||
Modal,
|
||||
Paper,
|
||||
Select,
|
||||
|
|
@ -67,10 +65,8 @@ import {
|
|||
import { notifications } from '@mantine/notifications';
|
||||
import {
|
||||
IconBolt,
|
||||
IconCirclePlus,
|
||||
IconPlayerPlay,
|
||||
IconPlus,
|
||||
IconSettings,
|
||||
IconTags,
|
||||
IconX,
|
||||
} from '@tabler/icons-react';
|
||||
|
|
@ -191,59 +187,6 @@ export function getDefaultSourceId(
|
|||
return sources[0].id;
|
||||
}
|
||||
|
||||
function SourceEditMenu({
|
||||
setModalOpen,
|
||||
setModelFormExpanded,
|
||||
}: {
|
||||
setModalOpen: (val: SetStateAction<boolean>) => void;
|
||||
setModelFormExpanded: (val: SetStateAction<boolean>) => void;
|
||||
}) {
|
||||
return (
|
||||
<Menu withArrow position="bottom-start">
|
||||
<Menu.Target>
|
||||
<ActionIcon
|
||||
data-testid="source-settings-menu"
|
||||
variant="subtle"
|
||||
size="sm"
|
||||
title="Edit Source"
|
||||
>
|
||||
<Text size="xs">
|
||||
<IconSettings size={14} />
|
||||
</Text>
|
||||
</ActionIcon>
|
||||
</Menu.Target>
|
||||
<Menu.Dropdown>
|
||||
<Menu.Label>Sources</Menu.Label>
|
||||
<Menu.Item
|
||||
data-testid="create-new-source-menu-item"
|
||||
leftSection={<IconCirclePlus size={14} />}
|
||||
onClick={() => setModalOpen(true)}
|
||||
>
|
||||
Create New Source
|
||||
</Menu.Item>
|
||||
{IS_LOCAL_MODE ? (
|
||||
<Menu.Item
|
||||
data-testid="edit-sources-menu-item"
|
||||
leftSection={<IconSettings size={14} />}
|
||||
onClick={() => setModelFormExpanded(v => !v)}
|
||||
>
|
||||
Edit Source
|
||||
</Menu.Item>
|
||||
) : (
|
||||
<Menu.Item
|
||||
data-testid="edit-sources-menu-item"
|
||||
leftSection={<IconSettings size={14} />}
|
||||
component={Link}
|
||||
href="/team"
|
||||
>
|
||||
Edit Sources
|
||||
</Menu.Item>
|
||||
)}
|
||||
</Menu.Dropdown>
|
||||
</Menu>
|
||||
);
|
||||
}
|
||||
|
||||
function SourceEditModal({
|
||||
opened,
|
||||
onClose,
|
||||
|
|
@ -1568,6 +1511,14 @@ function DBSearchPage() {
|
|||
setModelFormExpanded(false);
|
||||
}, [setModelFormExpanded]);
|
||||
|
||||
const onEditSources = useCallback(() => {
|
||||
if (IS_LOCAL_MODE) {
|
||||
setModelFormExpanded(v => !v);
|
||||
} else {
|
||||
router.push('/team');
|
||||
}
|
||||
}, [setModelFormExpanded]);
|
||||
|
||||
const setNewSourceModalClosed = useCallback(
|
||||
() => setNewSourceModalOpened(false),
|
||||
[setNewSourceModalOpened],
|
||||
|
|
@ -1609,22 +1560,18 @@ function DBSearchPage() {
|
|||
>
|
||||
{/* <DevTool control={control} /> */}
|
||||
<Flex gap="sm" px="sm" pt="sm" wrap="nowrap">
|
||||
<Group gap="4px" wrap="nowrap" style={{ minWidth: 150 }}>
|
||||
<SourceSelectControlled
|
||||
key={`${savedSearchId}`}
|
||||
size="xs"
|
||||
control={control}
|
||||
name="source"
|
||||
onCreate={openNewSourceModal}
|
||||
allowedSourceKinds={ALLOWED_SOURCE_KINDS}
|
||||
data-testid="source-selector"
|
||||
sourceSchemaPreview={sourceSchemaPreview}
|
||||
/>
|
||||
<SourceEditMenu
|
||||
setModalOpen={setNewSourceModalOpened}
|
||||
setModelFormExpanded={setModelFormExpanded}
|
||||
/>
|
||||
</Group>
|
||||
<SourceSelectControlled
|
||||
key={`${savedSearchId}`}
|
||||
size="xs"
|
||||
control={control}
|
||||
name="source"
|
||||
onCreate={openNewSourceModal}
|
||||
onEdit={onEditSources}
|
||||
allowedSourceKinds={ALLOWED_SOURCE_KINDS}
|
||||
data-testid="source-selector"
|
||||
sourceSchemaPreview={sourceSchemaPreview}
|
||||
style={{ minWidth: 150 }}
|
||||
/>
|
||||
<Box style={{ flex: '1 1 0%', minWidth: 100 }}>
|
||||
<SQLInlineEditorControlled
|
||||
tableConnection={inputSourceTableConnection}
|
||||
|
|
|
|||
|
|
@ -2,9 +2,15 @@ import { useCallback } from 'react';
|
|||
import { useController, UseControllerProps } from 'react-hook-form';
|
||||
import { Select, SelectProps } from '@mantine/core';
|
||||
|
||||
export enum SelectControlledSpecialValues {
|
||||
CreateNewValue = '_create_new_value',
|
||||
EditValue = '_edit_value',
|
||||
}
|
||||
|
||||
export type SelectControlledProps = SelectProps &
|
||||
UseControllerProps<any> & {
|
||||
onCreate?: () => void;
|
||||
onEdit?: () => void;
|
||||
allowDeselect?: boolean;
|
||||
};
|
||||
|
||||
|
|
@ -19,35 +25,47 @@ export default function SelectControlled(props: SelectControlledProps) {
|
|||
},
|
||||
fieldState,
|
||||
} = useController(props);
|
||||
const { onCreate, allowDeselect = true, ...restProps } = props;
|
||||
const { onCreate, onEdit, allowDeselect = true, ...restProps } = props;
|
||||
|
||||
// This is needed as mantine does not clear the select
|
||||
// if the value is not in the data after
|
||||
// if it was previously in the data (ex. data was deleted)
|
||||
const selected = props.data?.find(d =>
|
||||
typeof d === 'string'
|
||||
? d === fieldValue
|
||||
: 'value' in d
|
||||
? d.value === fieldValue
|
||||
: true,
|
||||
);
|
||||
// Mantine does not clear the select if the value is removed from data
|
||||
// after it was previously present (ex. data was deleted)
|
||||
const selected = props.data?.some(d => {
|
||||
if (typeof d === 'string') return d === fieldValue;
|
||||
if ('value' in d) return d.value === fieldValue;
|
||||
if ('items' in d) {
|
||||
return d.items.some(item =>
|
||||
typeof item === 'string'
|
||||
? item === fieldValue
|
||||
: item.value === fieldValue,
|
||||
);
|
||||
}
|
||||
return false;
|
||||
});
|
||||
|
||||
const onChange = useCallback(
|
||||
(value: string | null) => {
|
||||
if (value === '_create_new_value' && onCreate != null) {
|
||||
if (
|
||||
value === SelectControlledSpecialValues.CreateNewValue &&
|
||||
onCreate != null
|
||||
) {
|
||||
onCreate();
|
||||
} else if (
|
||||
value === SelectControlledSpecialValues.EditValue &&
|
||||
onEdit != null
|
||||
) {
|
||||
onEdit();
|
||||
} else if (value !== null || allowDeselect) {
|
||||
fieldOnChange(value);
|
||||
}
|
||||
},
|
||||
[fieldOnChange, onCreate, allowDeselect],
|
||||
[fieldOnChange, onCreate, onEdit, allowDeselect],
|
||||
);
|
||||
|
||||
return (
|
||||
<Select
|
||||
{...restProps}
|
||||
error={fieldState.error?.message}
|
||||
value={selected == null ? null : fieldValue}
|
||||
value={selected ? fieldValue : null}
|
||||
onChange={onChange}
|
||||
onBlur={fieldOnBlur}
|
||||
name={fieldName}
|
||||
|
|
|
|||
|
|
@ -1,12 +1,26 @@
|
|||
import { memo, useMemo } from 'react';
|
||||
import { UseControllerProps } from 'react-hook-form';
|
||||
import { memo, useCallback, useMemo } from 'react';
|
||||
import { UseControllerProps, useWatch } from 'react-hook-form';
|
||||
import { SourceKind } from '@hyperdx/common-utils/dist/types';
|
||||
import { SelectProps, UnstyledButton } from '@mantine/core';
|
||||
import { ComboboxChevron } from '@mantine/core';
|
||||
import { IconStack } from '@tabler/icons-react';
|
||||
import {
|
||||
ComboboxChevron,
|
||||
ComboboxItem,
|
||||
Group,
|
||||
SelectProps,
|
||||
UnstyledButton,
|
||||
} from '@mantine/core';
|
||||
import {
|
||||
IconChartLine,
|
||||
IconConnection,
|
||||
IconDeviceLaptop,
|
||||
IconLogs,
|
||||
IconPlus,
|
||||
IconSettings,
|
||||
IconStack,
|
||||
} from '@tabler/icons-react';
|
||||
|
||||
import SelectControlled from '@/components/SelectControlled';
|
||||
import { HDX_LOCAL_DEFAULT_SOURCES } from '@/config';
|
||||
import SelectControlled, {
|
||||
SelectControlledSpecialValues,
|
||||
} from '@/components/SelectControlled';
|
||||
import { useSources } from '@/source';
|
||||
|
||||
import styles from '../../styles/SourceSelectControlled.module.scss';
|
||||
|
|
@ -43,9 +57,22 @@ export const SourceSelectRightSection = ({
|
|||
};
|
||||
};
|
||||
|
||||
const SOURCE_KIND_ICONS: Record<string, React.ReactNode> = {
|
||||
[SourceKind.Log]: <IconLogs size={16} />,
|
||||
[SourceKind.Trace]: <IconConnection size={16} />,
|
||||
[SourceKind.Session]: <IconDeviceLaptop size={16} />,
|
||||
[SourceKind.Metric]: <IconChartLine size={16} />,
|
||||
};
|
||||
|
||||
const OPTION_ICONS: Record<string, React.ReactNode> = {
|
||||
[SelectControlledSpecialValues.CreateNewValue]: <IconPlus size={14} />,
|
||||
[SelectControlledSpecialValues.EditValue]: <IconSettings size={14} />,
|
||||
};
|
||||
|
||||
function SourceSelectControlledComponent({
|
||||
size,
|
||||
onCreate,
|
||||
onEdit,
|
||||
allowedSourceKinds,
|
||||
connectionId,
|
||||
comboboxProps,
|
||||
|
|
@ -54,41 +81,86 @@ function SourceSelectControlledComponent({
|
|||
}: {
|
||||
size?: string;
|
||||
onCreate?: () => void;
|
||||
onEdit?: () => void;
|
||||
allowedSourceKinds?: SourceKind[];
|
||||
connectionId?: string;
|
||||
sourceSchemaPreview?: React.ReactNode;
|
||||
} & UseControllerProps<any> &
|
||||
SelectProps) {
|
||||
const { data } = useSources();
|
||||
const hasLocalDefaultSources = !!HDX_LOCAL_DEFAULT_SOURCES;
|
||||
const selectedSourceId = useWatch({
|
||||
control: props.control,
|
||||
name: props.name,
|
||||
});
|
||||
|
||||
const values = useMemo(
|
||||
() => [
|
||||
...(
|
||||
data
|
||||
?.filter(
|
||||
source =>
|
||||
(!allowedSourceKinds ||
|
||||
allowedSourceKinds.includes(source.kind)) &&
|
||||
(!connectionId || source.connection === connectionId),
|
||||
)
|
||||
.map(d => ({
|
||||
value: d.id,
|
||||
label: d.name,
|
||||
})) ?? []
|
||||
).sort((a, b) => a.label.localeCompare(b.label)),
|
||||
...(onCreate && !hasLocalDefaultSources
|
||||
? [
|
||||
{
|
||||
value: '_create_new_value',
|
||||
label: 'Create New Source',
|
||||
},
|
||||
]
|
||||
: []),
|
||||
],
|
||||
[data, onCreate, allowedSourceKinds, connectionId, hasLocalDefaultSources],
|
||||
const selectedSourceKind = useMemo(
|
||||
() => data?.find(s => s.id === selectedSourceId)?.kind,
|
||||
[data, selectedSourceId],
|
||||
);
|
||||
|
||||
const leftIcon = SOURCE_KIND_ICONS[selectedSourceKind ?? ''] ?? (
|
||||
<IconStack size={16} />
|
||||
);
|
||||
|
||||
const sourceKindMap = useMemo(() => {
|
||||
const map = new Map<string, SourceKind>();
|
||||
data?.forEach(s => map.set(s.id, s.kind));
|
||||
return map;
|
||||
}, [data]);
|
||||
|
||||
const renderOption = useCallback(
|
||||
({ option }: { option: ComboboxItem }) => {
|
||||
const icon =
|
||||
OPTION_ICONS[option.value] ??
|
||||
SOURCE_KIND_ICONS[sourceKindMap.get(option.value) ?? ''];
|
||||
if (!icon) return option.label;
|
||||
return (
|
||||
<Group gap="xs" wrap="nowrap">
|
||||
{icon}
|
||||
{option.label}
|
||||
</Group>
|
||||
);
|
||||
},
|
||||
[sourceKindMap],
|
||||
);
|
||||
|
||||
const hasActions = !!onCreate || !!onEdit;
|
||||
|
||||
const values = useMemo(() => {
|
||||
const sourceItems = (
|
||||
data
|
||||
?.filter(
|
||||
source =>
|
||||
(!allowedSourceKinds || allowedSourceKinds.includes(source.kind)) &&
|
||||
(!connectionId || source.connection === connectionId),
|
||||
)
|
||||
.map(d => ({
|
||||
value: d.id,
|
||||
label: d.name,
|
||||
})) ?? []
|
||||
).sort((a, b) => a.label.localeCompare(b.label));
|
||||
|
||||
if (!hasActions) {
|
||||
return sourceItems;
|
||||
}
|
||||
|
||||
const actionItems: { value: string; label: string }[] = [];
|
||||
if (onCreate) {
|
||||
actionItems.push({
|
||||
value: SelectControlledSpecialValues.CreateNewValue,
|
||||
label: 'Create New Source',
|
||||
});
|
||||
}
|
||||
if (onEdit) {
|
||||
actionItems.push({
|
||||
value: SelectControlledSpecialValues.EditValue,
|
||||
label: 'Edit Sources',
|
||||
});
|
||||
}
|
||||
|
||||
return [...sourceItems, { group: 'Actions', items: actionItems }];
|
||||
}, [data, onCreate, onEdit, allowedSourceKinds, connectionId, hasActions]);
|
||||
|
||||
const rightSectionProps = SourceSelectRightSection({ sourceSchemaPreview });
|
||||
|
||||
return (
|
||||
|
|
@ -96,12 +168,15 @@ function SourceSelectControlledComponent({
|
|||
{...props}
|
||||
data={values}
|
||||
comboboxProps={{ withinPortal: false, ...comboboxProps }}
|
||||
classNames={{ groupLabel: styles.groupLabel }}
|
||||
renderOption={renderOption}
|
||||
searchable
|
||||
placeholder="Data Source"
|
||||
leftSection={<IconStack size={16} />}
|
||||
leftSection={leftIcon}
|
||||
maxDropdownHeight={280}
|
||||
size={size}
|
||||
onCreate={onCreate}
|
||||
onEdit={onEdit}
|
||||
{...rightSectionProps}
|
||||
/>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -10,3 +10,9 @@
|
|||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
|
||||
.groupLabel {
|
||||
&::after {
|
||||
border-color: var(--color-border);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -102,15 +102,13 @@ test.describe('Sources Functionality', { tag: ['@sources'] }, () => {
|
|||
await searchPage.goto();
|
||||
});
|
||||
|
||||
test('should open source settings menu', async () => {
|
||||
// Click source settings menu
|
||||
await searchPage.sourceMenu.click();
|
||||
test('should show source actions in dropdown', async () => {
|
||||
// Open source selector dropdown
|
||||
await searchPage.sourceDropdown.click();
|
||||
|
||||
// Verify create new source menu item is visible
|
||||
// Verify action items are visible in the dropdown
|
||||
await expect(searchPage.createNewSourceItem).toBeVisible();
|
||||
|
||||
// Verify edit source menu items are visible
|
||||
await expect(searchPage.editSourceMenuItem).toBeVisible();
|
||||
await expect(searchPage.editSourcesItem).toBeVisible();
|
||||
});
|
||||
|
||||
test(
|
||||
|
|
@ -144,7 +142,7 @@ test.describe('Sources Functionality', { tag: ['@sources'] }, () => {
|
|||
);
|
||||
|
||||
test('should show proper fields when creating a new source', async () => {
|
||||
await searchPage.sourceMenu.click();
|
||||
await searchPage.sourceDropdown.click();
|
||||
await searchPage.createNewSourceItem.click();
|
||||
// for each source type (log, trace, session, metric), verify the correct fields are shown
|
||||
for (const sourceData of allSourcesData) {
|
||||
|
|
|
|||
|
|
@ -251,7 +251,7 @@ async function globalSetup(_config: FullConfig) {
|
|||
await page.goto('/search', { timeout: PAGE_LOAD_TIMEOUT_MS });
|
||||
|
||||
// Wait for source selector to be ready (indicates sources are loaded)
|
||||
await page.waitForSelector('[data-testid="source-settings-menu"]', {
|
||||
await page.waitForSelector('[data-testid="source-selector"]', {
|
||||
state: 'visible',
|
||||
timeout: SOURCE_SELECTOR_TIMEOUT_MS,
|
||||
});
|
||||
|
|
|
|||
|
|
@ -25,8 +25,6 @@ export class SearchPage {
|
|||
readonly savedSearchModal: SavedSearchModalComponent;
|
||||
readonly alertModal: SearchPageAlertModalComponent;
|
||||
readonly defaultTimeout: number = 3000;
|
||||
readonly editSourceMenuItem: Locator;
|
||||
|
||||
private readonly alertsButtonLocator: Locator;
|
||||
|
||||
// Page-specific locators
|
||||
|
|
@ -39,8 +37,6 @@ export class SearchPage {
|
|||
private readonly luceneTab: Locator;
|
||||
private readonly sqlTab: Locator;
|
||||
private readonly sourceSelector: Locator;
|
||||
private readonly sourceSettingsMenu: Locator;
|
||||
private readonly createNewSourceMenuItem: Locator;
|
||||
|
||||
constructor(page: Page, defaultTimeout: number = 3000) {
|
||||
this.page = page;
|
||||
|
|
@ -71,19 +67,14 @@ export class SearchPage {
|
|||
this.sqlTab = page.getByRole('option', { name: 'SQL', exact: true });
|
||||
this.luceneTab = page.getByRole('option', { name: 'Lucene', exact: true });
|
||||
this.sourceSelector = page.getByTestId('source-selector');
|
||||
this.sourceSettingsMenu = page.getByTestId('source-settings-menu');
|
||||
this.editSourceMenuItem = page.getByTestId('edit-sources-menu-item');
|
||||
this.createNewSourceMenuItem = page.getByTestId(
|
||||
'create-new-source-menu-item',
|
||||
);
|
||||
}
|
||||
|
||||
get sourceMenu() {
|
||||
return this.sourceSettingsMenu;
|
||||
}
|
||||
|
||||
get createNewSourceItem() {
|
||||
return this.createNewSourceMenuItem;
|
||||
return this.page.getByRole('option', { name: 'Create New Source' });
|
||||
}
|
||||
|
||||
get editSourcesItem() {
|
||||
return this.page.getByRole('option', { name: 'Edit Sources' });
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -103,8 +94,8 @@ export class SearchPage {
|
|||
}
|
||||
|
||||
async openEditSourceModal() {
|
||||
await this.sourceSettingsMenu.click();
|
||||
await this.editSourceMenuItem.click();
|
||||
await this.sourceSelector.click();
|
||||
await this.editSourcesItem.click();
|
||||
}
|
||||
|
||||
async sourceModalShowOptionalFields() {
|
||||
|
|
|
|||
20
yarn.lock
20
yarn.lock
|
|
@ -4340,7 +4340,7 @@ __metadata:
|
|||
"@storybook/addon-themes": "npm:^10.1.4"
|
||||
"@storybook/nextjs": "npm:^10.1.4"
|
||||
"@storybook/react": "npm:^10.1.4"
|
||||
"@tabler/icons-react": "npm:^3.5.0"
|
||||
"@tabler/icons-react": "npm:^3.39.0"
|
||||
"@tanstack/react-query": "npm:^5.56.2"
|
||||
"@tanstack/react-query-devtools": "npm:^5.56.2"
|
||||
"@tanstack/react-table": "npm:^8.7.9"
|
||||
|
|
@ -8798,21 +8798,21 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@tabler/icons-react@npm:^3.5.0":
|
||||
version: 3.5.0
|
||||
resolution: "@tabler/icons-react@npm:3.5.0"
|
||||
"@tabler/icons-react@npm:^3.39.0":
|
||||
version: 3.40.0
|
||||
resolution: "@tabler/icons-react@npm:3.40.0"
|
||||
dependencies:
|
||||
"@tabler/icons": "npm:3.5.0"
|
||||
"@tabler/icons": "npm:3.40.0"
|
||||
peerDependencies:
|
||||
react: ">= 16"
|
||||
checksum: 10c0/9669d26e2ab4654d3d1c124a5ffd42c276d5a7ebc83b81ecb200e1f89dc3e9271557cf6eccc78bdd0ebdd0536fa4cd566e2b638ab4407e9415457c28ef05786e
|
||||
checksum: 10c0/a6985c9e8d1986c14594f5bc7007d2398b7b1efd2202304a24e23eb1fa6a48a23f9a6aaa3c5626781990cc0f0a46773c0a0cb747b6984dde26f03ba4ebd1d9cf
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@tabler/icons@npm:3.5.0":
|
||||
version: 3.5.0
|
||||
resolution: "@tabler/icons@npm:3.5.0"
|
||||
checksum: 10c0/0d6b15266e116dcf56bca417d013f1027b57404e5ed7f9a2a14e216eabdd04980ff971129491b666ffdc11953107c0b4540033c92d611fa74e127fc9d443fc4f
|
||||
"@tabler/icons@npm:3.40.0":
|
||||
version: 3.40.0
|
||||
resolution: "@tabler/icons@npm:3.40.0"
|
||||
checksum: 10c0/d335323695dff9218b486bb84a79893f86d5c284dc022c86bfdcfc550605c48ddffe72161c93f6fcab47ba6d828311c101fc5e306fb698b5b5104cf6250ed314
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue