Compare commits

...

2 Commits

Author SHA1 Message Date
Ashwin Bhatkal
65fc5a9cbb ci(e2e): stop label events from failing the e2e check (#13167)
Some checks are pending
build-staging / staging (push) Blocked by required conditions
build-staging / prepare (push) Waiting to run
build-staging / js-build (push) Blocked by required conditions
build-staging / go-build (push) Blocked by required conditions
cacheci / tests (push) Waiting to run
Release Drafter / update_release_draft (push) Waiting to run
#### Description

- Adding any label to a PR left failed `e2e` checks next to the real
result. A `pull_request_target` run failed as unauthorized, and the
`labeled` run cancelled the in-flight e2e run, whose `e2e` job then
reported failure.
- e2eci now runs only on `pull_request` opened / synchronize / reopened,
so each commit gets its `e2e` check from a single run.
- Labels are read live in the `changes` job. The new `e2elabelci`
workflow re-runs the commit's e2eci run when `safe-to-test` or
`safe-to-e2e` is added, so the labels still start e2e right away without
adding a second `e2e` check. It never checks out PR code.
- Fork and dependabot PRs still need `safe-to-test`, and run only on a
re-run, because `postci` strips the label on push and a first attempt
could read it before that lands.
- Fork e2e now runs the PR's code under `pull_request` with no secrets.
Under `pull_request_target`, checkout defaulted to the base branch, so
fork runs tested `main` instead of the PR.

---------

Co-authored-by: Aswin <aswin.km@signoz.io>
Co-authored-by: Aswin K.M. <astrobot7@gmail.com>
2026-10-19 15:29:43 +00:00
Aditya Singh
03bdeb9ba4 feat(saved-views): turn on saved views and the bottom strip (#13125)
Some checks failed
build-staging / staging (push) Has been cancelled
build-staging / prepare (push) Has been cancelled
build-staging / js-build (push) Has been cancelled
build-staging / go-build (push) Has been cancelled
cacheci / tests (push) Has been cancelled
Release Drafter / update_release_draft (push) Has been cancelled
#### Description
- This PR turns on saved views and the bottom strip for all users. The
`SAVED_VIEW_ENABLED` flag now always reads as on.
- The flag code stays in place. The cleanup in
https://github.com/SigNoz/events-pod/issues/77 removes it.
- The old saved views URLs now redirect to their explorer through the
existing old-routes redirects:
  - `/logs/saved-views` to `/logs/logs-explorer`
  - `/traces/saved-views` to `/traces-explorer`
  - `/metrics-explorer/views` to `/metrics-explorer/explorer`
  - `/meter/explorer/views` to `/meter/explorer`

#### Issues closed by this PR

Part of https://github.com/SigNoz/events-pod/issues/57

#### Additional Information
- Merge this PR after #13022, #13028 and #13027. Without them, the
explorers show a placeholder views panel next to the old bottom bar.
- The flag also hides the Pylon chat bubble and the chat support
gateway, because Support moves into the bottom strip.
2026-10-19 13:52:25 +00:00
13 changed files with 97 additions and 334 deletions

View File

@@ -2,25 +2,13 @@ name: e2eci
on:
pull_request:
types:
- opened
- synchronize
- reopened
- labeled
pull_request_target:
types:
- labeled
merge_group:
types:
- checks_requested
concurrency:
group: e2eci-${{ github.event_name }}-${{ github.event.pull_request.number || github.ref }}
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true
jobs:
changes:
if: github.event_name != 'merge_group'
runs-on: ubuntu-latest
outputs:
e2e: ${{ steps.changes.outputs.e2e }}
@@ -30,17 +18,21 @@ jobs:
env:
GH_TOKEN: ${{ github.token }}
PR_NUMBER: ${{ github.event.pull_request.number }}
LABELED: ${{ contains(github.event.pull_request.labels.*.name, 'safe-to-e2e') }}
FORCE: ${{ contains(github.event.pull_request.labels.*.name, 'safe-to-e2e') }}
run: |
files=$(gh api "repos/$GITHUB_REPOSITORY/pulls/$PR_NUMBER/files" --paginate --jq '.[].filename')
if [ "$LABELED" = true ] || grep -qE '^(frontend|tests/e2e)/' <<< "$files"; then
CHANGED_FILES=$(gh api "repos/$GITHUB_REPOSITORY/pulls/$PR_NUMBER/files" --paginate --jq '.[].filename | select(startswith("tests/integration/") | not)')
PATTERN='^(frontend/|tests/|\.github/workflows/e2eci\.yaml$|cmd/enterprise/Dockerfile\.with-web\.integration$)'
if [ "$FORCE" = "true" ]; then
echo "e2e=true" >> "$GITHUB_OUTPUT"
elif printf '%s\n' "$CHANGED_FILES" | grep -qE "$PATTERN"; then
echo "e2e=true" >> "$GITHUB_OUTPUT"
else
echo "e2e=false" >> "$GITHUB_OUTPUT"
fi
fmtlint:
needs: changes
if: |
((github.event_name == 'pull_request' && ! github.event.pull_request.head.repo.fork && github.event.pull_request.user.login != 'dependabot[bot]' && ! contains(github.event.pull_request.labels.*.name, 'safe-to-test')) ||
(github.event_name == 'pull_request_target' && contains(github.event.pull_request.labels.*.name, 'safe-to-test'))) && needs.changes.outputs.e2e == 'true'
if: needs.changes.outputs.e2e == 'true'
runs-on: ubuntu-latest
steps:
- name: checkout
@@ -70,9 +62,7 @@ jobs:
- chromium
shard: [1, 2, 3, 4]
needs: changes
if: |
((github.event_name == 'pull_request' && ! github.event.pull_request.head.repo.fork && github.event.pull_request.user.login != 'dependabot[bot]' && ! contains(github.event.pull_request.labels.*.name, 'safe-to-test')) ||
(github.event_name == 'pull_request_target' && contains(github.event.pull_request.labels.*.name, 'safe-to-test'))) && needs.changes.outputs.e2e == 'true'
if: needs.changes.outputs.e2e == 'true'
runs-on: ubuntu-latest
timeout-minutes: 30
steps:
@@ -181,17 +171,14 @@ jobs:
name: playwright-artifacts-${{ matrix.project }}-${{ matrix.shard }}
path: tests/e2e/artifacts/
retention-days: 5
# Required status check. Fails when e2e was needed but did not pass,
# including fork and dependabot PRs still waiting on safe-to-test.
# Required status check. Fails when e2e was needed but did not pass.
e2e:
if: always()
needs: [changes, fmtlint, test]
runs-on: ubuntu-latest
steps:
- name: verdict
if: |
github.event_name != 'merge_group' && (needs.changes.result != 'success' ||
(needs.changes.outputs.e2e == 'true' && (needs.fmtlint.result != 'success' || needs.test.result != 'success')))
if: contains(needs.*.result, 'failure') || contains(needs.*.result, 'cancelled')
run: |
echo "::error::e2e did not pass. Fork and dependabot PRs need the safe-to-test label."
echo "::error::e2e did not pass."
exit 1

View File

@@ -300,20 +300,17 @@ describe('PrivateRoute', () => {
);
});
it('should redirect /logs-save-views to /logs/saved-views', () => {
renderPrivateRoute({ initialRoute: '/logs-save-views' });
it.each([
['/logs-save-views', ROUTES.LOGS_EXPLORER],
['/traces-save-views', ROUTES.TRACES_EXPLORER],
[ROUTES.LOGS_SAVE_VIEWS, ROUTES.LOGS_EXPLORER],
[ROUTES.TRACES_SAVE_VIEWS, ROUTES.TRACES_EXPLORER],
[ROUTES.METRICS_EXPLORER_VIEWS, ROUTES.METRICS_EXPLORER_EXPLORER],
[ROUTES.METER_EXPLORER_VIEWS, ROUTES.METER_EXPLORER],
])('should redirect the old views route %s to %s', (from, to) => {
renderPrivateRoute({ initialRoute: from });
expect(screen.getByTestId('location-display')).toHaveTextContent(
'/logs/saved-views',
);
});
it('should redirect /traces-save-views to /traces/saved-views', () => {
renderPrivateRoute({ initialRoute: '/traces-save-views' });
expect(screen.getByTestId('location-display')).toHaveTextContent(
'/traces/saved-views',
);
expect(screen.getByTestId('location-display').textContent).toBe(to);
});
it('should redirect /settings/access-tokens to /settings/service-accounts', () => {
@@ -1635,11 +1632,6 @@ describe('PrivateRoute', () => {
['LIST_LICENSES', ROUTES.LIST_LICENSES, USER_ROLES.EDITOR as ROLES],
['ONBOARDING', ROUTES.ONBOARDING, USER_ROLES.VIEWER as ROLES],
['APPLICATION', ROUTES.APPLICATION, USER_ROLES.ANONYMOUS as ROLES],
[
'METRICS_EXPLORER_VIEWS',
ROUTES.METRICS_EXPLORER_VIEWS,
USER_ROLES.ANONYMOUS as ROLES,
],
];
it.each(authzRouteRolePairs)(

View File

@@ -513,8 +513,12 @@ export const LIST_LICENSES: AppRoutes = {
export const oldNewRoutesMapping: Record<string, string> = {
'/pipelines': '/logs/pipelines',
'/logs-explorer': '/logs/logs-explorer',
'/logs-save-views': '/logs/saved-views',
'/traces-save-views': '/traces/saved-views',
'/logs-save-views': '/logs/logs-explorer',
'/traces-save-views': '/traces-explorer',
'/logs/saved-views': '/logs/logs-explorer',
'/traces/saved-views': '/traces-explorer',
'/metrics-explorer/views': '/metrics-explorer/explorer',
'/meter/explorer/views': '/meter/explorer',
'/settings/access-tokens': '/settings/service-accounts',
'/settings/api-keys': '/settings/service-accounts',
'/messaging-queues': '/messaging-queues/overview',

View File

@@ -14,6 +14,7 @@ import * as useQueryBuilderHooks from 'hooks/queryBuilder/useQueryBuilder';
import * as useHandleExplorerTabChangeHooks from 'hooks/useHandleExplorerTabChange';
import * as appContextHooks from 'providers/App/App';
import { ErrorModalProvider } from 'providers/ErrorModalProvider';
import { PreferenceContextProvider } from 'providers/preferences/context/PreferenceContextProvider';
import * as timezoneHooks from 'providers/Timezone';
import store from 'store';
import { LicenseEvent } from 'types/api/licensesV3/getActive';
@@ -147,7 +148,9 @@ function renderExplorer(): void {
<MemoryRouter>
<Provider store={store}>
<ErrorModalProvider>
<Explorer />
<PreferenceContextProvider>
<Explorer />
</PreferenceContextProvider>
</ErrorModalProvider>
</Provider>
</MemoryRouter>

View File

@@ -1,11 +1,3 @@
import getLocalStorageKey from 'api/browser/localstorage/get';
import { LOCALSTORAGE } from 'constants/localStorage';
import { useState } from 'react';
export function useSavedViewEnabled(): boolean {
const [isEnabled] = useState(
() => getLocalStorageKey(LOCALSTORAGE.SAVED_VIEW_ENABLED) === 'true',
);
return isEnabled;
return true;
}

View File

@@ -1,47 +0,0 @@
/**
* AI-owned. Generated and maintained by the `signoz-page-story` skill.
* Do not hand-edit: regenerate instead.
*/
import ROUTES from 'constants/routes';
import { rest } from 'msw';
import { countControl } from '@/storybook/controls/controls';
import { defineStoryMocks } from '@/storybook/controls/defineStoryMocks';
import {
logsSavedViewsResponse,
SAVED_VIEW_MAX,
SAVED_VIEWS_PAGE_SIZE,
savedViewWriteResponse,
} from './__story_mockdata__/savedViews';
const LIST = 'Views · list';
export const savedViewsMocks = defineStoryMocks({
controls: {
views: countControl('Saved views', {
group: LIST,
description: `The views the org saved for logs. Past ${SAVED_VIEWS_PAGE_SIZE} the table paginates.`,
value: 4,
max: SAVED_VIEW_MAX,
}),
},
handlers: (values, response) => [
rest.get(
'http://localhost/api/v1/explorer/views',
response.json(() => logsSavedViewsResponse(values.views)),
),
rest.put(
'http://localhost/api/v1/explorer/views/:viewId',
response.json(savedViewWriteResponse),
),
rest.delete(
'http://localhost/api/v1/explorer/views/:viewId',
response.json(savedViewWriteResponse),
),
],
config: () => ({ route: ROUTES.LOGS_SAVE_VIEWS }),
});

View File

@@ -1,88 +0,0 @@
import type { Meta, StoryObj } from '@storybook/react-vite';
import { screen, userEvent, within } from 'storybook/test';
import { storyMocks } from '@/storybook/controls/defineStoryMocks';
import type { PageStoryArgs } from '@/storybook/runtime/resolveStory';
import { savedViewsMocks } from './SavedViews.stories.mocks';
import LogsModulePage from '../../LogsModulePage';
type SavedViewsArgs = PageStoryArgs<typeof savedViewsMocks>;
const pageStory = storyMocks(savedViewsMocks, { layout: 'app' });
/**
* Saved views on the logs explorer, and the actions the legacy editor role gates.
*
* Route: `/logs/saved-views`.
*/
const meta = {
title: 'Pages/Logs/Saved Views',
tags: ['role-gated', 'play'],
component: LogsModulePage,
...pageStory,
parameters: { ...pageStory.parameters },
} satisfies Meta<SavedViewsArgs>;
export default meta;
type Story = StoryObj<SavedViewsArgs>;
/** The page fetches before it renders a row, which outlasts the 1s default. */
const untilLoaded = { timeout: 15_000 };
/** Row actions are per-view icons, so a story that opens one picks the first. */
const openFirstAction = async (
canvasElement: HTMLElement,
testId: string,
): Promise<void> => {
const [action] = await within(canvasElement).findAllByTestId(
testId,
undefined,
untilLoaded,
);
await userEvent.click(action);
};
/**
* The views tab: every view the org saved for logs, who saved it and when, with
* the colour the view picker shows it under.
*/
export const Default: Story = {};
/** A workspace where nobody has saved a view yet. */
export const NoViews: Story = {
args: { views: 0 },
};
/** Enough views to paginate, which is the only place the second page shows up. */
export const Paginated: Story = {
args: { views: 8 },
};
/** Renaming a view: its label and the colour the picker shows it under. */
export const EditView: Story = {
play: async ({ canvasElement }): Promise<void> => {
await openFirstAction(canvasElement, 'edit-view');
await screen.findByText('Edit view details', undefined, untilLoaded);
},
};
/** The confirmation a view has to pass before it is deleted. */
export const DeleteView: Story = {
play: async ({ canvasElement }): Promise<void> => {
await openFirstAction(canvasElement, 'delete-view');
await screen.findByText(
/are you sure you want to delete/i,
undefined,
untilLoaded,
);
},
};
/** A viewer: the views are listed, none of them editable. */
export const ViewerAccess: Story = {
args: { access: 'viewer' },
};

View File

@@ -64,6 +64,7 @@ import {
metricTemporalityOf,
metricTypeOf,
metricsSavedViewsResponse,
metricsSavedViewsV2Response,
metricsStatsResponse,
metricsTreemapResponse,
REDUCTION_RULE_MAX,
@@ -517,6 +518,11 @@ export const metricsMocks = defineStoryMocks({
response.json(() => metricsSavedViewsResponse(values.savedViews)),
),
rest.get(
'http://localhost/api/v2/saved_views',
response.json(() => metricsSavedViewsV2Response(values.savedViews)),
),
rest.post(
'http://localhost/api/v5/query_range',
response.json(async (req) => {

View File

@@ -16,6 +16,7 @@ import type {
InspectMetrics200,
ListMetricReductionRules200,
ListMetrics200,
ListSavedViews200,
MetricreductionruletypesGettableReductionRuleDTO,
MetricsexplorertypesStatDTO,
MetricsexplorertypesTreemapEntryDTO,
@@ -24,7 +25,12 @@ import {
MetricreductionruletypesMatchTypeDTO,
MetrictypesTemporalityDTO,
MetrictypesTypeDTO,
Querybuildertypesv5QueryBuilderQueryGithubComSigNozSignozPkgTypesQuerybuildertypesQuerybuildertypesv5MetricAggregationDTOSignal as MetricsSignal,
Querybuildertypesv5QueryEnvelopeBuilderDTOType,
Querybuildertypesv5RequestTypeDTO,
SavedviewtypesPanelTypeDTO,
SavedviewtypesSchemaVersionDTO,
SavedviewtypesSourceDTO,
} from 'api/generated/services/sigNoz.schemas';
import { explorerView } from 'mocks-server/__mockdata__/explorer_views';
import type { AllViewsProps, ViewProps } from 'types/api/saveViews/types';
@@ -785,6 +791,33 @@ export const metricsSavedViewsResponse = (count: number): AllViewsProps => ({
})),
});
export const metricsSavedViewsV2Response = (
count: number,
): ListSavedViews200 => ({
status: 'success',
data: VIEW_NAMES.slice(0, count).map((displayName, index) => ({
id: `storybook-metrics-view-${index + 1}`,
name: `storybook-metrics-view-${index + 1}`,
source: SavedviewtypesSourceDTO.metrics,
schemaVersion: SavedviewtypesSchemaVersionDTO.v2,
createdAt: '2026-08-20T09:00:00Z',
createdBy: 'ada@signoz.io',
updatedAt: '2026-08-20T09:00:00Z',
updatedBy: 'ada@signoz.io',
spec: {
displayName,
panelType: SavedviewtypesPanelTypeDTO.graph,
requestType: Querybuildertypesv5RequestTypeDTO.time_series,
queries: [
{
type: Querybuildertypesv5QueryEnvelopeBuilderDTOType.builder_query,
spec: { name: 'A', signal: MetricsSignal.metrics },
},
],
},
})),
});
const FIELD_KEYS = [
'service.name',
'deployment.environment',

View File

@@ -10,7 +10,6 @@ import {
import { server } from 'mocks-server/server';
import { rest } from 'msw';
import {
act,
cleanup,
fireEvent,
render,
@@ -316,99 +315,17 @@ describe('TracesExplorer -', () => {
});
});
it('test for explorer options', async () => {
const { getByText, getByTestId } = renderWithTracesExplorerRouter(
<TracesExplorer />,
['/traces-explorer/?panelType=list&selectedExplorerView=list'],
);
// assert explorer options - action btns
[
'Save this view',
'Create an Alert',
'Add to Dashboard',
'Select a view',
].forEach((val) => expect(getByText(val)).toBeInTheDocument());
const hideExplorerOption = getByTestId('hide-toolbar');
expect(hideExplorerOption).toBeInTheDocument();
fireEvent.click(hideExplorerOption);
// explorer options should hide and show btn should be present
await expect(
screen.findByTestId('show-explorer-option'),
).resolves.toBeInTheDocument();
expect(screen.queryByTestId('hide-toolbar')).toBeNull();
// show explorer options
const showExplorerOption = screen.getByTestId('show-explorer-option');
expect(showExplorerOption).toBeInTheDocument();
fireEvent.click(showExplorerOption);
// explorer options should show and hide btn should be present
await expect(
screen.findByTestId('hide-toolbar'),
).resolves.toBeInTheDocument();
});
it('select a view options - assert and save this view', async () => {
const { container } = renderWithTracesExplorerRouter(<TracesExplorer />, [
'/traces-explorer/?panelType=list&selectedExplorerView=list',
]);
const viewSearchInput = await waitFor(() => {
const el = container.querySelector(
'.view-options .ant-select-selection-search-input',
) as HTMLElement;
expect(el).toBeInTheDocument();
return el;
});
fireEvent.mouseDown(viewSearchInput);
// The saved-views request has to land and re-render the dropdown before the
// option exists; findByRole's 1s default is not enough on a loaded runner.
await expect(
screen.findByRole('option', { name: 'R-test panel' }, { timeout: 5000 }),
).resolves.toBeInTheDocument();
// save this view
fireEvent.click(await screen.findByText('Save this view'));
const saveViewModalInput = await screen.findByPlaceholderText(
'e.g. External http method view',
);
expect(saveViewModalInput).toBeInTheDocument();
const saveViewModal = await waitFor(() => {
const el = document.querySelector('.ant-modal-content') as HTMLElement;
expect(el).toBeInTheDocument();
return el;
});
await act(async () =>
fireEvent.change(saveViewModalInput, { target: { value: 'test view' } }),
);
expect(saveViewModalInput).toHaveValue('test view');
await act(async () => {
fireEvent.click(within(saveViewModal).getByTestId('save-view-btn'));
});
await waitFor(() => {
expect(successNotification).toHaveBeenCalledWith({
message: 'View Saved Successfully',
});
});
}, 15000);
it('create a dashboard btn assert', async () => {
renderWithTracesExplorerRouter(<TracesExplorer />, [
'/traces-explorer/?panelType=list&selectedExplorerView=list',
]);
const createDashboardBtn = await screen.findByText('Add to Dashboard');
expect(createDashboardBtn).toBeInTheDocument();
const createDashboardBtn = await screen.findByTestId(
'explorer-add-to-dashboard',
);
await waitFor(() =>
expect(createDashboardBtn).not.toHaveAttribute('aria-disabled', 'true'),
);
fireEvent.click(createDashboardBtn);
const createDashboardModal = (await screen.findByRole(
@@ -431,8 +348,10 @@ describe('TracesExplorer -', () => {
'/traces-explorer/?panelType=list&selectedExplorerView=list',
]);
const createAlertBtn = await screen.findByText('Create an Alert');
expect(createAlertBtn).toBeInTheDocument();
const createAlertBtn = await screen.findByTestId('explorer-create-alert');
await waitFor(() =>
expect(createAlertBtn).not.toHaveAttribute('aria-disabled', 'true'),
);
fireEvent.click(createAlertBtn);
expect(historyPush).toHaveBeenCalledWith(

View File

@@ -1,7 +1,7 @@
import type { Meta, StoryObj } from '@storybook/react-vite';
import ROUTES from 'constants/routes';
import { toast } from '@signozhq/ui/sonner';
import { screen, userEvent, waitFor } from 'storybook/test';
import { screen, userEvent } from 'storybook/test';
import type { GlobalMockArgs } from '../globals';
@@ -14,10 +14,10 @@ function EmptyPage(): JSX.Element {
const untilLoaded = { timeout: 10_000 };
/**
* The strip along the foot of the shell, behind the `SAVED_VIEW_ENABLED` flag:
* what the page puts on the left, the build version when it puts nothing, and
* the shell's own Ask Noz and Support on the right. Support follows the chat
* support the workspace has, the way the floating bubble it replaces did.
* The strip along the foot of the shell: what the page puts on the left, the
* build version when it puts nothing, and the shell's own Ask Noz and Support
* on the right. Support follows the chat support the workspace has, the way
* the floating bubble it replaces did.
*
* Route: every page inside the app shell; these stories use `/home`.
*/
@@ -78,38 +78,6 @@ export const Tooltips: Story = {
args: { noz: true, support: 'pylon', tooltipsOpen: true },
};
/**
* The flag off, on a cloud trial without a card: no strip, and chat support is
* the floating bubble in the corner.
*/
export const WithoutStrip: Story = {
args: { bottomStrip: false, noz: true, support: 'add-card' },
};
/** The Add Credit Card modal, opened from the floating bubble with the flag off. */
export const WithoutStripAddCardModal: Story = {
args: { bottomStrip: false, noz: true, support: 'add-card' },
play: async (): Promise<void> => {
// The bubble is an icon with no label or test id of its own.
const bubble = await waitFor(() => {
const found = document.querySelector<HTMLElement>(
'.chat-support-gateway-btn',
);
if (!found) {
throw new Error('the floating support bubble never rendered');
}
return found;
}, untilLoaded);
await userEvent.click(bubble);
await screen.findByRole('dialog', {
name: /Add Credit Card for Chat Support/i,
});
},
};
/** Raises a toast and waits for it, so the shot is taken with it on screen. */
const raiseToast: NonNullable<Story['play']> = async () => {
toast.success('Service account deleted');
@@ -117,16 +85,10 @@ const raiseToast: NonNullable<Story['play']> = async () => {
};
/**
* A toast with the strip on. Sonner pins itself to the foot of the viewport, so
* A toast above the strip. Sonner pins itself to the foot of the viewport, so
* without an offset it lands on top of Ask Noz and Support.
*/
export const WithToast: Story = {
args: { noz: true, support: 'pylon' },
play: raiseToast,
};
/** The same toast with the flag off, back at sonner's own inset from the viewport. */
export const WithoutStripToast: Story = {
args: { bottomStrip: false, noz: true, support: 'pylon' },
play: raiseToast,
};

View File

@@ -34,7 +34,7 @@ export const PATH = {
k8s: '/infrastructure-monitoring/kubernetes',
login: '/login',
metricsSummary: '/metrics-explorer/summary',
metricsViews: '/metrics-explorer/views',
metricsExplorer: '/metrics-explorer/explorer',
roles: '/settings/roles',
roleCreate: '/settings/roles/new',
services: '/services',

View File

@@ -11,13 +11,13 @@ test.describe('Routing — route matching', () => {
await routing.goto(PATH.metricsSummary);
await expect(routing.activeRouteTab(PATH.metricsSummary)).toBeVisible();
await routing.routeTab(PATH.metricsViews).click();
await routing.waitForPath(PATH.metricsViews);
await expect(routing.activeRouteTab(PATH.metricsViews)).toBeVisible();
await routing.routeTab(PATH.metricsExplorer).click();
await routing.waitForPath(PATH.metricsExplorer);
await expect(routing.activeRouteTab(PATH.metricsExplorer)).toBeVisible();
await routing.reload();
expect(routing.url().pathname).toBe(PATH.metricsViews);
await expect(routing.activeRouteTab(PATH.metricsViews)).toBeVisible();
expect(routing.url().pathname).toBe(PATH.metricsExplorer);
await expect(routing.activeRouteTab(PATH.metricsExplorer)).toBeVisible();
});
test('TC-10 a prefix-route deep link loads with the right tab selected', async ({