Compare commits
27 Commits
nv/api-sta
...
feat/react
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ef92f4d754 | ||
|
|
54aee59e01 | ||
|
|
3d376083fd | ||
|
|
6c610940a1 | ||
|
|
c635827c1c | ||
|
|
4cf6b67042 | ||
|
|
b9c8cad8dd | ||
|
|
4638e9a0c4 | ||
|
|
c806371276 | ||
|
|
3adec1385c | ||
|
|
ab5064dff2 | ||
|
|
82d9f69d0a | ||
|
|
1f09c38d68 | ||
|
|
c108617ddc | ||
|
|
13722b6d0d | ||
|
|
c34223ecab | ||
|
|
fdd400e1b9 | ||
|
|
cad93a8063 | ||
|
|
aed096bf27 | ||
|
|
6b66ab64c8 | ||
|
|
362d3a4fdf | ||
|
|
ff80758e1c | ||
|
|
f9d425bf5c | ||
|
|
7ce73f3470 | ||
|
|
5aca8b0d3c | ||
|
|
f1c9e0f1d0 | ||
|
|
10c0af327b |
@@ -179,7 +179,6 @@ The `handler.New` function ties the HTTP handler to OpenAPI metadata via `OpenAP
|
||||
- **SuccessStatusCode**: The HTTP status for successful responses (for example, `http.StatusOK`, `http.StatusCreated`, `http.StatusNoContent`).
|
||||
- **ErrorStatusCodes**: Additional error status codes beyond the standard ones automatically added by `handler.New`.
|
||||
- **SecuritySchemes**: Auth mechanisms and scopes required by the operation.
|
||||
- **Stability**: Maturity marker (`handler.StabilityAlpha`, `handler.StabilityBeta`, `handler.StabilityStable`) emitted as the `x-stability` extension on every operation. Unset is emitted as `stable`.
|
||||
|
||||
The generic handler:
|
||||
|
||||
|
||||
@@ -80,15 +80,6 @@ func (ah *APIHandler) getFeatureFlags(w http.ResponseWriter, r *http.Request) {
|
||||
Route: "",
|
||||
})
|
||||
|
||||
aiObservability := ah.Signoz.Flagger.BooleanOrEmpty(ctx, flagger.FeatureEnableAIObservability, evalCtx)
|
||||
featureSet = append(featureSet, &licensetypes.Feature{
|
||||
Name: valuer.NewString(flagger.FeatureEnableAIObservability.String()),
|
||||
Active: aiObservability,
|
||||
Usage: 0,
|
||||
UsageLimit: -1,
|
||||
Route: "",
|
||||
})
|
||||
|
||||
metricsReduction := ah.Signoz.Flagger.BooleanOrEmpty(ctx, flagger.FeatureEnableMetricsReduction, evalCtx)
|
||||
featureSet = append(featureSet, &licensetypes.Feature{
|
||||
Name: valuer.NewString(flagger.FeatureEnableMetricsReduction.String()),
|
||||
|
||||
@@ -297,6 +297,9 @@
|
||||
// Forces useDashboardFetchRequired() outside the root V2 pages (allowlisted in overrides below)
|
||||
"signoz/no-msw-in-story-file": "error",
|
||||
// Bans msw imports in *.stories.tsx; handlers/mock data belong in the sibling .stories.mocks.tsx
|
||||
"signoz/no-direct-react-router-import": "error",
|
||||
// Steers new call sites to the src/lib/router facade. Warn, not error: ~310 pre-existing
|
||||
// violations are what the v6 migration is working through (allowlisted in overrides below)
|
||||
"no-restricted-globals": [
|
||||
"error",
|
||||
{
|
||||
@@ -589,6 +592,25 @@
|
||||
"no-console": "off",
|
||||
"sonarjs/cognitive-complexity": "off"
|
||||
}
|
||||
},
|
||||
{
|
||||
// The react-router allowlist from docs/react-router-v6-migration.md: the facade itself,
|
||||
// the route table, the router mount, the two navigation hooks, the history singleton
|
||||
// and the two harnesses that mount a router (jest and Storybook).
|
||||
// Test files are deliberately absent, their router imports go away outright.
|
||||
"files": [
|
||||
"src/lib/router/**",
|
||||
"src/AppRoutes/**",
|
||||
"src/app/AppRouter.tsx",
|
||||
"src/hooks/useSafeNavigate.ts",
|
||||
"src/hooks/useNavigationBlocker.ts",
|
||||
"src/lib/history.ts",
|
||||
"src/tests/router.tsx",
|
||||
"src/storybook/renderAtRoute.tsx"
|
||||
],
|
||||
"rules": {
|
||||
"signoz/no-direct-react-router-import": "off"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import type { Preview } from '@storybook/react-vite';
|
||||
import type { SetupWorker } from 'msw';
|
||||
import { setupWorker } from 'msw';
|
||||
import { configure } from 'storybook/test';
|
||||
|
||||
import { settleForCapture } from '../src/storybook/visual/settleForCapture';
|
||||
import PageDocs from '../src/storybook/docs/PageDocs';
|
||||
@@ -83,6 +84,10 @@ const translationsReady = i18n.loadNamespaces(
|
||||
),
|
||||
);
|
||||
|
||||
// testing-library's 1s default for `findBy*` and `waitFor` is shorter than a
|
||||
// popup or a query takes to land on a loaded CI runner.
|
||||
configure({ asyncUtilTimeout: 10_000 });
|
||||
|
||||
const preview: Preview = {
|
||||
parameters: {
|
||||
layout: 'fullscreen',
|
||||
|
||||
@@ -30,6 +30,8 @@ interface CapturedMessage {
|
||||
text: string;
|
||||
}
|
||||
|
||||
const PREPARE_TIMEOUT_MS = 180_000;
|
||||
|
||||
const messagesByPage = new WeakMap<Page, CapturedMessage[]>();
|
||||
|
||||
/**
|
||||
@@ -52,6 +54,15 @@ const config: TestRunnerConfig = {
|
||||
// msw logs every mocked request at `log`; keep it out of the failure dump
|
||||
// unless the job is re-run with debug logging (GitHub sets RUNNER_DEBUG=1).
|
||||
logLevel: process.env.RUNNER_DEBUG === '1' ? 'info' : 'warn',
|
||||
// The runner's default prepare, minus Playwright's 30s navigation timeout: on
|
||||
// a cold dev server the first load compiles the whole preview, which outlasts
|
||||
// it on a shared CI runner.
|
||||
async prepare({ page }): Promise<void> {
|
||||
await page.goto(new URL('iframe.html', process.env.TARGET_URL).toString(), {
|
||||
waitUntil: 'load',
|
||||
timeout: PREPARE_TIMEOUT_MS,
|
||||
});
|
||||
},
|
||||
async preVisit(page): Promise<void> {
|
||||
const existing = messagesByPage.get(page);
|
||||
if (existing) {
|
||||
|
||||
@@ -5,6 +5,8 @@
|
||||
/**
|
||||
* Adds custom matchers from the react testing library to all tests
|
||||
*/
|
||||
import { TextDecoder, TextEncoder } from 'node:util';
|
||||
|
||||
import '@testing-library/jest-dom';
|
||||
import '@testing-library/jest-dom/extend-expect';
|
||||
import 'jest-styled-components';
|
||||
@@ -14,6 +16,14 @@ import { server } from './src/mocks-server/server';
|
||||
import './src/styles.scss';
|
||||
// Establish API mocking before all tests.
|
||||
|
||||
// react-router@7's entry point pulls in its server-runtime cookie signing,
|
||||
// which builds a TextEncoder at module scope. jsdom ships neither encoder, so
|
||||
// importing anything from the router throws before a test starts.
|
||||
Object.assign(globalThis, {
|
||||
TextEncoder: globalThis.TextEncoder ?? TextEncoder,
|
||||
TextDecoder: globalThis.TextDecoder ?? TextDecoder,
|
||||
});
|
||||
|
||||
// Mock window.matchMedia
|
||||
window.matchMedia =
|
||||
window.matchMedia ||
|
||||
|
||||
@@ -82,7 +82,7 @@
|
||||
"dompurify": "3.4.15",
|
||||
"event-source-polyfill": "1.0.31",
|
||||
"eventemitter3": "5.0.1",
|
||||
"history": "4.10.1",
|
||||
"history": "5.3.0",
|
||||
"html-to-image": "1.11.13",
|
||||
"http-status-codes": "2.3.0",
|
||||
"i18next": "^21.6.12",
|
||||
@@ -118,8 +118,7 @@
|
||||
"react-query": "3.39.3",
|
||||
"react-redux": "^7.2.2",
|
||||
"react-rnd": "^10.5.3",
|
||||
"react-router-dom": "^5.2.0",
|
||||
"react-router-dom-v5-compat": "6.30.6",
|
||||
"react-router": "7.18.4",
|
||||
"react-syntax-highlighter": "15.5.0",
|
||||
"react-use": "^17.3.2",
|
||||
"react-virtuoso": "4.0.3",
|
||||
@@ -174,7 +173,6 @@
|
||||
"@types/crypto-js": "4.2.2",
|
||||
"@types/d3-hierarchy": "1.1.11",
|
||||
"@types/event-source-polyfill": "^1.0.0",
|
||||
"@types/history": "4.7.11",
|
||||
"@types/jest": "30.0.0",
|
||||
"@types/lodash-es": "^4.17.4",
|
||||
"@types/node": "^16.10.3",
|
||||
@@ -186,7 +184,6 @@
|
||||
"@types/react-grid-layout": "^1.1.2",
|
||||
"@types/react-redux": "^7.1.11",
|
||||
"@types/react-resizable": "3.0.3",
|
||||
"@types/react-router-dom": "^5.1.6",
|
||||
"@types/react-syntax-highlighter": "15.5.13",
|
||||
"@types/redux-mock-store": "1.0.4",
|
||||
"@types/styled-components": "^5.1.4",
|
||||
|
||||
@@ -0,0 +1,91 @@
|
||||
import { ruleTester } from './rule-tester.mjs';
|
||||
|
||||
const FACADE = 'src/lib/router facade';
|
||||
const HISTORY = 'lib/history singleton';
|
||||
|
||||
await ruleTester({
|
||||
rule: 'no-direct-react-router-import',
|
||||
valid: [
|
||||
{
|
||||
name: 'facade import',
|
||||
code: "import { useAppNavigate } from 'lib/router/useAppNavigate';",
|
||||
},
|
||||
{
|
||||
name:
|
||||
'the history package itself is a version-bump concern, not a facade one',
|
||||
code: "import { createBrowserHistory } from 'history';",
|
||||
},
|
||||
{
|
||||
name: 'unrelated module whose name contains history',
|
||||
code: "import { useHistoryPanel } from 'container/HistoryPanel';",
|
||||
},
|
||||
{
|
||||
name: 'jest.mock is not an import',
|
||||
code: "jest.mock('lib/history');",
|
||||
},
|
||||
{
|
||||
name: 'require of an unrelated module',
|
||||
code: "const x = require('lib/dashboardVariables');",
|
||||
},
|
||||
{
|
||||
name: 'export without a source',
|
||||
code: 'const a = 1;\nexport { a };',
|
||||
},
|
||||
],
|
||||
invalid: [
|
||||
{
|
||||
name: 'react-router-dom named import',
|
||||
code: "import { useHistory } from 'react-router-dom';",
|
||||
errors: [{ message: FACADE, line: 1, column: 28 }],
|
||||
},
|
||||
{
|
||||
name: 'react-router named import',
|
||||
code: "import { useLocation } from 'react-router';",
|
||||
errors: [{ message: FACADE }],
|
||||
},
|
||||
{
|
||||
name: 'react-router-dom type-only import',
|
||||
code: "import type { RouteProps } from 'react-router-dom';",
|
||||
errors: [{ message: FACADE }],
|
||||
},
|
||||
{
|
||||
name: 'react-router-dom is flagged as the v7 re-export shim',
|
||||
code: "import { Outlet } from 'react-router-dom';",
|
||||
errors: [{ message: 'only a re-export shim' }],
|
||||
},
|
||||
{
|
||||
name: 'lib/history default import',
|
||||
code: "import history from 'lib/history';",
|
||||
errors: [{ message: HISTORY }],
|
||||
},
|
||||
{
|
||||
name: 'lib/history require',
|
||||
code: "const history = require('lib/history').default;",
|
||||
errors: [{ message: HISTORY }],
|
||||
},
|
||||
{
|
||||
name: 'dynamic import',
|
||||
code: "const mod = await import('react-router-dom');",
|
||||
errors: [{ message: FACADE }],
|
||||
},
|
||||
{
|
||||
name: 're-export',
|
||||
code: "export { Link } from 'react-router-dom';",
|
||||
errors: [{ message: FACADE }],
|
||||
},
|
||||
{
|
||||
name: 'export all',
|
||||
code: "export * from 'react-router-dom';",
|
||||
errors: [{ message: FACADE }],
|
||||
},
|
||||
{
|
||||
name: 'one report per import statement',
|
||||
code:
|
||||
"import { Link } from 'react-router-dom';\nimport history from 'lib/history';",
|
||||
errors: [
|
||||
{ message: FACADE, line: 1 },
|
||||
{ message: HISTORY, line: 2 },
|
||||
],
|
||||
},
|
||||
],
|
||||
});
|
||||
99
frontend/plugins/rules/no-direct-react-router-import.mjs
Normal file
@@ -0,0 +1,99 @@
|
||||
/**
|
||||
* Rule: no-direct-react-router-import
|
||||
*
|
||||
* The v5 -> v6 migration (docs/react-router-v6-migration.md) routes every router
|
||||
* concern through the `src/lib/router/*` facade, so a version flip is a change to
|
||||
* one directory instead of ~300 call sites. This rule keeps new call sites from
|
||||
* reaching past it.
|
||||
*
|
||||
* `react-router-dom` is still flagged even though the package is gone: on v7 it is a
|
||||
* re-export shim over `react-router`, so reinstalling it would split the tree again.
|
||||
*
|
||||
* `history` (the package) is deliberately not flagged: it is a transitive concern of
|
||||
* the version bump, not something the facade replaces.
|
||||
*
|
||||
* The allowlist — the facade itself, the route table, the two navigation hooks, the
|
||||
* history singleton and the test harness — is applied via overrides in .oxlintrc.json,
|
||||
* not here, so the file list stays visible next to the severity.
|
||||
*/
|
||||
|
||||
import path from 'node:path';
|
||||
|
||||
const HISTORY_MODULE_SUFFIX = path.join('src', 'lib', 'history');
|
||||
|
||||
const MESSAGE_IDS = {
|
||||
'react-router': 'router',
|
||||
'react-router-dom': 'routerDom',
|
||||
'lib/history': 'historySingleton',
|
||||
};
|
||||
|
||||
/** Resolves `./history` / `../history` so files inside src/lib count too. */
|
||||
function isHistoryModule(specifier, filename) {
|
||||
if (!specifier.startsWith('.') || !filename) {
|
||||
return false;
|
||||
}
|
||||
const resolved = path.resolve(path.dirname(filename), specifier);
|
||||
return (
|
||||
resolved.endsWith(HISTORY_MODULE_SUFFIX) ||
|
||||
resolved.endsWith(`${HISTORY_MODULE_SUFFIX}.ts`)
|
||||
);
|
||||
}
|
||||
|
||||
function messageIdFor(specifier, filename) {
|
||||
if (MESSAGE_IDS[specifier] !== undefined) {
|
||||
return MESSAGE_IDS[specifier];
|
||||
}
|
||||
return isHistoryModule(specifier, filename) ? 'historySingleton' : null;
|
||||
}
|
||||
|
||||
export default {
|
||||
meta: {
|
||||
type: 'suggestion',
|
||||
docs: {
|
||||
description:
|
||||
'Disallow direct react-router / lib/history imports; import from src/lib/router instead',
|
||||
category: 'React Router migration',
|
||||
},
|
||||
schema: [],
|
||||
messages: {
|
||||
router:
|
||||
'Do not import react-router directly. Use the src/lib/router facade (useAppNavigate, useAppLocation, useAppParams, AppLink, Redirect, matchRoute) so a version flip stays contained. See frontend/docs/react-router-v6-migration.md.',
|
||||
routerDom:
|
||||
'Do not import react-router-dom. The app is on react-router; on v7 react-router-dom is only a re-export shim, and installing it puts two copies of the router in the tree. Use the src/lib/router facade. See frontend/docs/react-router-v7-upgrade.md.',
|
||||
historySingleton:
|
||||
'Do not import the lib/history singleton. Use useAppNavigate() inside components, or the imperative helpers in src/lib/router/navigation.ts outside them — history loses basename handling under v6. See frontend/docs/react-router-v6-migration.md.',
|
||||
},
|
||||
},
|
||||
|
||||
create(context) {
|
||||
const report = (sourceNode) => {
|
||||
if (
|
||||
sourceNode === null ||
|
||||
sourceNode === undefined ||
|
||||
typeof sourceNode.value !== 'string'
|
||||
) {
|
||||
return;
|
||||
}
|
||||
const messageId = messageIdFor(sourceNode.value, context.filename);
|
||||
if (messageId !== null) {
|
||||
context.report({ node: sourceNode, messageId });
|
||||
}
|
||||
};
|
||||
|
||||
return {
|
||||
ImportDeclaration: (node) => report(node.source),
|
||||
ImportExpression: (node) => report(node.source),
|
||||
ExportAllDeclaration: (node) => report(node.source),
|
||||
ExportNamedDeclaration: (node) => report(node.source),
|
||||
CallExpression(node) {
|
||||
const { callee } = node;
|
||||
if (
|
||||
(callee.type === 'Identifier' && callee.name === 'require') ||
|
||||
callee.type === 'Import'
|
||||
) {
|
||||
report(node.arguments[0]);
|
||||
}
|
||||
},
|
||||
};
|
||||
},
|
||||
};
|
||||
@@ -16,6 +16,7 @@ import noDashboardFetchOutsideRoot from './rules/no-dashboard-fetch-outside-root
|
||||
import noConditionalTextNodesWithSiblings from './rules/no-conditional-text-nodes-with-siblings.mjs';
|
||||
import noReturnTextNodes from './rules/no-return-text-nodes.mjs';
|
||||
import noMswInStoryFile from './rules/no-msw-in-story-file.mjs';
|
||||
import noDirectReactRouterImport from './rules/no-direct-react-router-import.mjs';
|
||||
|
||||
export default {
|
||||
meta: {
|
||||
@@ -33,5 +34,6 @@ export default {
|
||||
'no-conditional-text-nodes-with-siblings': noConditionalTextNodesWithSiblings,
|
||||
'no-return-text-nodes': noReturnTextNodes,
|
||||
'no-msw-in-story-file': noMswInStoryFile,
|
||||
'no-direct-react-router-import': noDirectReactRouterImport,
|
||||
},
|
||||
};
|
||||
|
||||
432
frontend/pnpm-lock.yaml
generated
@@ -74,11 +74,6 @@ overrides:
|
||||
# (postcss-selector-parser ^7.0.0)
|
||||
# remove: blocked, 4.2.0 is latest and the range is open, so the floor is what pulls the fix
|
||||
postcss-selector-parser@>=7.1.0 <7.1.3: '>=7.1.3 <8'
|
||||
# via: @signozhq/ui > nuqs@2 (react-router ^6.4.0 || ^7)
|
||||
# remove: blocked. GHSA-wrjc-x8rr-h8h6 and the deserializeErrors advisory are patched
|
||||
# only in 7.18.0. Do NOT open the cap: react-router >=7 requires React 19 and breaks
|
||||
# the app-wide CompatRouter, so those two moderates stay until the app moves to React 19
|
||||
react-router@>=6.7.0 <6.30.6: '>=6.30.6 <7'
|
||||
# via: msw@1.3.2 (devDep) > inquirer@8 > external-editor@3.1.0 (tmp ^0.0.33)
|
||||
# remove: upgrade msw to >=2 (drops the inquirer/external-editor chain)
|
||||
tmp@<0.2.6: '>=0.2.6 <0.3.0'
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { ReactChild, useCallback, useMemo } from 'react';
|
||||
import { matchPath, Redirect, useLocation } from 'react-router-dom';
|
||||
import getLocalStorageApi from 'api/browser/localstorage/get';
|
||||
import setLocalStorageApi from 'api/browser/localstorage/set';
|
||||
import { useListUsers } from 'api/generated/services/users';
|
||||
@@ -8,8 +7,10 @@ import { ORG_PREFERENCES } from 'constants/orgPreferences';
|
||||
import ROUTES from 'constants/routes';
|
||||
import { useGetTenantLicense } from 'hooks/useGetTenantLicense';
|
||||
import { useIsAIAssistantEnabled } from 'hooks/useIsAIAssistantEnabled';
|
||||
import { useIsAIObservabilityEnabled } from 'hooks/useIsAIObservabilityEnabled';
|
||||
import { isEmpty } from 'lodash-es';
|
||||
import { matchRoute } from 'lib/router/matchRoute';
|
||||
import { Redirect } from 'lib/router/Redirect';
|
||||
import { useAppLocation } from 'lib/router/useAppLocation';
|
||||
import { useAppContext } from 'providers/App/App';
|
||||
import { LicensePlatform, LicenseState } from 'types/api/licensesV3/getActive';
|
||||
import { OrgPreference } from 'types/api/preferences/preference';
|
||||
@@ -29,7 +30,7 @@ import routes, {
|
||||
|
||||
// eslint-disable-next-line sonarjs/cognitive-complexity
|
||||
function PrivateRoute({ children }: PrivateRouteProps): JSX.Element {
|
||||
const location = useLocation();
|
||||
const location = useAppLocation();
|
||||
const { pathname } = location;
|
||||
const {
|
||||
org,
|
||||
@@ -44,15 +45,15 @@ function PrivateRoute({ children }: PrivateRouteProps): JSX.Element {
|
||||
|
||||
const isAdmin = user.role === USER_ROLES.ADMIN;
|
||||
const isAIAssistantEnabled = useIsAIAssistantEnabled();
|
||||
const isAIObservabilityEnabled = useIsAIObservabilityEnabled();
|
||||
const mapRoutes = useMemo(
|
||||
() =>
|
||||
new Map(
|
||||
[...routes, LIST_LICENSES, SUPPORT_ROUTE].map((e) => {
|
||||
const currentPath = matchPath(pathname, {
|
||||
path: e.path,
|
||||
});
|
||||
return [currentPath === null ? null : 'current', e];
|
||||
const patterns = Array.isArray(e.path) ? e.path : [e.path];
|
||||
const matches = patterns.some(
|
||||
(pattern) => matchRoute(pathname, pattern) !== null,
|
||||
);
|
||||
return [matches ? 'current' : null, e];
|
||||
}),
|
||||
),
|
||||
[pathname],
|
||||
@@ -135,14 +136,6 @@ function PrivateRoute({ children }: PrivateRouteProps): JSX.Element {
|
||||
return <Redirect to={ROUTES.HOME} />;
|
||||
}
|
||||
|
||||
if (
|
||||
(pathname.startsWith(`${ROUTES.AI_OBSERVABILITY_BASE}/`) ||
|
||||
pathname === ROUTES.AI_OBSERVABILITY_BASE) &&
|
||||
!isAIObservabilityEnabled
|
||||
) {
|
||||
return <Redirect to={ROUTES.HOME} />;
|
||||
}
|
||||
|
||||
// Check for workspace access restriction (cloud only)
|
||||
const isCloudPlatform = activeLicense?.platform === LicensePlatform.CLOUD;
|
||||
|
||||
@@ -252,7 +245,7 @@ function PrivateRoute({ children }: PrivateRouteProps): JSX.Element {
|
||||
);
|
||||
if (fromPathname) {
|
||||
setLocalStorageApi(LOCALSTORAGE.UNAUTHENTICATED_ROUTE_HIT, '');
|
||||
return <Redirect to={fromPathname} />;
|
||||
return <Redirect to={fromPathname} replace={false} />;
|
||||
}
|
||||
if (pathname !== ROUTES.SOMETHING_WENT_WRONG) {
|
||||
return <Redirect to={ROUTES.HOME} />;
|
||||
@@ -266,7 +259,7 @@ function PrivateRoute({ children }: PrivateRouteProps): JSX.Element {
|
||||
);
|
||||
if (fromPathname) {
|
||||
setLocalStorageApi(LOCALSTORAGE.UNAUTHENTICATED_ROUTE_HIT, '');
|
||||
return <Redirect to={fromPathname} />;
|
||||
return <Redirect to={fromPathname} replace={false} />;
|
||||
}
|
||||
return <Redirect to={ROUTES.HOME} />;
|
||||
} else {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { ReactElement } from 'react';
|
||||
import { QueryClient, QueryClientProvider } from 'react-query';
|
||||
import { MemoryRouter, Route, Switch, useLocation } from 'react-router-dom';
|
||||
import { MemoryRouter, Route, Routes, useLocation } from 'react-router';
|
||||
import { act, render, screen, waitFor } from '@testing-library/react';
|
||||
import { LOCALSTORAGE } from 'constants/localStorage';
|
||||
import { ORG_PREFERENCES } from 'constants/orgPreferences';
|
||||
@@ -237,12 +237,17 @@ function buildPrivateRouteTree(
|
||||
<MemoryRouter initialEntries={[initialRoute]}>
|
||||
<AppContext.Provider value={contextValue}>
|
||||
<PrivateRoute>
|
||||
<Switch>
|
||||
<Route path="*">
|
||||
<div data-testid="children-rendered">Content</div>
|
||||
<LocationDisplay />
|
||||
</Route>
|
||||
</Switch>
|
||||
<Routes>
|
||||
<Route
|
||||
path="*"
|
||||
element={
|
||||
<>
|
||||
<div data-testid="children-rendered">Content</div>
|
||||
<LocationDisplay />
|
||||
</>
|
||||
}
|
||||
/>
|
||||
</Routes>
|
||||
</PrivateRoute>
|
||||
</AppContext.Provider>
|
||||
</MemoryRouter>
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { ReactNode, Suspense, useCallback, useEffect, useState } from 'react';
|
||||
import { Route, Router, Switch } from 'react-router-dom';
|
||||
import { CompatRouter } from 'react-router-dom-v5-compat';
|
||||
import { Route, Routes } from 'react-router';
|
||||
import * as Sentry from '@sentry/react';
|
||||
import getLocalStorageApi from 'api/browser/localstorage/get';
|
||||
import setLocalStorageApi from 'api/browser/localstorage/set';
|
||||
@@ -22,7 +21,7 @@ import { useIsAIAssistantEnabled } from 'hooks/useIsAIAssistantEnabled';
|
||||
import { useIsDarkMode } from 'hooks/useDarkMode';
|
||||
import { useGetTenantLicense } from 'hooks/useGetTenantLicense';
|
||||
import { StatusCodes } from 'http-status-codes';
|
||||
import history from 'lib/history';
|
||||
import { getCurrentLocation, navigate, subscribe } from 'lib/router/navigation';
|
||||
import ErrorBoundaryFallback from 'pages/ErrorBoundaryFallback/ErrorBoundaryFallback';
|
||||
import posthog from 'posthog-js';
|
||||
import { useAppContext } from 'providers/App/App';
|
||||
@@ -38,12 +37,6 @@ import defaultRoutes, {
|
||||
SUPPORT_ROUTE,
|
||||
} from './routes';
|
||||
|
||||
const appRouter = (children: ReactNode): ReactNode => (
|
||||
<Router history={history}>
|
||||
<CompatRouter>{children}</CompatRouter>
|
||||
</Router>
|
||||
);
|
||||
|
||||
const appLayout = (children: ReactNode): ReactNode => (
|
||||
<AppLayout>{children}</AppLayout>
|
||||
);
|
||||
@@ -68,14 +61,16 @@ function App(): JSX.Element {
|
||||
const isAIAssistantEnabled = useIsAIAssistantEnabled();
|
||||
|
||||
const { hostname } = window.location;
|
||||
const [pathname, setPathname] = useState(history.location.pathname);
|
||||
// Through the facade, not the raw history: the router owns the base path
|
||||
// now, so `history.location.pathname` would still carry it.
|
||||
const [pathname, setPathname] = useState(getCurrentLocation().pathname);
|
||||
|
||||
const { isCloudUser, isEnterpriseSelfHostedUser } = useGetTenantLicense();
|
||||
|
||||
const [isSentryInitialized, setIsSentryInitialized] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
return history.listen((location) => {
|
||||
return subscribe(({ location }) => {
|
||||
setPathname(location.pathname);
|
||||
});
|
||||
}, []);
|
||||
@@ -439,7 +434,7 @@ function App(): JSX.Element {
|
||||
// this needs to be on top of data missing error because if there is an error, data will never be loaded and it will
|
||||
// move to indefinitive loading
|
||||
if (userFetchError && pathname !== ROUTES.SOMETHING_WENT_WRONG) {
|
||||
history.replace(ROUTES.SOMETHING_WENT_WRONG);
|
||||
navigate(ROUTES.SOMETHING_WENT_WRONG, { replace: true });
|
||||
}
|
||||
|
||||
// if all of the data is not set then return a spinner, this is required because there is some gap between loading states and data setting
|
||||
@@ -455,7 +450,6 @@ function App(): JSX.Element {
|
||||
return (
|
||||
<Sentry.ErrorBoundary fallback={<ErrorBoundaryFallback />}>
|
||||
<AppShell
|
||||
router={appRouter}
|
||||
overlays={
|
||||
isLoggedInState && (
|
||||
<>
|
||||
@@ -468,18 +462,19 @@ function App(): JSX.Element {
|
||||
<PrivateRoute>
|
||||
<AppPageProviders layout={appLayout}>
|
||||
<Suspense fallback={<Spinner size="large" tip="Loading..." />}>
|
||||
<Switch>
|
||||
{routes.map(({ path, component, exact }) => (
|
||||
<Route
|
||||
key={`${path}`}
|
||||
exact={exact}
|
||||
path={path}
|
||||
component={component}
|
||||
/>
|
||||
))}
|
||||
<Route exact path="/" component={Home} />
|
||||
<Route path="*" component={NotFound} />
|
||||
</Switch>
|
||||
<Routes>
|
||||
{routes.flatMap(({ path, component: Component, nested }) =>
|
||||
(Array.isArray(path) ? path : [path]).map((pattern) => (
|
||||
<Route
|
||||
key={pattern}
|
||||
path={nested ? `${pattern}/*` : pattern}
|
||||
element={<Component />}
|
||||
/>
|
||||
)),
|
||||
)}
|
||||
<Route path="/" element={<Home />} />
|
||||
<Route path="*" element={<NotFound />} />
|
||||
</Routes>
|
||||
</Suspense>
|
||||
</AppPageProviders>
|
||||
</PrivateRoute>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { RouteProps } from 'react-router-dom';
|
||||
import type { ComponentType } from 'react';
|
||||
import ROUTES from 'constants/routes';
|
||||
|
||||
import {
|
||||
@@ -61,27 +61,25 @@ const routes: AppRoutes[] = [
|
||||
{
|
||||
component: SignupPage,
|
||||
path: ROUTES.SIGN_UP,
|
||||
exact: true,
|
||||
isPrivate: false,
|
||||
key: 'SIGN_UP',
|
||||
},
|
||||
{
|
||||
path: ROUTES.GET_STARTED_WITH_CLOUD,
|
||||
exact: false,
|
||||
nested: true,
|
||||
component: OnboardingV2,
|
||||
isPrivate: true,
|
||||
key: 'GET_STARTED_WITH_CLOUD',
|
||||
},
|
||||
{
|
||||
path: ROUTES.HOME,
|
||||
exact: true,
|
||||
component: Home,
|
||||
isPrivate: true,
|
||||
key: 'HOME',
|
||||
},
|
||||
{
|
||||
path: ROUTES.ONBOARDING,
|
||||
exact: false,
|
||||
nested: true,
|
||||
component: OrgOnboarding,
|
||||
isPrivate: true,
|
||||
key: 'ONBOARDING',
|
||||
@@ -89,27 +87,23 @@ const routes: AppRoutes[] = [
|
||||
{
|
||||
component: LogsIndexToFields,
|
||||
path: ROUTES.LOGS_INDEX_FIELDS,
|
||||
exact: true,
|
||||
isPrivate: true,
|
||||
key: 'LOGS_INDEX_FIELDS',
|
||||
},
|
||||
{
|
||||
component: ServicesTablePage,
|
||||
path: ROUTES.APPLICATION,
|
||||
exact: true,
|
||||
isPrivate: true,
|
||||
key: 'APPLICATION',
|
||||
},
|
||||
{
|
||||
path: ROUTES.SERVICE_METRICS,
|
||||
exact: true,
|
||||
component: ServiceMetricsPage,
|
||||
isPrivate: true,
|
||||
key: 'SERVICE_METRICS',
|
||||
},
|
||||
{
|
||||
path: ROUTES.SERVICE_TOP_LEVEL_OPERATIONS,
|
||||
exact: true,
|
||||
component: ServiceTopLevelOperationsPage,
|
||||
isPrivate: true,
|
||||
key: 'SERVICE_TOP_LEVEL_OPERATIONS',
|
||||
@@ -118,320 +112,276 @@ const routes: AppRoutes[] = [
|
||||
path: ROUTES.SERVICE_MAP,
|
||||
component: ServiceMapPage,
|
||||
isPrivate: true,
|
||||
exact: true,
|
||||
key: 'SERVICE_MAP',
|
||||
},
|
||||
{
|
||||
path: ROUTES.LOGS_SAVE_VIEWS,
|
||||
component: LogsSaveViews,
|
||||
isPrivate: true,
|
||||
exact: true,
|
||||
key: 'LOGS_SAVE_VIEWS',
|
||||
},
|
||||
{
|
||||
path: ROUTES.TRACE_DETAIL,
|
||||
exact: true,
|
||||
component: TraceDetailV3,
|
||||
isPrivate: true,
|
||||
key: 'TRACE_DETAIL',
|
||||
},
|
||||
{
|
||||
path: ROUTES.SETTINGS,
|
||||
exact: false,
|
||||
nested: true,
|
||||
component: SettingsPage,
|
||||
isPrivate: true,
|
||||
key: 'SETTINGS',
|
||||
},
|
||||
{
|
||||
path: ROUTES.USAGE_EXPLORER,
|
||||
exact: true,
|
||||
component: UsageExplorerPage,
|
||||
isPrivate: true,
|
||||
key: 'USAGE_EXPLORER',
|
||||
},
|
||||
{
|
||||
path: ROUTES.ALL_DASHBOARD,
|
||||
exact: true,
|
||||
component: DashboardsListPage,
|
||||
isPrivate: true,
|
||||
key: 'ALL_DASHBOARD',
|
||||
},
|
||||
{
|
||||
path: ROUTES.DASHBOARD,
|
||||
exact: true,
|
||||
component: DashboardPage,
|
||||
isPrivate: true,
|
||||
key: 'DASHBOARD',
|
||||
},
|
||||
{
|
||||
path: ROUTES.PUBLIC_DASHBOARD,
|
||||
exact: false,
|
||||
nested: true,
|
||||
component: PublicDashboardPage,
|
||||
isPrivate: false,
|
||||
key: 'PUBLIC_DASHBOARD',
|
||||
},
|
||||
{
|
||||
path: ROUTES.DASHBOARD_PANEL_EDITOR,
|
||||
exact: true,
|
||||
component: DashboardPanelEditorPage,
|
||||
isPrivate: true,
|
||||
key: 'DASHBOARD_PANEL_EDITOR',
|
||||
},
|
||||
{
|
||||
path: ROUTES.EDIT_ALERTS,
|
||||
exact: true,
|
||||
component: EditRulesPage,
|
||||
isPrivate: true,
|
||||
key: 'EDIT_ALERTS',
|
||||
},
|
||||
{
|
||||
path: ROUTES.LIST_ALL_ALERT,
|
||||
exact: true,
|
||||
component: ListAllALertsPage,
|
||||
isPrivate: true,
|
||||
key: 'LIST_ALL_ALERT',
|
||||
},
|
||||
{
|
||||
path: ROUTES.ALERTS_NEW,
|
||||
exact: true,
|
||||
component: CreateNewAlerts,
|
||||
isPrivate: true,
|
||||
key: 'ALERTS_NEW',
|
||||
},
|
||||
{
|
||||
path: ROUTES.ALERT_HISTORY,
|
||||
exact: true,
|
||||
component: AlertHistory,
|
||||
isPrivate: true,
|
||||
key: 'ALERT_HISTORY',
|
||||
},
|
||||
{
|
||||
path: ROUTES.ALERT_OVERVIEW,
|
||||
exact: true,
|
||||
component: AlertOverview,
|
||||
isPrivate: true,
|
||||
key: 'ALERT_OVERVIEW',
|
||||
},
|
||||
{
|
||||
path: ROUTES.TRACES_EXPLORER,
|
||||
exact: true,
|
||||
component: TracesExplorer,
|
||||
isPrivate: true,
|
||||
key: 'TRACES_EXPLORER',
|
||||
},
|
||||
{
|
||||
path: ROUTES.TRACES_SAVE_VIEWS,
|
||||
exact: true,
|
||||
component: TracesSaveViews,
|
||||
isPrivate: true,
|
||||
key: 'TRACES_SAVE_VIEWS',
|
||||
},
|
||||
{
|
||||
path: ROUTES.TRACES_FUNNELS,
|
||||
exact: true,
|
||||
component: TracesFunnels,
|
||||
isPrivate: true,
|
||||
key: 'TRACES_FUNNELS',
|
||||
},
|
||||
{
|
||||
path: ROUTES.TRACES_FUNNELS_DETAIL,
|
||||
exact: true,
|
||||
component: TracesFunnelDetails,
|
||||
isPrivate: true,
|
||||
key: 'TRACES_FUNNELS_DETAIL',
|
||||
},
|
||||
{
|
||||
path: ROUTES.CHANNELS_NEW,
|
||||
exact: true,
|
||||
component: ChannelsNew,
|
||||
isPrivate: true,
|
||||
key: 'CHANNELS_NEW',
|
||||
},
|
||||
{
|
||||
path: ROUTES.CHANNELS_EDIT,
|
||||
exact: true,
|
||||
component: ChannelsEdit,
|
||||
isPrivate: true,
|
||||
key: 'CHANNELS_EDIT',
|
||||
},
|
||||
{
|
||||
path: ROUTES.ALL_ERROR,
|
||||
exact: true,
|
||||
isPrivate: true,
|
||||
component: AllErrors,
|
||||
key: 'ALL_ERROR',
|
||||
},
|
||||
{
|
||||
path: ROUTES.ERROR_DETAIL,
|
||||
exact: true,
|
||||
component: ErrorDetails,
|
||||
isPrivate: true,
|
||||
key: 'ERROR_DETAIL',
|
||||
},
|
||||
{
|
||||
path: ROUTES.VERSION,
|
||||
exact: true,
|
||||
component: StatusPage,
|
||||
isPrivate: true,
|
||||
key: 'VERSION',
|
||||
},
|
||||
{
|
||||
path: ROUTES.LOGS,
|
||||
exact: true,
|
||||
component: Logs,
|
||||
key: 'LOGS',
|
||||
isPrivate: true,
|
||||
},
|
||||
{
|
||||
path: ROUTES.LIVE_LOGS,
|
||||
exact: true,
|
||||
component: LiveLogs,
|
||||
key: 'LIVE_LOGS',
|
||||
isPrivate: true,
|
||||
},
|
||||
{
|
||||
path: ROUTES.LOGS_PIPELINES,
|
||||
exact: true,
|
||||
component: PipelinePage,
|
||||
key: 'LOGS_PIPELINES',
|
||||
isPrivate: true,
|
||||
},
|
||||
{
|
||||
path: ROUTES.LOGIN,
|
||||
exact: true,
|
||||
component: Login,
|
||||
isPrivate: false,
|
||||
key: 'LOGIN',
|
||||
},
|
||||
{
|
||||
path: ROUTES.FORGOT_PASSWORD,
|
||||
exact: true,
|
||||
component: ForgotPassword,
|
||||
isPrivate: false,
|
||||
key: 'FORGOT_PASSWORD',
|
||||
},
|
||||
{
|
||||
path: ROUTES.UN_AUTHORIZED,
|
||||
exact: true,
|
||||
component: UnAuthorized,
|
||||
key: 'UN_AUTHORIZED',
|
||||
isPrivate: true,
|
||||
},
|
||||
{
|
||||
path: ROUTES.PASSWORD_RESET,
|
||||
exact: true,
|
||||
component: PasswordReset,
|
||||
key: 'PASSWORD_RESET',
|
||||
isPrivate: false,
|
||||
},
|
||||
{
|
||||
path: ROUTES.SOMETHING_WENT_WRONG,
|
||||
exact: true,
|
||||
component: SomethingWentWrong,
|
||||
key: 'SOMETHING_WENT_WRONG',
|
||||
isPrivate: false,
|
||||
},
|
||||
{
|
||||
path: ROUTES.WORKSPACE_LOCKED,
|
||||
exact: true,
|
||||
component: WorkspaceBlocked,
|
||||
isPrivate: true,
|
||||
key: 'WORKSPACE_LOCKED',
|
||||
},
|
||||
{
|
||||
path: ROUTES.WORKSPACE_SUSPENDED,
|
||||
exact: true,
|
||||
component: WorkspaceSuspended,
|
||||
isPrivate: true,
|
||||
key: 'WORKSPACE_SUSPENDED',
|
||||
},
|
||||
{
|
||||
path: ROUTES.WORKSPACE_ACCESS_RESTRICTED,
|
||||
exact: true,
|
||||
component: WorkspaceAccessRestricted,
|
||||
isPrivate: true,
|
||||
key: 'WORKSPACE_ACCESS_RESTRICTED',
|
||||
},
|
||||
{
|
||||
path: ROUTES.INTEGRATIONS_DETAIL,
|
||||
exact: true,
|
||||
component: IntegrationsDetailsPage,
|
||||
isPrivate: true,
|
||||
key: 'INTEGRATIONS_DETAIL',
|
||||
},
|
||||
{
|
||||
path: ROUTES.INTEGRATIONS,
|
||||
exact: true,
|
||||
component: Integrations,
|
||||
isPrivate: true,
|
||||
key: 'INTEGRATIONS',
|
||||
},
|
||||
{
|
||||
path: ROUTES.MESSAGING_QUEUES_KAFKA,
|
||||
exact: true,
|
||||
component: MessagingQueuesMainPage,
|
||||
key: 'MESSAGING_QUEUES_KAFKA',
|
||||
isPrivate: true,
|
||||
},
|
||||
{
|
||||
path: ROUTES.MESSAGING_QUEUES_CELERY_TASK,
|
||||
exact: true,
|
||||
component: MessagingQueuesMainPage,
|
||||
key: 'MESSAGING_QUEUES_CELERY_TASK',
|
||||
isPrivate: true,
|
||||
},
|
||||
{
|
||||
path: ROUTES.MESSAGING_QUEUES_OVERVIEW,
|
||||
exact: true,
|
||||
component: MessagingQueuesMainPage,
|
||||
key: 'MESSAGING_QUEUES_OVERVIEW',
|
||||
isPrivate: true,
|
||||
},
|
||||
{
|
||||
path: ROUTES.MESSAGING_QUEUES_KAFKA_DETAIL,
|
||||
exact: true,
|
||||
component: MessagingQueuesMainPage,
|
||||
key: 'MESSAGING_QUEUES_KAFKA_DETAIL',
|
||||
isPrivate: true,
|
||||
},
|
||||
{
|
||||
path: ROUTES.INFRASTRUCTURE_MONITORING_HOSTS,
|
||||
exact: true,
|
||||
component: InfrastructureMonitoring,
|
||||
key: 'INFRASTRUCTURE_MONITORING_HOSTS',
|
||||
isPrivate: true,
|
||||
},
|
||||
{
|
||||
path: ROUTES.INFRASTRUCTURE_MONITORING_KUBERNETES,
|
||||
exact: true,
|
||||
component: InfrastructureMonitoring,
|
||||
key: 'INFRASTRUCTURE_MONITORING_KUBERNETES',
|
||||
isPrivate: true,
|
||||
},
|
||||
{
|
||||
path: ROUTES.METRICS_EXPLORER,
|
||||
exact: true,
|
||||
component: MetricsExplorer,
|
||||
key: 'METRICS_EXPLORER',
|
||||
isPrivate: true,
|
||||
},
|
||||
{
|
||||
path: ROUTES.METRICS_EXPLORER_EXPLORER,
|
||||
exact: true,
|
||||
component: MetricsExplorer,
|
||||
key: 'METRICS_EXPLORER_EXPLORER',
|
||||
isPrivate: true,
|
||||
},
|
||||
{
|
||||
path: ROUTES.METRICS_EXPLORER_VIEWS,
|
||||
exact: true,
|
||||
component: MetricsExplorer,
|
||||
key: 'METRICS_EXPLORER_VIEWS',
|
||||
isPrivate: true,
|
||||
},
|
||||
{
|
||||
path: ROUTES.METRICS_EXPLORER_VOLUME_CONTROL,
|
||||
exact: true,
|
||||
component: MetricsExplorer,
|
||||
key: 'METRICS_EXPLORER_VOLUME_CONTROL',
|
||||
isPrivate: true,
|
||||
@@ -439,63 +389,54 @@ const routes: AppRoutes[] = [
|
||||
|
||||
{
|
||||
path: ROUTES.METER,
|
||||
exact: true,
|
||||
component: MeterExplorerPage,
|
||||
key: 'METER',
|
||||
isPrivate: true,
|
||||
},
|
||||
{
|
||||
path: ROUTES.METER_EXPLORER,
|
||||
exact: true,
|
||||
component: MeterExplorerPage,
|
||||
key: 'METER_EXPLORER',
|
||||
isPrivate: true,
|
||||
},
|
||||
{
|
||||
path: ROUTES.METER_EXPLORER_VIEWS,
|
||||
exact: true,
|
||||
component: MeterExplorerPage,
|
||||
key: 'METER_EXPLORER_VIEWS',
|
||||
isPrivate: true,
|
||||
},
|
||||
{
|
||||
path: ROUTES.API_MONITORING,
|
||||
exact: true,
|
||||
component: ApiMonitoring,
|
||||
key: 'API_MONITORING',
|
||||
isPrivate: true,
|
||||
},
|
||||
{
|
||||
path: [ROUTES.AI_ASSISTANT_BASE, ROUTES.AI_ASSISTANT],
|
||||
exact: true,
|
||||
component: AIAssistantPage,
|
||||
key: 'AI_ASSISTANT',
|
||||
isPrivate: true,
|
||||
},
|
||||
{
|
||||
path: ROUTES.AI_OBSERVABILITY_ATTRIBUTE_MAPPING,
|
||||
exact: true,
|
||||
component: LLMObservabilityPage,
|
||||
key: 'AI_OBSERVABILITY_ATTRIBUTE_MAPPING',
|
||||
isPrivate: true,
|
||||
},
|
||||
{
|
||||
path: ROUTES.AI_OBSERVABILITY_OVERVIEW,
|
||||
exact: true,
|
||||
component: LLMObservabilityPage,
|
||||
key: 'AI_OBSERVABILITY_OVERVIEW',
|
||||
isPrivate: true,
|
||||
},
|
||||
{
|
||||
path: ROUTES.AI_OBSERVABILITY_EXPLORER,
|
||||
exact: true,
|
||||
component: LLMObservabilityPage,
|
||||
key: 'AI_OBSERVABILITY_EXPLORER',
|
||||
isPrivate: true,
|
||||
},
|
||||
{
|
||||
path: ROUTES.AI_OBSERVABILITY_CONFIGURATION,
|
||||
exact: true,
|
||||
component: LLMObservabilityPage,
|
||||
key: 'AI_OBSERVABILITY_CONFIGURATION',
|
||||
isPrivate: true,
|
||||
@@ -504,7 +445,6 @@ const routes: AppRoutes[] = [
|
||||
|
||||
export const SUPPORT_ROUTE: AppRoutes = {
|
||||
path: ROUTES.SUPPORT,
|
||||
exact: true,
|
||||
component: SupportPage,
|
||||
key: 'SUPPORT',
|
||||
isPrivate: true,
|
||||
@@ -512,7 +452,6 @@ export const SUPPORT_ROUTE: AppRoutes = {
|
||||
|
||||
export const LIST_LICENSES: AppRoutes = {
|
||||
path: ROUTES.LIST_LICENSES,
|
||||
exact: true,
|
||||
component: LicensePage,
|
||||
isPrivate: true,
|
||||
key: 'LIST_LICENSES',
|
||||
@@ -541,9 +480,15 @@ export const ROUTES_NOT_TO_BE_OVERRIDEN: string[] = [
|
||||
];
|
||||
|
||||
export interface AppRoutes {
|
||||
component: RouteProps['component'];
|
||||
path: RouteProps['path'];
|
||||
exact: RouteProps['exact'];
|
||||
component: ComponentType;
|
||||
/** An array registers the same component under each pattern. */
|
||||
path: string | string[];
|
||||
/**
|
||||
* The route renders its own child routes, so it matches as a prefix. v6
|
||||
* matches the whole path by default, so the mount appends a `/*` splat for
|
||||
* these and nothing for the rest.
|
||||
*/
|
||||
nested?: boolean;
|
||||
isPrivate: boolean;
|
||||
key: keyof typeof ROUTES;
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import deleteLocalStorageKey from 'api/browser/localstorage/remove';
|
||||
import { LOCALSTORAGE } from 'constants/localStorage';
|
||||
import ROUTES from 'constants/routes';
|
||||
import history from 'lib/history';
|
||||
import { navigate } from 'lib/router/navigation';
|
||||
|
||||
import deleteSession from './v2/sessions/delete';
|
||||
|
||||
@@ -21,5 +21,5 @@ export const Logout = async (): Promise<void> => {
|
||||
deleteLocalStorageKey(LOCALSTORAGE.CHAT_SUPPORT);
|
||||
deleteLocalStorageKey(LOCALSTORAGE.USER_ID);
|
||||
window.dispatchEvent(new CustomEvent('LOGOUT'));
|
||||
history.push(ROUTES.LOGIN);
|
||||
navigate(ROUTES.LOGIN);
|
||||
};
|
||||
|
||||
@@ -16,6 +16,7 @@ export interface AppProvidersProps {
|
||||
store: Store;
|
||||
queryClient: QueryClient;
|
||||
appContext: AppLayer;
|
||||
router: AppLayer;
|
||||
searchParams: AppLayer;
|
||||
}
|
||||
|
||||
@@ -27,27 +28,34 @@ export interface AppProvidersProps {
|
||||
* A new provider belongs here only if it holds process-wide state that does not
|
||||
* depend on the user, the license or the route. One that fetches on mount would
|
||||
* fire unauthenticated from here; put it in `AppShell` or lower.
|
||||
*
|
||||
* `router` is the outermost layer because `searchParams` (nuqs) reads the
|
||||
* router's `useNavigate` / `useSearchParams`. Everything below, including the
|
||||
* boot spinner, therefore renders inside the router.
|
||||
*/
|
||||
function AppProviders({
|
||||
children,
|
||||
store,
|
||||
queryClient,
|
||||
appContext,
|
||||
router,
|
||||
searchParams,
|
||||
}: AppProvidersProps): JSX.Element {
|
||||
return (
|
||||
<HelmetProvider>
|
||||
{searchParams(
|
||||
<ThemeProvider>
|
||||
<TimezoneProvider>
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<Provider store={store}>
|
||||
<GlobalTimeStoreAdapter />
|
||||
{appContext(children)}
|
||||
</Provider>
|
||||
</QueryClientProvider>
|
||||
</TimezoneProvider>
|
||||
</ThemeProvider>,
|
||||
{router(
|
||||
searchParams(
|
||||
<ThemeProvider>
|
||||
<TimezoneProvider>
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<Provider store={store}>
|
||||
<GlobalTimeStoreAdapter />
|
||||
{appContext(children)}
|
||||
</Provider>
|
||||
</QueryClientProvider>
|
||||
</TimezoneProvider>
|
||||
</ThemeProvider>,
|
||||
),
|
||||
)}
|
||||
</HelmetProvider>
|
||||
);
|
||||
|
||||
26
frontend/src/app/AppRouter.tsx
Normal file
@@ -0,0 +1,26 @@
|
||||
import { ReactNode } from 'react';
|
||||
import { unstable_HistoryRouter as HistoryRouter } from 'react-router';
|
||||
import history from 'lib/history';
|
||||
import { getBasePath } from 'utils/basePath';
|
||||
|
||||
/**
|
||||
* The outermost layer. It is above `NuqsAdapter` on purpose:
|
||||
* `nuqs/adapters/react-router/v7` calls `useNavigate` and `useSearchParams`, so
|
||||
* it only works inside a router.
|
||||
*
|
||||
* Transitions are off on purpose: under one React keeps the previous screen up
|
||||
* instead of committing the Suspense fallback, so a route whose chunk is not
|
||||
* cached yet renders no loader at all. Rendering pending UI under it needs
|
||||
* `useNavigation()` and a data router. See docs/react-router-v7-upgrade.md.
|
||||
*/
|
||||
export function appRouter(children: ReactNode): ReactNode {
|
||||
return (
|
||||
<HistoryRouter
|
||||
basename={getBasePath()}
|
||||
history={history}
|
||||
useTransitions={false}
|
||||
>
|
||||
{children}
|
||||
</HistoryRouter>
|
||||
);
|
||||
}
|
||||
@@ -5,11 +5,8 @@ import { NotificationProvider } from 'hooks/useNotifications';
|
||||
import { CmdKProvider } from 'providers/cmdKProvider';
|
||||
import { ErrorModalProvider } from 'providers/ErrorModalProvider';
|
||||
|
||||
import { AppLayer } from './types';
|
||||
|
||||
export interface AppShellProps {
|
||||
children: ReactNode;
|
||||
router: AppLayer;
|
||||
/** Mounted beside the routed content: the command palette and its siblings. */
|
||||
overlays?: ReactNode;
|
||||
}
|
||||
@@ -19,28 +16,26 @@ export interface AppShellProps {
|
||||
* fetches settle, above `PrivateRoute`, so it also covers the redirects and the
|
||||
* not-found route, and it survives every navigation.
|
||||
*
|
||||
* A new provider belongs here if it needs the router or the user and has to
|
||||
* outlive the page: a global overlay, a shortcut host, anything one route opens
|
||||
* and the next one keeps.
|
||||
* A new provider belongs here if it needs the user and has to outlive the page:
|
||||
* a global overlay, a shortcut host, anything one route opens and the next one
|
||||
* keeps. The router itself is higher up, in `AppProviders`.
|
||||
*
|
||||
* `ConfigProvider` reads `useThemeConfig`, which needs `ThemeProvider` above it,
|
||||
* so the antd theme is settled here instead of by the caller.
|
||||
*/
|
||||
function AppShell({ children, router, overlays }: AppShellProps): JSX.Element {
|
||||
function AppShell({ children, overlays }: AppShellProps): JSX.Element {
|
||||
const themeConfig = useThemeConfig();
|
||||
|
||||
return (
|
||||
<ConfigProvider theme={themeConfig}>
|
||||
{router(
|
||||
<CmdKProvider>
|
||||
<NotificationProvider>
|
||||
<ErrorModalProvider>
|
||||
{overlays}
|
||||
{children}
|
||||
</ErrorModalProvider>
|
||||
</NotificationProvider>
|
||||
</CmdKProvider>,
|
||||
)}
|
||||
<CmdKProvider>
|
||||
<NotificationProvider>
|
||||
<ErrorModalProvider>
|
||||
{overlays}
|
||||
{children}
|
||||
</ErrorModalProvider>
|
||||
</NotificationProvider>
|
||||
</CmdKProvider>
|
||||
</ConfigProvider>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<svg version="1.1" id="Layer_1" xmlns:x="ns_extend;" xmlns:i="ns_ai;" xmlns:graph="ns_graphs;" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" viewBox="0 0 92.2 65" style="enable-background:new 0 0 92.2 65;" xml:space="preserve">
|
||||
<style type="text/css">
|
||||
.st0{fill:#FFFFFF;}
|
||||
.st0{fill:#7A8291;}
|
||||
</style>
|
||||
<metadata>
|
||||
<sfw xmlns="ns_sfw;">
|
||||
|
||||
|
Before Width: | Height: | Size: 714 B After Width: | Height: | Size: 714 B |
3
frontend/src/assets/Logos/antigravity.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg role="img" viewBox="0 0 16 15" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M14.0777 13.984C14.945 14.6345 16.2458 14.2008 15.0533 13.0084C11.476 9.53949 12.2349 0 7.79033 0C3.34579 0 4.10461 9.53949 0.527295 13.0084C-0.773543 14.3092 0.635692 14.6345 1.50293 13.984C4.86344 11.7076 4.64663 7.69664 7.79033 7.69664C10.934 7.69664 10.7172 11.7076 14.0777 13.984Z" fill="#4285F4" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 394 B |
1
frontend/src/assets/Logos/aurora.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64"><g fill="none" fill-rule="evenodd"><path fill="#c925d1" d="M0 0h64v64H0z"/><path fill="#fff" d="M36.213 16.011h-2.016v-2.005h2.016V12h2.017v2.006h2.016v2.005H38.23v2.006h-2.017zm9.074 8.023h-2.016v-2.006h2.016v-2.005h2.016v2.005h2.017v2.006h-2.017v2.006h-2.016v-2.006m-3.434 22.64c-1.495-3.292-4.482-6.263-7.792-7.75 3.31-1.487 6.297-4.459 7.792-7.75 1.494 3.291 4.482 6.263 7.791 7.75-3.31 1.487-6.297 4.458-7.79 7.75m12.139-8.753c-5.202 0-11.13-5.898-11.13-11.071 0-.555-.452-1.003-1.009-1.003s-1.008.448-1.008 1.003c0 5.173-5.93 11.071-11.13 11.071-.558 0-1.009.448-1.009 1.003s.45 1.003 1.008 1.003c5.202 0 11.13 5.898 11.13 11.07 0 .554.452 1.003 1.01 1.003.556 0 1.007-.45 1.007-1.003 0-5.172 5.93-11.07 11.13-11.07.558 0 1.009-.45 1.009-1.003 0-.555-.45-1.003-1.008-1.003m-31.39-19.904c6.85 0 10.587 1.988 10.587 3.008s-3.737 3.009-10.586 3.009-10.587-1.988-10.587-3.009 3.737-3.008 10.587-3.008m-.35 15.15c-5.012 0-8.62-1.063-10.236-2.19v-7.113c2.367 1.433 6.487 2.176 10.587 2.176s8.22-.743 10.586-2.176v6.484c-1.016 1.406-5.247 2.819-10.936 2.819M33.19 45.975c0 1.435-4.126 3.52-10.59 3.52-6.46 0-10.583-2.085-10.583-3.52v-4.407c2.258 1.354 6.014 2.192 10.276 2.192 2.942 0 5.786-.413 8.01-1.166l-.651-1.898c-2.019.682-4.633 1.058-7.359 1.058-5.39 0-9.252-1.402-10.276-2.76v-5.707c2.411 1.176 6.113 1.885 10.237 1.885 3.92 0 8.34-.725 10.936-2.269v2.162h2.016v-14.04c0-3.292-6.34-5.014-12.602-5.014S10 17.733 10 21.025v24.95c0 3.59 6.49 5.526 12.598 5.526 6.112 0 12.607-1.937 12.607-5.526v-2.887h-2.016z"/></g></svg>
|
||||
|
After Width: | Height: | Size: 1.5 KiB |
@@ -1,6 +1,6 @@
|
||||
<svg version="1.1" id="Layer_1" xmlns:x="ns_extend;" xmlns:i="ns_ai;" xmlns:graph="ns_graphs;" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" viewBox="0 0 50.6 50.6" style="enable-background:new 0 0 50.6 50.6;" xml:space="preserve">
|
||||
<style type="text/css">
|
||||
.st0{fill:#FFFFFF;}
|
||||
.st0{fill:#7A8291;}
|
||||
</style>
|
||||
<metadata>
|
||||
<sfw xmlns="ns_sfw;">
|
||||
|
||||
|
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 1.3 KiB |
3
frontend/src/assets/Logos/cline.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg role="img" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="m23.365 13.556-1.442-2.895V8.994c0-2.764-2.218-5.002-4.954-5.002h-2.464c.178-.367.276-.779.276-1.213A2.77 2.77 0 0 0 12.018 0a2.77 2.77 0 0 0-2.763 2.779c0 .434.098.846.276 1.213H7.067c-2.736 0-4.954 2.238-4.954 5.002v1.667L.64 13.549c-.149.29-.149.636 0 .927l1.472 2.855v1.667C2.113 21.762 4.33 24 7.067 24h9.902c2.736 0 4.954-2.238 4.954-5.002V17.33l1.44-2.865c.143-.286.143-.622.002-.91m-12.854 2.36a2.27 2.27 0 0 1-2.261 2.273 2.27 2.27 0 0 1-2.261-2.273v-4.042A2.27 2.27 0 0 1 8.249 9.6a2.267 2.267 0 0 1 2.262 2.274zm7.285 0a2.27 2.27 0 0 1-2.26 2.273 2.27 2.27 0 0 1-2.262-2.273v-4.042A2.267 2.267 0 0 1 15.535 9.6a2.267 2.267 0 0 1 2.261 2.274z" fill="#7A8291" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 761 B |
@@ -1,6 +1,6 @@
|
||||
<svg version="1.1" id="Layer_1" xmlns:x="ns_extend;" xmlns:i="ns_ai;" xmlns:graph="ns_graphs;" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" viewBox="0 0 43 43" style="enable-background:new 0 0 43 43;" xml:space="preserve">
|
||||
<style type="text/css">
|
||||
.st0{fill-rule:evenodd;clip-rule:evenodd;fill:#FFFFFF;}
|
||||
.st0{fill-rule:evenodd;clip-rule:evenodd;fill:#0074A2;}
|
||||
</style>
|
||||
<metadata>
|
||||
<sfw xmlns="ns_sfw;">
|
||||
|
||||
|
Before Width: | Height: | Size: 1.9 KiB After Width: | Height: | Size: 1.9 KiB |
3
frontend/src/assets/Logos/cursor.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg role="img" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M11.503.131 1.891 5.678a.84.84 0 0 0-.42.726v11.188c0 .3.162.575.42.724l9.609 5.55a1 1 0 0 0 .998 0l9.61-5.55a.84.84 0 0 0 .42-.724V6.404a.84.84 0 0 0-.42-.726L12.497.131a1.01 1.01 0 0 0-.996 0M2.657 6.338h18.55c.263 0 .43.287.297.515L12.23 22.918c-.062.107-.229.064-.229-.06V12.335a.59.59 0 0 0-.295-.51l-9.11-5.257c-.109-.063-.064-.23.061-.23" fill="#7A8291" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 453 B |
@@ -3,7 +3,7 @@
|
||||
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
viewBox="0 0 800.5 907.77" style="enable-background:new 0 0 800.5 907.77;" xml:space="preserve">
|
||||
<style type="text/css">
|
||||
.st0{fill:#FFFFFF;}
|
||||
.st0{fill:#8B5CC7;}
|
||||
</style>
|
||||
<path class="st0" d="M303.36,238.61c31.36-21.37,71.76-12.97,65-6.53c-12.89,12.28,4.26,8.65,6.11,31.31
|
||||
c1.36,16.69-4.09,25.88-8.78,31.11c-9.79,1.28-21.69,3.67-36.02,8.33c-8.48,2.76-15.85,5.82-22.31,8.9
|
||||
|
||||
|
Before Width: | Height: | Size: 3.8 KiB After Width: | Height: | Size: 3.8 KiB |
10
frontend/src/assets/Logos/daytona.svg
Normal file
@@ -0,0 +1,10 @@
|
||||
<svg role="img" viewBox="0 0 275 287" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M14.5584 193.736H114.275V227.925H14.5584V193.736Z" fill="#7A8291" />
|
||||
<path d="M148.464 74.076H262.426V108.265H148.464V74.076Z" fill="#7A8291" />
|
||||
<path d="M88.6338 84.6127L173.246 0L197.422 24.175L112.809 108.788L88.6338 84.6127Z" fill="#7A8291" />
|
||||
<path d="M89.157 170.084L24.175 105.102L0 129.277L64.9819 194.259L89.157 170.084Z" fill="#7A8291" />
|
||||
<path d="M174.629 217.911L106.133 286.407L81.9577 262.232L150.454 193.736L174.629 217.911Z" fill="#7A8291" />
|
||||
<path d="M174.106 132.44L250.66 208.994L274.835 184.819L198.281 108.265L174.106 132.44Z" fill="#7A8291" />
|
||||
<path d="M88.6338 48.434V131.057H54.4451L54.4451 48.434H88.6338Z" fill="#7A8291" />
|
||||
<path d="M208.294 168.094V270.66H174.106V168.094H208.294Z" fill="#7A8291" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 825 B |
@@ -1 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="#fff" viewBox="0 0 24 24"><title>Deno</title><path d="M1.105 18.02A11.9 11.9 0 0 1 0 12.985q0-.698.078-1.376a12 12 0 0 1 .231-1.34A12 12 0 0 1 4.025 4.02a12 12 0 0 1 5.46-2.771 12 12 0 0 1 3.428-.23c1.452.112 2.825.477 4.077 1.05a12 12 0 0 1 2.78 1.774 12.02 12.02 0 0 1 4.053 7.078A12 12 0 0 1 24 12.985q0 .454-.036.914a12 12 0 0 1-.728 3.305 12 12 0 0 1-2.38 3.875c-1.33 1.357-3.02 1.962-4.43 1.936a4.4 4.4 0 0 1-2.724-1.024c-.99-.853-1.391-1.83-1.53-2.919a5 5 0 0 1 .128-1.518c.105-.38.37-1.116.76-1.437-.455-.197-1.04-.624-1.226-.829-.045-.05-.04-.13 0-.183a.155.155 0 0 1 .177-.053c.392.134.869.267 1.372.35.66.111 1.484.25 2.317.292 2.03.1 4.153-.813 4.812-2.627s.403-3.609-1.96-4.685-3.454-2.356-5.363-3.128c-1.247-.505-2.636-.205-4.06.582-3.838 2.121-7.277 8.822-5.69 15.032a.191.191 0 0 1-.315.19 12 12 0 0 1-1.25-1.634 12 12 0 0 1-.769-1.404M11.57 6.087c.649-.051 1.214.501 1.31 1.236.13.979-.228 1.99-1.41 2.013-1.01.02-1.315-.997-1.248-1.614.066-.616.574-1.575 1.35-1.635"/></svg>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="#7A8291" viewBox="0 0 24 24"><title>Deno</title><path d="M1.105 18.02A11.9 11.9 0 0 1 0 12.985q0-.698.078-1.376a12 12 0 0 1 .231-1.34A12 12 0 0 1 4.025 4.02a12 12 0 0 1 5.46-2.771 12 12 0 0 1 3.428-.23c1.452.112 2.825.477 4.077 1.05a12 12 0 0 1 2.78 1.774 12.02 12.02 0 0 1 4.053 7.078A12 12 0 0 1 24 12.985q0 .454-.036.914a12 12 0 0 1-.728 3.305 12 12 0 0 1-2.38 3.875c-1.33 1.357-3.02 1.962-4.43 1.936a4.4 4.4 0 0 1-2.724-1.024c-.99-.853-1.391-1.83-1.53-2.919a5 5 0 0 1 .128-1.518c.105-.38.37-1.116.76-1.437-.455-.197-1.04-.624-1.226-.829-.045-.05-.04-.13 0-.183a.155.155 0 0 1 .177-.053c.392.134.869.267 1.372.35.66.111 1.484.25 2.317.292 2.03.1 4.153-.813 4.812-2.627s.403-3.609-1.96-4.685-3.454-2.356-5.363-3.128c-1.247-.505-2.636-.205-4.06.582-3.838 2.121-7.277 8.822-5.69 15.032a.191.191 0 0 1-.315.19 12 12 0 0 1-1.25-1.634 12 12 0 0 1-.769-1.404M11.57 6.087c.649-.051 1.214.501 1.31 1.236.13.979-.228 1.99-1.41 2.013-1.01.02-1.315-.997-1.248-1.614.066-.616.574-1.575 1.35-1.635"/></svg>
|
||||
|
Before Width: | Height: | Size: 1.0 KiB After Width: | Height: | Size: 1.0 KiB |
@@ -1 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="100" height="100" fill="#FFF" viewBox="0 0 50 50"><path d="M7 2v46h36V14.594l-.281-.313-12-12L30.406 2Zm2 2h20v12h12v30H9Zm22 1.438L39.563 14H31ZM15 22v2h20v-2Zm0 6v2h16v-2Zm0 6v2h20v-2Z"/></svg>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="100" height="100" fill="#7A8291" viewBox="0 0 50 50"><path d="M7 2v46h36V14.594l-.281-.313-12-12L30.406 2Zm2 2h20v12h12v30H9Zm22 1.438L39.563 14H31ZM15 22v2h20v-2Zm0 6v2h16v-2Zm0 6v2h20v-2Z"/></svg>
|
||||
|
Before Width: | Height: | Size: 242 B After Width: | Height: | Size: 245 B |
3
frontend/src/assets/Logos/e2b.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg role="img" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M9 6h10v12H9a4 4 0 0 1-4-4v-4a4 4 0 0 1 4-4zm1.2 4.6h8.8v.9h-8.8zm0 3.6h8.8v.9h-8.8z" fill="#7A8291" fill-rule="evenodd" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 213 B |
5
frontend/src/assets/Logos/emqx.svg
Normal file
@@ -0,0 +1,5 @@
|
||||
<svg role="img" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M6.5 5.5h13a1.5 1.5 0 0 1 0 3h-13a1.5 1.5 0 0 1 0-3z" fill="#00B173" />
|
||||
<path d="M4 10.5h10a1.5 1.5 0 0 1 0 3H4a1.5 1.5 0 0 1 0-3z" fill="#00B173" />
|
||||
<path d="M6.5 15.5h13a1.5 1.5 0 0 1 0 3h-13a1.5 1.5 0 0 1 0-3z" fill="#00B173" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 323 B |
@@ -1 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xml:space="preserve" viewBox="0 0 115.28 122.88"><path d="M25.38 57h64.88V37.34H69.59c-2.17 0-5.19-1.17-6.62-2.6s-2.3-4.01-2.3-6.17V7.64H8.15c-.18 0-.32.09-.41.18-.15.1-.19.23-.19.42v106.45c0 .14.09.32.18.41.09.14.28.18.41.18h81.51c.18 0 .17-.09.27-.18.14-.09.33-.28.33-.41v-11.16H25.38c-4.14 0-7.56-3.4-7.56-7.56V64.55c0-4.15 3.4-7.55 7.56-7.55m4.11 11.38h7.43v18.15h11.63v5.92H29.49zm20.4 12.05c0-3.93 1.09-6.99 3.28-9.17 2.19-2.19 5.24-3.28 9.15-3.28 4.01 0 7.09 1.08 9.26 3.22 2.17 2.15 3.25 5.16 3.25 9.04 0 2.81-.47 5.11-1.42 6.91q-1.425 2.7-4.11 4.2t-6.69 1.5c-2.71 0-4.96-.43-6.74-1.29-1.78-.87-3.22-2.23-4.32-4.11-1.11-1.87-1.66-4.21-1.66-7.02m7.42.01c0 2.43.45 4.17 1.36 5.23s2.14 1.59 3.7 1.59c1.6 0 2.84-.52 3.71-1.56.88-1.04 1.32-2.9 1.32-5.6 0-2.26-.46-3.92-1.37-4.96-.92-1.05-2.16-1.57-3.73-1.57-1.5 0-2.71.53-3.62 1.59-.91 1.08-1.37 2.83-1.37 5.28m33.11 3.3v-5.01h11.49v10.23c-2.2 1.5-4.15 2.53-5.83 3.07-1.69.54-3.7.81-6.02.81-2.86 0-5.19-.49-6.99-1.46s-3.19-2.42-4.18-4.35c-.99-1.92-1.48-4.13-1.48-6.63 0-2.63.54-4.91 1.62-6.85s2.67-3.41 4.76-4.42c1.63-.78 3.83-1.17 6.58-1.17 2.66 0 4.64.24 5.96.72s2.41 1.23 3.28 2.24 1.52 2.3 1.96 3.85l-7.16 1.29c-.3-.91-.8-1.61-1.5-2.09-.71-.49-1.6-.73-2.7-.73-1.62 0-2.92.57-3.89 1.7s-1.45 2.92-1.45 5.37c0 2.6.49 4.46 1.47 5.57.97 1.11 2.34 1.68 4.09 1.68q1.245 0 2.37-.36c.75-.24 1.61-.65 2.59-1.22v-2.25h-4.97zM97.79 57h9.93c4.16 0 7.56 3.41 7.56 7.56v31.42c0 4.15-3.41 7.56-7.56 7.56h-9.93v13.55c0 1.61-.65 3.04-1.7 4.1a5.74 5.74 0 0 1-4.1 1.7H5.81a5.74 5.74 0 0 1-4.1-1.7 5.74 5.74 0 0 1-1.7-4.1V5.85c0-1.61.65-3.04 1.7-4.1a5.8 5.8 0 0 1 4.1-1.7h58.72c.13-.05.27-.05.41-.05.64 0 1.29.28 1.75.69h.09c.09.05.14.09.23.18L97 31.23c.51.51.88 1.2.88 1.98 0 .23-.05.41-.09.65zM67.52 27.97V8.94l21.43 21.7H70.19c-.74 0-1.38-.32-1.89-.78-.46-.46-.78-1.15-.78-1.89" style="fill:#D1D5DB;fill-rule:evenodd;clip-rule:evenodd"/></svg>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xml:space="preserve" viewBox="0 0 115.28 122.88"><path d="M25.38 57h64.88V37.34H69.59c-2.17 0-5.19-1.17-6.62-2.6s-2.3-4.01-2.3-6.17V7.64H8.15c-.18 0-.32.09-.41.18-.15.1-.19.23-.19.42v106.45c0 .14.09.32.18.41.09.14.28.18.41.18h81.51c.18 0 .17-.09.27-.18.14-.09.33-.28.33-.41v-11.16H25.38c-4.14 0-7.56-3.4-7.56-7.56V64.55c0-4.15 3.4-7.55 7.56-7.55m4.11 11.38h7.43v18.15h11.63v5.92H29.49zm20.4 12.05c0-3.93 1.09-6.99 3.28-9.17 2.19-2.19 5.24-3.28 9.15-3.28 4.01 0 7.09 1.08 9.26 3.22 2.17 2.15 3.25 5.16 3.25 9.04 0 2.81-.47 5.11-1.42 6.91q-1.425 2.7-4.11 4.2t-6.69 1.5c-2.71 0-4.96-.43-6.74-1.29-1.78-.87-3.22-2.23-4.32-4.11-1.11-1.87-1.66-4.21-1.66-7.02m7.42.01c0 2.43.45 4.17 1.36 5.23s2.14 1.59 3.7 1.59c1.6 0 2.84-.52 3.71-1.56.88-1.04 1.32-2.9 1.32-5.6 0-2.26-.46-3.92-1.37-4.96-.92-1.05-2.16-1.57-3.73-1.57-1.5 0-2.71.53-3.62 1.59-.91 1.08-1.37 2.83-1.37 5.28m33.11 3.3v-5.01h11.49v10.23c-2.2 1.5-4.15 2.53-5.83 3.07-1.69.54-3.7.81-6.02.81-2.86 0-5.19-.49-6.99-1.46s-3.19-2.42-4.18-4.35c-.99-1.92-1.48-4.13-1.48-6.63 0-2.63.54-4.91 1.62-6.85s2.67-3.41 4.76-4.42c1.63-.78 3.83-1.17 6.58-1.17 2.66 0 4.64.24 5.96.72s2.41 1.23 3.28 2.24 1.52 2.3 1.96 3.85l-7.16 1.29c-.3-.91-.8-1.61-1.5-2.09-.71-.49-1.6-.73-2.7-.73-1.62 0-2.92.57-3.89 1.7s-1.45 2.92-1.45 5.37c0 2.6.49 4.46 1.47 5.57.97 1.11 2.34 1.68 4.09 1.68q1.245 0 2.37-.36c.75-.24 1.61-.65 2.59-1.22v-2.25h-4.97zM97.79 57h9.93c4.16 0 7.56 3.41 7.56 7.56v31.42c0 4.15-3.41 7.56-7.56 7.56h-9.93v13.55c0 1.61-.65 3.04-1.7 4.1a5.74 5.74 0 0 1-4.1 1.7H5.81a5.74 5.74 0 0 1-4.1-1.7 5.74 5.74 0 0 1-1.7-4.1V5.85c0-1.61.65-3.04 1.7-4.1a5.8 5.8 0 0 1 4.1-1.7h58.72c.13-.05.27-.05.41-.05.64 0 1.29.28 1.75.69h.09c.09.05.14.09.23.18L97 31.23c.51.51.88 1.2.88 1.98 0 .23-.05.41-.09.65zM67.52 27.97V8.94l21.43 21.7H70.19c-.74 0-1.38-.32-1.89-.78-.46-.46-.78-1.15-.78-1.89" style="fill:#7A8291;fill-rule:evenodd;clip-rule:evenodd"/></svg>
|
||||
|
Before Width: | Height: | Size: 1.9 KiB After Width: | Height: | Size: 1.9 KiB |
@@ -1 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="#fff" viewBox="0 0 24 24"><title>Haystack</title><path d="M2.008 0C.9 0 0 .9 0 2.008v19.984C0 23.1.9 24 2.008 24h19.984C23.1 24 24 23.1 24 21.992V2.008C24 .9 23.1 0 21.992 0Zm9.963 3.84c3.43 0 6.21 2.763 6.21 6.17v6.488a.27.27 0 0 1-.27.268 2.423 2.423 0 0 1-2.43-2.414V10.01c0-1.927-1.572-3.488-3.51-3.488S8.547 8.085 8.547 10.01v1.608a.263.263 0 0 0 .259.268h1.54a.27.27 0 0 0 .275-.263V9.945c0-.74.604-1.341 1.35-1.341s1.35.6 1.35 1.341V20.03a.275.275 0 0 1-.28.268 2.41 2.41 0 0 1-2.42-2.404v-3.23a.275.275 0 0 0-.276-.269H8.811a.264.264 0 0 0-.264.263v1.08c0 1.333-1.175 2.414-2.517 2.414a.27.27 0 0 1-.27-.268v-7.872c0-3.408 2.78-6.171 6.21-6.171"/></svg>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="#0E9A89" viewBox="0 0 24 24"><title>Haystack</title><path d="M2.008 0C.9 0 0 .9 0 2.008v19.984C0 23.1.9 24 2.008 24h19.984C23.1 24 24 23.1 24 21.992V2.008C24 .9 23.1 0 21.992 0Zm9.963 3.84c3.43 0 6.21 2.763 6.21 6.17v6.488a.27.27 0 0 1-.27.268 2.423 2.423 0 0 1-2.43-2.414V10.01c0-1.927-1.572-3.488-3.51-3.488S8.547 8.085 8.547 10.01v1.608a.263.263 0 0 0 .259.268h1.54a.27.27 0 0 0 .275-.263V9.945c0-.74.604-1.341 1.35-1.341s1.35.6 1.35 1.341V20.03a.275.275 0 0 1-.28.268 2.41 2.41 0 0 1-2.42-2.404v-3.23a.275.275 0 0 0-.276-.269H8.811a.264.264 0 0 0-.264.263v1.08c0 1.333-1.175 2.414-2.517 2.414a.27.27 0 0 1-.27-.268v-7.872c0-3.408 2.78-6.171 6.21-6.171"/></svg>
|
||||
|
Before Width: | Height: | Size: 707 B After Width: | Height: | Size: 710 B |
@@ -1 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="154" height="250" preserveAspectRatio="xMidYMid" viewBox="0 0 256 416"><path d="M201.816 230.216c-16.186 0-30.697 7.171-40.634 18.461l-25.463-18.026c2.703-7.442 4.255-15.433 4.255-23.797 0-8.219-1.498-16.076-4.112-23.408l25.406-17.835c9.936 11.233 24.409 18.365 40.548 18.365 29.875 0 54.184-24.305 54.184-54.184s-24.309-54.184-54.184-54.184-54.184 24.305-54.184 54.184c0 5.348.808 10.505 2.258 15.389l-25.423 17.844c-10.62-13.175-25.911-22.374-43.333-25.182v-30.64c24.544-5.155 43.037-26.962 43.037-53.019C124.171 24.305 99.862 0 69.987 0S15.803 24.305 15.803 54.184c0 25.708 18.014 47.246 42.067 52.769v31.038C25.044 143.753 0 172.401 0 206.854c0 34.621 25.292 63.374 58.355 68.94v32.774c-24.299 5.341-42.552 27.011-42.552 52.894 0 29.879 24.309 54.184 54.184 54.184s54.184-24.305 54.184-54.184c0-25.883-18.253-47.553-42.552-52.894v-32.775a69.97 69.97 0 0 0 42.6-24.776l25.633 18.143c-1.423 4.84-2.22 9.946-2.22 15.24 0 29.879 24.309 54.184 54.184 54.184S256 314.279 256 284.4s-24.309-54.184-54.184-54.184m0-126.695c14.487 0 26.27 11.788 26.27 26.271s-11.783 26.27-26.27 26.27-26.27-11.787-26.27-26.27 11.783-26.271 26.27-26.271m-158.1-49.337c0-14.483 11.784-26.27 26.271-26.27s26.27 11.787 26.27 26.27-11.783 26.27-26.27 26.27-26.271-11.787-26.271-26.27m52.541 307.278c0 14.483-11.783 26.27-26.27 26.27s-26.271-11.787-26.271-26.27 11.784-26.27 26.271-26.27 26.27 11.787 26.27 26.27m-26.272-117.97c-20.205 0-36.642-16.434-36.642-36.638 0-20.205 16.437-36.642 36.642-36.642 20.204 0 36.641 16.437 36.641 36.642 0 20.204-16.437 36.638-36.641 36.638m131.831 67.179c-14.487 0-26.27-11.788-26.27-26.271s11.783-26.27 26.27-26.27 26.27 11.787 26.27 26.27-11.783 26.271-26.27 26.271" style="fill:#fff"/></svg>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="154" height="250" preserveAspectRatio="xMidYMid" viewBox="0 0 256 416"><path d="M201.816 230.216c-16.186 0-30.697 7.171-40.634 18.461l-25.463-18.026c2.703-7.442 4.255-15.433 4.255-23.797 0-8.219-1.498-16.076-4.112-23.408l25.406-17.835c9.936 11.233 24.409 18.365 40.548 18.365 29.875 0 54.184-24.305 54.184-54.184s-24.309-54.184-54.184-54.184-54.184 24.305-54.184 54.184c0 5.348.808 10.505 2.258 15.389l-25.423 17.844c-10.62-13.175-25.911-22.374-43.333-25.182v-30.64c24.544-5.155 43.037-26.962 43.037-53.019C124.171 24.305 99.862 0 69.987 0S15.803 24.305 15.803 54.184c0 25.708 18.014 47.246 42.067 52.769v31.038C25.044 143.753 0 172.401 0 206.854c0 34.621 25.292 63.374 58.355 68.94v32.774c-24.299 5.341-42.552 27.011-42.552 52.894 0 29.879 24.309 54.184 54.184 54.184s54.184-24.305 54.184-54.184c0-25.883-18.253-47.553-42.552-52.894v-32.775a69.97 69.97 0 0 0 42.6-24.776l25.633 18.143c-1.423 4.84-2.22 9.946-2.22 15.24 0 29.879 24.309 54.184 54.184 54.184S256 314.279 256 284.4s-24.309-54.184-54.184-54.184m0-126.695c14.487 0 26.27 11.788 26.27 26.271s-11.783 26.27-26.27 26.27-26.27-11.787-26.27-26.27 11.783-26.271 26.27-26.271m-158.1-49.337c0-14.483 11.784-26.27 26.271-26.27s26.27 11.787 26.27 26.27-11.783 26.27-26.27 26.27-26.271-11.787-26.271-26.27m52.541 307.278c0 14.483-11.783 26.27-26.27 26.27s-26.271-11.787-26.271-26.27 11.784-26.27 26.271-26.27 26.27 11.787 26.27 26.27m-26.272-117.97c-20.205 0-36.642-16.434-36.642-36.638 0-20.205 16.437-36.642 36.642-36.642 20.204 0 36.641 16.437 36.641 36.642 0 20.204-16.437 36.638-36.641 36.638m131.831 67.179c-14.487 0-26.27-11.788-26.27-26.271s11.783-26.27 26.27-26.27 26.27 11.787 26.27 26.27-11.783 26.271-26.27 26.271" style="fill:#7A8291"/></svg>
|
||||
|
Before Width: | Height: | Size: 1.7 KiB After Width: | Height: | Size: 1.7 KiB |
5
frontend/src/assets/Logos/karpenter.svg
Normal file
@@ -0,0 +1,5 @@
|
||||
<svg role="img" viewBox="0 0 500 500" xmlns="http://www.w3.org/2000/svg">
|
||||
<g transform="translate(2.1186441,37.661017)">
|
||||
<path d="m 237.70492,0 c 76.95771,0 139.34426,62.386551 139.34426,139.34426 v 0 l -1.8e-4,197.00774 65.57395,-1.5e-4 c 31.68847,0 57.37705,25.68858 57.37705,57.37705 v 0 V 443 h -7.54607 c -18.22527,0 -33.09496,-14.59276 -33.43754,-32.81482 v 0 -8.22465 l -0.003,-0.40664 c -0.21715,-13.39321 -11.14191,-24.18352 -24.58687,-24.18352 v 0 H 106.55738 C 47.707363,377.37037 0,329.66301 0,270.81299 v 0 -131.46873 C 0,62.386551 62.386551,0 139.34426,0 v 0 z m -8.19672,41.018518 h -81.96722 l -1.76212,0.01428 C 87.741593,41.97378 40.983607,89.314381 40.983607,147.5759 v 0 123.20218 l 0.0088,1.08438 c 0.579068,35.71525 29.711746,64.48939 65.564993,64.48939 v 0 h 163.9344 c 36.2154,0 65.57377,-29.35838 65.57377,-65.57377 v 0 -123.20218 l -0.0143,-1.76213 C 335.11031,87.776505 287.76971,41.018518 229.5082,41.018518 Z m -81.63935,65.629632 c 9.34426,0 16.72131,7.62944 16.72131,17.22778 v 0 49.96055 l 56.80328,-58.57444 c 7.37705,-6.89111 17.45902,-7.38334 24.59017,-0.73834 v 0 0.24611 c 6.88524,6.39889 6.39344,16.98167 -1.22951,23.87278 v 0 l -42.04918,43.31556 48.93442,69.89555 c 5.90164,8.61389 4.18033,19.44278 -3.68852,24.61111 -8.60656,4.92223 -18.44262,2.21501 -24.34426,-6.39888 v 0 l -44.0164,-64.48112 -15,15.75112 v 40.36222 c 0,9.84444 -7.37705,17.22778 -16.72131,17.22778 -9.34426,0 -16.72131,-7.38334 -16.72131,-17.22778 v 0 -137.82222 c 0,-9.59834 7.37705,-17.22778 16.72131,-17.22778 z" fill="#5C62B0" fill-rule="evenodd" />
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.5 KiB |
@@ -1 +1 @@
|
||||
<svg height="1em" style="flex:none;line-height:1" viewBox="0 0 24 24" width="1em" xmlns="http://www.w3.org/2000/svg"><title>LangChain</title><path d="M8.373 14.502c.013-.06.024-.118.038-.17l.061.145c.115.28.229.557.506.714-.012.254-.334.357-.552.326-.048-.114-.115-.228-.255-.164-.143.056-.3-.01-.266-.185.333-.012.407-.371.468-.666zM18.385 9.245c-.318 0-.616.122-.839.342l-.902.887c-.243.24-.368.572-.343.913l.006.056c.032.262.149.498.337.682.13.128.273.21.447.266a.866.866 0 01-.247.777l-.056.055a2.022 2.022 0 01-1.355-1.555l-.01-.057-.046.037c-.03.024-.06.05-.088.078l-.902.887a1.156 1.156 0 000 1.65c.231.228.535.342.84.342.304 0 .607-.114.838-.341l.902-.888a1.156 1.156 0 00-.436-1.921.953.953 0 01.276-.842 2.062 2.062 0 011.371 1.57l.01.057.047-.037c.03-.024.06-.05.088-.078l.902-.888a1.155 1.155 0 000-1.65 1.188 1.188 0 00-.84-.342z" fill="#1C3C3C"></path><path clip-rule="evenodd" d="M17.901 6H6.1C2.736 6 0 8.692 0 12s2.736 6 6.099 6H17.9C21.264 18 24 15.308 24 12s-2.736-6-6.099-6zm-5.821 9.407c-.195.04-.414.047-.562-.106-.045.1-.136.077-.221.056a.797.797 0 00-.061-.014c-.01.025-.017.048-.026.073-.329.021-.575-.309-.732-.558a4.991 4.991 0 00-.473-.21c-.172-.07-.345-.14-.509-.23a2.218 2.218 0 00-.004.173c-.002.244-.004.503-.227.651-.007.295.236.292.476.29.207-.003.41-.005.447.184a.485.485 0 01-.05.003c-.046 0-.092 0-.127.034-.117.111-.242.063-.372.013-.12-.046-.243-.094-.367-.02a2.318 2.318 0 00-.262.154.97.97 0 01-.548.194c-.024-.036-.014-.059.006-.08a.562.562 0 00.043-.056c.019-.028.035-.057.051-.084.054-.095.103-.18.242-.22-.185-.029-.344.055-.5.137l-.004.002a4.21 4.21 0 01-.065.034c-.097.04-.154.009-.212-.023-.082-.045-.168-.092-.376.04-.04-.032-.02-.061.002-.086.091-.109.21-.125.345-.119-.351-.193-.604-.056-.81.055-.182.098-.327.176-.471-.012-.065.017-.102.063-.138.108-.015.02-.03.038-.047.055-.035-.039-.027-.083-.018-.128l.005-.026a.242.242 0 00.003-.03l-.027-.01c-.053-.022-.105-.044-.09-.124-.117-.04-.2.03-.286.094-.054-.041-.01-.095.032-.145a.279.279 0 00.045-.065c.038-.065.103-.067.166-.069.054-.001.108-.003.145-.042.133-.075.297-.036.462.003.121.028.242.057.354.042.203.025.454-.18.352-.385-.186-.233-.184-.528-.183-.813v-.143c-.016-.108-.172-.233-.328-.358-.12-.095-.24-.191-.298-.28-.16-.177-.285-.382-.409-.585l-.015-.024c-.212-.404-.297-.86-.382-1.315-.103-.546-.205-1.09-.526-1.54-.266.144-.612.075-.841-.118-.12.107-.13.247-.138.396l-.001.014c-.297-.292-.26-.844-.023-1.17.097-.128.213-.233.342-.326.03-.021.04-.042.039-.074.235-1.04 1.836-.839 2.342-.103.167.206.281.442.395.678.137.283.273.566.5.795.22.237.452.463.684.689.359.35.718.699 1.032 1.089.49.587.839 1.276 1.144 1.97.05.092.08.193.11.293.044.15.089.299.2.417.026.035.084.088.149.148.156.143.357.328.289.409.009.019.027.04.05.06.032.028.074.058.116.088.122.087.25.178.16.25zm7.778-3.545l-.902.887c-.24.237-.537.413-.859.51l-.017.005-.006.015A2.021 2.021 0 0117.6 14l-.902.888c-.393.387-.916.6-1.474.6-.557 0-1.08-.213-1.474-.6a2.03 2.03 0 010-2.9l.902-.888c.242-.238.531-.409.859-.508l.016-.004.006-.016c.105-.272.265-.516.475-.724l.902-.887c.393-.387.917-.6 1.474-.6.558 0 1.08.213 1.474.6.394.387.61.902.61 1.45 0 .549-.216 1.064-.61 1.45v.001z" fill="#1C3C3C" fill-rule="evenodd"></path></svg>
|
||||
<svg height="1em" style="flex:none;line-height:1" viewBox="0 0 24 24" width="1em" xmlns="http://www.w3.org/2000/svg"><title>LangChain</title><path d="M8.373 14.502c.013-.06.024-.118.038-.17l.061.145c.115.28.229.557.506.714-.012.254-.334.357-.552.326-.048-.114-.115-.228-.255-.164-.143.056-.3-.01-.266-.185.333-.012.407-.371.468-.666zM18.385 9.245c-.318 0-.616.122-.839.342l-.902.887c-.243.24-.368.572-.343.913l.006.056c.032.262.149.498.337.682.13.128.273.21.447.266a.866.866 0 01-.247.777l-.056.055a2.022 2.022 0 01-1.355-1.555l-.01-.057-.046.037c-.03.024-.06.05-.088.078l-.902.887a1.156 1.156 0 000 1.65c.231.228.535.342.84.342.304 0 .607-.114.838-.341l.902-.888a1.156 1.156 0 00-.436-1.921.953.953 0 01.276-.842 2.062 2.062 0 011.371 1.57l.01.057.047-.037c.03-.024.06-.05.088-.078l.902-.888a1.155 1.155 0 000-1.65 1.188 1.188 0 00-.84-.342z" fill="#3B8686"></path><path clip-rule="evenodd" d="M17.901 6H6.1C2.736 6 0 8.692 0 12s2.736 6 6.099 6H17.9C21.264 18 24 15.308 24 12s-2.736-6-6.099-6zm-5.821 9.407c-.195.04-.414.047-.562-.106-.045.1-.136.077-.221.056a.797.797 0 00-.061-.014c-.01.025-.017.048-.026.073-.329.021-.575-.309-.732-.558a4.991 4.991 0 00-.473-.21c-.172-.07-.345-.14-.509-.23a2.218 2.218 0 00-.004.173c-.002.244-.004.503-.227.651-.007.295.236.292.476.29.207-.003.41-.005.447.184a.485.485 0 01-.05.003c-.046 0-.092 0-.127.034-.117.111-.242.063-.372.013-.12-.046-.243-.094-.367-.02a2.318 2.318 0 00-.262.154.97.97 0 01-.548.194c-.024-.036-.014-.059.006-.08a.562.562 0 00.043-.056c.019-.028.035-.057.051-.084.054-.095.103-.18.242-.22-.185-.029-.344.055-.5.137l-.004.002a4.21 4.21 0 01-.065.034c-.097.04-.154.009-.212-.023-.082-.045-.168-.092-.376.04-.04-.032-.02-.061.002-.086.091-.109.21-.125.345-.119-.351-.193-.604-.056-.81.055-.182.098-.327.176-.471-.012-.065.017-.102.063-.138.108-.015.02-.03.038-.047.055-.035-.039-.027-.083-.018-.128l.005-.026a.242.242 0 00.003-.03l-.027-.01c-.053-.022-.105-.044-.09-.124-.117-.04-.2.03-.286.094-.054-.041-.01-.095.032-.145a.279.279 0 00.045-.065c.038-.065.103-.067.166-.069.054-.001.108-.003.145-.042.133-.075.297-.036.462.003.121.028.242.057.354.042.203.025.454-.18.352-.385-.186-.233-.184-.528-.183-.813v-.143c-.016-.108-.172-.233-.328-.358-.12-.095-.24-.191-.298-.28-.16-.177-.285-.382-.409-.585l-.015-.024c-.212-.404-.297-.86-.382-1.315-.103-.546-.205-1.09-.526-1.54-.266.144-.612.075-.841-.118-.12.107-.13.247-.138.396l-.001.014c-.297-.292-.26-.844-.023-1.17.097-.128.213-.233.342-.326.03-.021.04-.042.039-.074.235-1.04 1.836-.839 2.342-.103.167.206.281.442.395.678.137.283.273.566.5.795.22.237.452.463.684.689.359.35.718.699 1.032 1.089.49.587.839 1.276 1.144 1.97.05.092.08.193.11.293.044.15.089.299.2.417.026.035.084.088.149.148.156.143.357.328.289.409.009.019.027.04.05.06.032.028.074.058.116.088.122.087.25.178.16.25zm7.778-3.545l-.902.887c-.24.237-.537.413-.859.51l-.017.005-.006.015A2.021 2.021 0 0117.6 14l-.902.888c-.393.387-.916.6-1.474.6-.557 0-1.08-.213-1.474-.6a2.03 2.03 0 010-2.9l.902-.888c.242-.238.531-.409.859-.508l.016-.004.006-.016c.105-.272.265-.516.475-.724l.902-.887c.393-.387.917-.6 1.474-.6.558 0 1.08.213 1.474.6.394.387.61.902.61 1.45 0 .549-.216 1.064-.61 1.45v.001z" fill="#3B8686" fill-rule="evenodd"></path></svg>
|
||||
|
Before Width: | Height: | Size: 3.1 KiB After Width: | Height: | Size: 3.1 KiB |
3
frontend/src/assets/Logos/meta.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg role="img" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M6.915 4.03c-1.968 0-3.683 1.28-4.871 3.113C.704 9.208 0 11.883 0 14.449c0 .706.07 1.369.21 1.973a6.624 6.624 0 0 0 .265.86 5.297 5.297 0 0 0 .371.761c.696 1.159 1.818 1.927 3.593 1.927 1.497 0 2.633-.671 3.965-2.444.76-1.012 1.144-1.626 2.663-4.32l.756-1.339.186-.325c.061.1.121.196.183.3l2.152 3.595c.724 1.21 1.665 2.556 2.47 3.314 1.046.987 1.992 1.22 3.06 1.22 1.075 0 1.876-.355 2.455-.843a3.743 3.743 0 0 0 .81-.973c.542-.939.861-2.127.861-3.745 0-2.72-.681-5.357-2.084-7.45-1.282-1.912-2.957-2.93-4.716-2.93-1.047 0-2.088.467-3.053 1.308-.652.57-1.257 1.29-1.82 2.05-.69-.875-1.335-1.547-1.958-2.056-1.182-.966-2.315-1.303-3.454-1.303zm10.16 2.053c1.147 0 2.188.758 2.992 1.999 1.132 1.748 1.647 4.195 1.647 6.4 0 1.548-.368 2.9-1.839 2.9-.58 0-1.027-.23-1.664-1.004-.496-.601-1.343-1.878-2.832-4.358l-.617-1.028a44.908 44.908 0 0 0-1.255-1.98c.07-.109.141-.224.211-.327 1.12-1.667 2.118-2.602 3.358-2.602zm-10.201.553c1.265 0 2.058.791 2.675 1.446.307.327.737.871 1.234 1.579l-1.02 1.566c-.757 1.163-1.882 3.017-2.837 4.338-1.191 1.649-1.81 1.817-2.486 1.817-.524 0-1.038-.237-1.383-.794-.263-.426-.464-1.13-.464-2.046 0-2.221.63-4.535 1.66-6.088.454-.687.964-1.226 1.533-1.533a2.264 2.264 0 0 1 1.088-.285z" fill="#0467DF" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.3 KiB |
3
frontend/src/assets/Logos/modal.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg role="img" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M4.89 5.57 0 14.002l2.521 4.4h5.05l4.396-7.718 4.512 7.709 4.996.037L24 14.057l-4.857-8.452-5.073-.015-2.076 3.598L9.94 5.57Zm.837.729h3.787l1.845 3.252H7.572Zm9.189.021 3.803.012 4.228 7.355-3.736-.027zm-9.82.346L6.94 9.914l-4.209 7.389-1.892-3.3Zm9.187.014 4.297 7.343-1.892 3.282-4.3-7.344zm-6.713 3.6h3.79l-4.212 7.394H3.361Zm11.64 4.109 3.74.027-1.893 3.281-3.74-.027z" fill="#3E9C2A" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 482 B |
@@ -1 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="#fff" viewBox="0 0 24 24"><title>Ollama</title><path d="M16.361 10.26a.9.9 0 0 0-.558.47l-.072.148.001.207c0 .193.004.217.059.353.076.193.152.312.291.448.24.238.51.3.872.205a.86.86 0 0 0 .517-.436.75.75 0 0 0 .08-.498c-.064-.453-.33-.782-.724-.897a1.1 1.1 0 0 0-.466 0m-9.203.005c-.305.096-.533.32-.65.639a1.2 1.2 0 0 0-.06.52c.057.309.31.59.598.667.362.095.632.033.872-.205.14-.136.215-.255.291-.448.055-.136.059-.16.059-.353l.001-.207-.072-.148a.9.9 0 0 0-.565-.472 1 1 0 0 0-.474.007m4.184 2c-.131.071-.223.25-.195.383.031.143.157.288.353.407.105.063.112.072.117.136.004.038-.01.146-.029.243-.02.094-.036.194-.036.222.002.074.07.195.143.253.064.052.076.054.255.059.164.005.198.001.264-.03.169-.082.212-.234.15-.525-.052-.243-.042-.28.087-.355.137-.08.281-.219.324-.314a.365.365 0 0 0-.175-.48.4.4 0 0 0-.181-.033c-.126 0-.207.03-.355.124l-.085.053-.053-.032c-.219-.13-.259-.145-.391-.143a.4.4 0 0 0-.193.032m.39-2.195c-.373.036-.475.05-.654.086a4.5 4.5 0 0 0-.951.328c-.94.46-1.589 1.226-1.787 2.114-.04.176-.045.234-.045.53 0 .294.005.357.043.524.264 1.16 1.332 2.017 2.714 2.173.3.033 1.596.033 1.896 0 1.11-.125 2.064-.727 2.493-1.571.114-.226.169-.372.22-.602.039-.167.044-.23.044-.523 0-.297-.005-.355-.045-.531-.288-1.29-1.539-2.304-3.072-2.497a7 7 0 0 0-.855-.031zm.645.937a3.3 3.3 0 0 1 1.44.514c.223.148.537.458.671.662.166.251.26.508.303.82.02.143.01.251-.043.482-.08.345-.332.705-.672.957a3 3 0 0 1-.689.348c-.382.122-.632.144-1.525.138-.582-.006-.686-.01-.853-.042q-.856-.16-1.35-.68c-.264-.28-.385-.535-.45-.946-.03-.192.025-.509.137-.776.136-.326.488-.73.836-.963.403-.269.934-.46 1.422-.512.187-.02.586-.02.773-.002m-5.503-11a1.65 1.65 0 0 0-.683.298C5.617.74 5.173 1.666 4.985 2.819c-.07.436-.119 1.04-.119 1.503 0 .544.064 1.24.155 1.721.02.107.031.202.023.208l-.187.152a5.3 5.3 0 0 0-.949 1.02 5.5 5.5 0 0 0-.94 2.339 6.6 6.6 0 0 0-.023 1.357c.091.78.325 1.438.727 2.04l.13.195-.037.064c-.269.452-.498 1.105-.605 1.732-.084.496-.095.629-.095 1.294 0 .67.009.803.088 1.266.095.555.288 1.143.503 1.534.071.128.243.393.264.407.007.003-.014.067-.046.141a7.4 7.4 0 0 0-.548 1.873 5 5 0 0 0-.071.991c0 .56.031.832.148 1.279L3.42 24h1.478l-.05-.091c-.297-.552-.325-1.575-.068-2.597.117-.472.25-.819.498-1.296l.148-.29v-.177c0-.165-.003-.184-.057-.293a.9.9 0 0 0-.194-.25 1.7 1.7 0 0 1-.385-.543c-.424-.92-.506-2.286-.208-3.451.124-.486.329-.918.544-1.154a.8.8 0 0 0 .223-.531c0-.195-.07-.355-.224-.522a3.14 3.14 0 0 1-.817-1.729c-.14-.96.114-2.005.69-2.834.563-.814 1.353-1.336 2.237-1.475.199-.033.57-.028.776.01.226.04.367.028.512-.041.179-.085.268-.19.374-.431.093-.215.165-.333.36-.576.234-.29.46-.489.822-.729.413-.27.884-.467 1.352-.561.17-.035.25-.04.569-.04s.398.005.569.04a4.07 4.07 0 0 1 1.914.997c.117.109.398.457.488.602.034.057.095.177.132.267.105.241.195.346.374.43.14.068.286.082.503.045.343-.058.607-.053.943.016 1.144.23 2.14 1.173 2.581 2.437.385 1.108.276 2.267-.296 3.153-.097.15-.193.27-.333.419-.301.322-.301.722-.001 1.053.493.539.801 1.866.708 3.036-.062.772-.26 1.463-.533 1.854a2 2 0 0 1-.224.258.9.9 0 0 0-.194.25c-.054.109-.057.128-.057.293v.178l.148.29c.248.476.38.823.498 1.295.253 1.008.231 2.01-.059 2.581a1 1 0 0 0-.044.098c0 .006.329.009.732.009h.73l.02-.074.036-.134c.019-.076.057-.3.088-.516a9 9 0 0 0 0-1.258c-.11-.875-.295-1.57-.597-2.226-.032-.074-.053-.138-.046-.141a1.4 1.4 0 0 0 .108-.152c.376-.569.607-1.284.724-2.228.031-.26.031-1.378 0-1.628-.083-.645-.182-1.082-.348-1.525a6 6 0 0 0-.329-.7l-.038-.064.131-.194c.402-.604.636-1.262.727-2.04a6.6 6.6 0 0 0-.024-1.358 5.5 5.5 0 0 0-.939-2.339 5.3 5.3 0 0 0-.95-1.02l-.186-.152a.7.7 0 0 1 .023-.208c.208-1.087.201-2.443-.017-3.503-.19-.924-.535-1.658-.98-2.082-.354-.338-.716-.482-1.15-.455-.996.059-1.8 1.205-2.116 3.01a7 7 0 0 0-.097.726c0 .036-.007.066-.015.066a1 1 0 0 1-.149-.078A4.86 4.86 0 0 0 12 3.03c-.832 0-1.687.243-2.456.698a1 1 0 0 1-.148.078c-.008 0-.015-.03-.015-.066a7 7 0 0 0-.097-.725C8.997 1.392 8.337.319 7.46.048a2 2 0 0 0-.585-.041Zm.293 1.402c.248.197.523.759.682 1.388.03.113.06.244.069.292.007.047.026.152.041.233.067.365.098.76.102 1.24l.002.475-.12.175-.118.178h-.278c-.324 0-.646.041-.954.124l-.238.06c-.033.007-.038-.003-.057-.144a8.4 8.4 0 0 1 .016-2.323c.124-.788.413-1.501.696-1.711.067-.05.079-.049.157.013m9.825-.012c.17.126.358.46.498.888.28.854.36 2.028.212 3.145-.019.14-.024.151-.057.144l-.238-.06a3.7 3.7 0 0 0-.954-.124h-.278l-.119-.178-.119-.175.002-.474c.004-.669.066-1.19.214-1.772.157-.623.434-1.185.68-1.382.078-.062.09-.063.159-.012"/></svg>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="#7A8291" viewBox="0 0 24 24"><title>Ollama</title><path d="M16.361 10.26a.9.9 0 0 0-.558.47l-.072.148.001.207c0 .193.004.217.059.353.076.193.152.312.291.448.24.238.51.3.872.205a.86.86 0 0 0 .517-.436.75.75 0 0 0 .08-.498c-.064-.453-.33-.782-.724-.897a1.1 1.1 0 0 0-.466 0m-9.203.005c-.305.096-.533.32-.65.639a1.2 1.2 0 0 0-.06.52c.057.309.31.59.598.667.362.095.632.033.872-.205.14-.136.215-.255.291-.448.055-.136.059-.16.059-.353l.001-.207-.072-.148a.9.9 0 0 0-.565-.472 1 1 0 0 0-.474.007m4.184 2c-.131.071-.223.25-.195.383.031.143.157.288.353.407.105.063.112.072.117.136.004.038-.01.146-.029.243-.02.094-.036.194-.036.222.002.074.07.195.143.253.064.052.076.054.255.059.164.005.198.001.264-.03.169-.082.212-.234.15-.525-.052-.243-.042-.28.087-.355.137-.08.281-.219.324-.314a.365.365 0 0 0-.175-.48.4.4 0 0 0-.181-.033c-.126 0-.207.03-.355.124l-.085.053-.053-.032c-.219-.13-.259-.145-.391-.143a.4.4 0 0 0-.193.032m.39-2.195c-.373.036-.475.05-.654.086a4.5 4.5 0 0 0-.951.328c-.94.46-1.589 1.226-1.787 2.114-.04.176-.045.234-.045.53 0 .294.005.357.043.524.264 1.16 1.332 2.017 2.714 2.173.3.033 1.596.033 1.896 0 1.11-.125 2.064-.727 2.493-1.571.114-.226.169-.372.22-.602.039-.167.044-.23.044-.523 0-.297-.005-.355-.045-.531-.288-1.29-1.539-2.304-3.072-2.497a7 7 0 0 0-.855-.031zm.645.937a3.3 3.3 0 0 1 1.44.514c.223.148.537.458.671.662.166.251.26.508.303.82.02.143.01.251-.043.482-.08.345-.332.705-.672.957a3 3 0 0 1-.689.348c-.382.122-.632.144-1.525.138-.582-.006-.686-.01-.853-.042q-.856-.16-1.35-.68c-.264-.28-.385-.535-.45-.946-.03-.192.025-.509.137-.776.136-.326.488-.73.836-.963.403-.269.934-.46 1.422-.512.187-.02.586-.02.773-.002m-5.503-11a1.65 1.65 0 0 0-.683.298C5.617.74 5.173 1.666 4.985 2.819c-.07.436-.119 1.04-.119 1.503 0 .544.064 1.24.155 1.721.02.107.031.202.023.208l-.187.152a5.3 5.3 0 0 0-.949 1.02 5.5 5.5 0 0 0-.94 2.339 6.6 6.6 0 0 0-.023 1.357c.091.78.325 1.438.727 2.04l.13.195-.037.064c-.269.452-.498 1.105-.605 1.732-.084.496-.095.629-.095 1.294 0 .67.009.803.088 1.266.095.555.288 1.143.503 1.534.071.128.243.393.264.407.007.003-.014.067-.046.141a7.4 7.4 0 0 0-.548 1.873 5 5 0 0 0-.071.991c0 .56.031.832.148 1.279L3.42 24h1.478l-.05-.091c-.297-.552-.325-1.575-.068-2.597.117-.472.25-.819.498-1.296l.148-.29v-.177c0-.165-.003-.184-.057-.293a.9.9 0 0 0-.194-.25 1.7 1.7 0 0 1-.385-.543c-.424-.92-.506-2.286-.208-3.451.124-.486.329-.918.544-1.154a.8.8 0 0 0 .223-.531c0-.195-.07-.355-.224-.522a3.14 3.14 0 0 1-.817-1.729c-.14-.96.114-2.005.69-2.834.563-.814 1.353-1.336 2.237-1.475.199-.033.57-.028.776.01.226.04.367.028.512-.041.179-.085.268-.19.374-.431.093-.215.165-.333.36-.576.234-.29.46-.489.822-.729.413-.27.884-.467 1.352-.561.17-.035.25-.04.569-.04s.398.005.569.04a4.07 4.07 0 0 1 1.914.997c.117.109.398.457.488.602.034.057.095.177.132.267.105.241.195.346.374.43.14.068.286.082.503.045.343-.058.607-.053.943.016 1.144.23 2.14 1.173 2.581 2.437.385 1.108.276 2.267-.296 3.153-.097.15-.193.27-.333.419-.301.322-.301.722-.001 1.053.493.539.801 1.866.708 3.036-.062.772-.26 1.463-.533 1.854a2 2 0 0 1-.224.258.9.9 0 0 0-.194.25c-.054.109-.057.128-.057.293v.178l.148.29c.248.476.38.823.498 1.295.253 1.008.231 2.01-.059 2.581a1 1 0 0 0-.044.098c0 .006.329.009.732.009h.73l.02-.074.036-.134c.019-.076.057-.3.088-.516a9 9 0 0 0 0-1.258c-.11-.875-.295-1.57-.597-2.226-.032-.074-.053-.138-.046-.141a1.4 1.4 0 0 0 .108-.152c.376-.569.607-1.284.724-2.228.031-.26.031-1.378 0-1.628-.083-.645-.182-1.082-.348-1.525a6 6 0 0 0-.329-.7l-.038-.064.131-.194c.402-.604.636-1.262.727-2.04a6.6 6.6 0 0 0-.024-1.358 5.5 5.5 0 0 0-.939-2.339 5.3 5.3 0 0 0-.95-1.02l-.186-.152a.7.7 0 0 1 .023-.208c.208-1.087.201-2.443-.017-3.503-.19-.924-.535-1.658-.98-2.082-.354-.338-.716-.482-1.15-.455-.996.059-1.8 1.205-2.116 3.01a7 7 0 0 0-.097.726c0 .036-.007.066-.015.066a1 1 0 0 1-.149-.078A4.86 4.86 0 0 0 12 3.03c-.832 0-1.687.243-2.456.698a1 1 0 0 1-.148.078c-.008 0-.015-.03-.015-.066a7 7 0 0 0-.097-.725C8.997 1.392 8.337.319 7.46.048a2 2 0 0 0-.585-.041Zm.293 1.402c.248.197.523.759.682 1.388.03.113.06.244.069.292.007.047.026.152.041.233.067.365.098.76.102 1.24l.002.475-.12.175-.118.178h-.278c-.324 0-.646.041-.954.124l-.238.06c-.033.007-.038-.003-.057-.144a8.4 8.4 0 0 1 .016-2.323c.124-.788.413-1.501.696-1.711.067-.05.079-.049.157.013m9.825-.012c.17.126.358.46.498.888.28.854.36 2.028.212 3.145-.019.14-.024.151-.057.144l-.238-.06a3.7 3.7 0 0 0-.954-.124h-.278l-.119-.178-.119-.175.002-.474c.004-.669.066-1.19.214-1.772.157-.623.434-1.185.68-1.382.078-.062.09-.063.159-.012"/></svg>
|
||||
|
Before Width: | Height: | Size: 4.4 KiB After Width: | Height: | Size: 4.4 KiB |
@@ -1 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 48"><path fill="#fff" d="M44.559 19.646a11.957 11.957 0 0 0-1.028-9.822 12.094 12.094 0 0 0-13.026-5.802A11.962 11.962 0 0 0 21.485 0 12.097 12.097 0 0 0 9.95 8.373a11.964 11.964 0 0 0-7.997 5.8A12.097 12.097 0 0 0 3.44 28.356a11.957 11.957 0 0 0 1.028 9.822 12.094 12.094 0 0 0 13.026 5.802 11.953 11.953 0 0 0 9.02 4.02 12.096 12.096 0 0 0 11.54-8.379 11.964 11.964 0 0 0 7.997-5.8 12.099 12.099 0 0 0-1.491-14.177zM26.517 44.863a8.966 8.966 0 0 1-5.759-2.082 6.85 6.85 0 0 0 .284-.16L30.6 37.1c.49-.278.79-.799.786-1.361V22.265l4.04 2.332a.141.141 0 0 1 .078.111v11.16a9.006 9.006 0 0 1-8.987 8.995zM7.191 36.608a8.957 8.957 0 0 1-1.073-6.027c.071.042.195.119.284.17l9.558 5.52a1.556 1.556 0 0 0 1.57 0l11.67-6.738v4.665a.15.15 0 0 1-.057.124l-9.662 5.579a9.006 9.006 0 0 1-12.288-3.293zM4.675 15.744a8.966 8.966 0 0 1 4.682-3.943c0 .082-.005.228-.005.33v11.042a1.555 1.555 0 0 0 .785 1.359l11.669 6.736-4.04 2.333a.143.143 0 0 1-.136.012L7.967 28.03a9.006 9.006 0 0 1-3.293-12.284zm33.19 7.724L26.196 16.73l4.04-2.331a.143.143 0 0 1 .136-.012l9.664 5.579c4.302 2.485 5.776 7.989 3.29 12.29a8.991 8.991 0 0 1-4.68 3.943V24.827a1.553 1.553 0 0 0-.78-1.36zm4.02-6.051c-.07-.044-.195-.119-.283-.17l-9.558-5.52a1.556 1.556 0 0 0-1.57 0l-11.67 6.738V13.8a.15.15 0 0 1 .057-.124l9.662-5.574a8.995 8.995 0 0 1 13.36 9.315zm-25.277 8.315-4.04-2.333a.141.141 0 0 1-.079-.11v-11.16a8.997 8.997 0 0 1 14.753-6.91c-.073.04-.2.11-.283.161L17.4 10.9a1.552 1.552 0 0 0-.786 1.36l-.006 13.469zM18.803 21l5.198-3.002 5.197 3V27l-5.197 3-5.198-3z"/></svg>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 48"><path fill="#7A8291" d="M44.559 19.646a11.957 11.957 0 0 0-1.028-9.822 12.094 12.094 0 0 0-13.026-5.802A11.962 11.962 0 0 0 21.485 0 12.097 12.097 0 0 0 9.95 8.373a11.964 11.964 0 0 0-7.997 5.8A12.097 12.097 0 0 0 3.44 28.356a11.957 11.957 0 0 0 1.028 9.822 12.094 12.094 0 0 0 13.026 5.802 11.953 11.953 0 0 0 9.02 4.02 12.096 12.096 0 0 0 11.54-8.379 11.964 11.964 0 0 0 7.997-5.8 12.099 12.099 0 0 0-1.491-14.177zM26.517 44.863a8.966 8.966 0 0 1-5.759-2.082 6.85 6.85 0 0 0 .284-.16L30.6 37.1c.49-.278.79-.799.786-1.361V22.265l4.04 2.332a.141.141 0 0 1 .078.111v11.16a9.006 9.006 0 0 1-8.987 8.995zM7.191 36.608a8.957 8.957 0 0 1-1.073-6.027c.071.042.195.119.284.17l9.558 5.52a1.556 1.556 0 0 0 1.57 0l11.67-6.738v4.665a.15.15 0 0 1-.057.124l-9.662 5.579a9.006 9.006 0 0 1-12.288-3.293zM4.675 15.744a8.966 8.966 0 0 1 4.682-3.943c0 .082-.005.228-.005.33v11.042a1.555 1.555 0 0 0 .785 1.359l11.669 6.736-4.04 2.333a.143.143 0 0 1-.136.012L7.967 28.03a9.006 9.006 0 0 1-3.293-12.284zm33.19 7.724L26.196 16.73l4.04-2.331a.143.143 0 0 1 .136-.012l9.664 5.579c4.302 2.485 5.776 7.989 3.29 12.29a8.991 8.991 0 0 1-4.68 3.943V24.827a1.553 1.553 0 0 0-.78-1.36zm4.02-6.051c-.07-.044-.195-.119-.283-.17l-9.558-5.52a1.556 1.556 0 0 0-1.57 0l-11.67 6.738V13.8a.15.15 0 0 1 .057-.124l9.662-5.574a8.995 8.995 0 0 1 13.36 9.315zm-25.277 8.315-4.04-2.333a.141.141 0 0 1-.079-.11v-11.16a8.997 8.997 0 0 1 14.753-6.91c-.073.04-.2.11-.283.161L17.4 10.9a1.552 1.552 0 0 0-.786 1.36l-.006 13.469zM18.803 21l5.198-3.002 5.197 3V27l-5.197 3-5.198-3z"/></svg>
|
||||
|
Before Width: | Height: | Size: 1.6 KiB After Width: | Height: | Size: 1.6 KiB |
@@ -1 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="#fff" viewBox="0 0 24 24"><title>OpenRouter</title><path d="M16.778 1.844v1.919q-.569-.026-1.138-.032-.708-.008-1.415.037c-1.93.126-4.023.728-6.149 2.237-2.911 2.066-2.731 1.95-4.14 2.75-.396.223-1.342.574-2.185.798-.841.225-1.753.333-1.751.333v4.229s.768.108 1.61.333c.842.224 1.789.575 2.185.799 1.41.798 1.228.683 4.14 2.75 2.126 1.509 4.22 2.11 6.148 2.236.88.058 1.716.041 2.555.005v1.918l7.222-4.168-7.222-4.17v2.176c-.86.038-1.611.065-2.278.021-1.364-.09-2.417-.357-3.979-1.465-2.244-1.593-2.866-2.027-3.68-2.508.889-.518 1.449-.906 3.822-2.59 1.56-1.109 2.614-1.377 3.978-1.466.667-.044 1.418-.017 2.278.02v2.176L24 6.014Z"/></svg>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="#7A8291" viewBox="0 0 24 24"><title>OpenRouter</title><path d="M16.778 1.844v1.919q-.569-.026-1.138-.032-.708-.008-1.415.037c-1.93.126-4.023.728-6.149 2.237-2.911 2.066-2.731 1.95-4.14 2.75-.396.223-1.342.574-2.185.798-.841.225-1.753.333-1.751.333v4.229s.768.108 1.61.333c.842.224 1.789.575 2.185.799 1.41.798 1.228.683 4.14 2.75 2.126 1.509 4.22 2.11 6.148 2.236.88.058 1.716.041 2.555.005v1.918l7.222-4.168-7.222-4.17v2.176c-.86.038-1.611.065-2.278.021-1.364-.09-2.417-.357-3.979-1.465-2.244-1.593-2.866-2.027-3.68-2.508.889-.518 1.449-.906 3.822-2.59 1.56-1.109 2.614-1.377 3.978-1.466.667-.044 1.418-.017 2.278.02v2.176L24 6.014Z"/></svg>
|
||||
|
Before Width: | Height: | Size: 685 B After Width: | Height: | Size: 688 B |
3
frontend/src/assets/Logos/podman.svg
Normal file
|
After Width: | Height: | Size: 9.7 KiB |
10
frontend/src/assets/Logos/sglang.svg
Normal file
@@ -0,0 +1,10 @@
|
||||
<svg role="img" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
||||
<g fill="none" stroke="#A5300F" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path d="M17.4 7.7c0 4.6-4.7 4.9-4.7 8.6" />
|
||||
<path d="M8 10.7c3.3 0 4.2 2.4 4.2 5.6" />
|
||||
<circle cx="17.4" cy="4.6" r="3.1" fill="#FADDCD" />
|
||||
<circle cx="4.9" cy="10.7" r="3.1" fill="#FADDCD" />
|
||||
<rect x="7.9" y="16.3" width="9.6" height="6.6" rx="1.6" fill="#FADDCD" />
|
||||
<path d="M11.3 18.6 9.9 19.9l1.4 1.3M14.1 18.6l1.4 1.3-1.4 1.3M13.1 18.3l-1.2 3.3" stroke-width="1.1" />
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 571 B |
@@ -1 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 1155 1000"><path fill="#fff" d="m577.3 0 577.4 1000H0z"/></svg>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 1155 1000"><path fill="#7A8291" d="m577.3 0 577.4 1000H0z"/></svg>
|
||||
|
Before Width: | Height: | Size: 128 B After Width: | Height: | Size: 131 B |
4
frontend/src/assets/Logos/vllm.svg
Normal file
@@ -0,0 +1,4 @@
|
||||
<svg role="img" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="m23.6 0-8.721 4.59L9.829 24h7.41z" fill="#30A2FF" />
|
||||
<path d="M9.83 24V5.142H.4z" fill="#FDB515" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 190 B |
@@ -1 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="800" height="800" fill="#fff" stroke="#fff" viewBox="0 0 24 24"><path d="M20.98 11.802a1 1 0 0 0-.738-.771l-6.86-1.716 2.537-5.921a1 1 0 0 0-.317-1.192.996.996 0 0 0-1.234.024l-11 9a1 1 0 0 0 .39 1.744l6.719 1.681-3.345 5.854A1 1 0 0 0 8 22a1 1 0 0 0 .6-.2l12-9a1 1 0 0 0 .38-.998z"/></svg>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="800" height="800" fill="#7A8291" stroke="#7A8291" viewBox="0 0 24 24"><path d="M20.98 11.802a1 1 0 0 0-.738-.771l-6.86-1.716 2.537-5.921a1 1 0 0 0-.317-1.192.996.996 0 0 0-1.234.024l-11 9a1 1 0 0 0 .39 1.744l6.719 1.681-3.345 5.854A1 1 0 0 0 8 22a1 1 0 0 0 .6-.2l12-9a1 1 0 0 0 .38-.998z"/></svg>
|
||||
|
Before Width: | Height: | Size: 337 B After Width: | Height: | Size: 343 B |
@@ -1,6 +1,7 @@
|
||||
import { useCallback, useMemo, useState } from 'react';
|
||||
import { useHistory, useLocation } from 'react-router-dom';
|
||||
import { Row, Select, Spin } from 'antd';
|
||||
import { navigate } from 'lib/router/navigation';
|
||||
import { useAppLocation } from 'lib/router/useAppLocation';
|
||||
import {
|
||||
getValuesFromQueryParams,
|
||||
setQueryParamsFromOptions,
|
||||
@@ -35,8 +36,7 @@ export function FilterSelect({
|
||||
useCeleryFilterOptions(filterType);
|
||||
|
||||
const urlQuery = useUrlQuery();
|
||||
const history = useHistory();
|
||||
const location = useLocation();
|
||||
const location = useAppLocation();
|
||||
|
||||
// Add state to track the current search input
|
||||
const [searchValue, setSearchValue] = useState<string>('');
|
||||
@@ -66,7 +66,7 @@ export function FilterSelect({
|
||||
setQueryParamsFromOptions(
|
||||
value as string[],
|
||||
urlQuery,
|
||||
history,
|
||||
navigate,
|
||||
location,
|
||||
queryParam,
|
||||
);
|
||||
@@ -77,7 +77,6 @@ export function FilterSelect({
|
||||
handleSearch,
|
||||
shouldSetQueryParams,
|
||||
urlQuery,
|
||||
history,
|
||||
location,
|
||||
queryParam,
|
||||
onChange,
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { useHistory, useLocation } from 'react-router-dom';
|
||||
import { Select, Spin } from 'antd';
|
||||
import { navigate } from 'lib/router/navigation';
|
||||
import { useAppLocation } from 'lib/router/useAppLocation';
|
||||
import { Typography } from '@signozhq/ui/typography';
|
||||
import { SelectMaxTagPlaceholder } from 'components/MessagingQueues/MQCommon/MQCommon';
|
||||
import { QueryParams } from 'constants/query';
|
||||
@@ -16,8 +17,7 @@ import './CeleryTaskConfigOptions.styles.scss';
|
||||
function CeleryTaskConfigOptions(): JSX.Element {
|
||||
const { handleSearch, isFetching, options } =
|
||||
useCeleryFilterOptions('celery.task_name');
|
||||
const history = useHistory();
|
||||
const location = useLocation();
|
||||
const location = useAppLocation();
|
||||
|
||||
const urlQuery = useUrlQuery();
|
||||
|
||||
@@ -52,7 +52,7 @@ function CeleryTaskConfigOptions(): JSX.Element {
|
||||
setQueryParamsFromOptions(
|
||||
value,
|
||||
urlQuery,
|
||||
history,
|
||||
navigate,
|
||||
location,
|
||||
QueryParams.taskName,
|
||||
);
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import { useCallback, useMemo, useState } from 'react';
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
import { useHistory, useLocation } from 'react-router-dom';
|
||||
import { Color } from '@signozhq/design-tokens';
|
||||
import { navigate } from 'lib/router/navigation';
|
||||
import { useAppLocation } from 'lib/router/useAppLocation';
|
||||
import { QueryParams } from 'constants/query';
|
||||
import { PANEL_TYPES } from 'constants/queryBuilder';
|
||||
import { ViewMenuAction } from 'container/WidgetCard/config';
|
||||
@@ -50,8 +51,7 @@ function CeleryTaskBar({
|
||||
queryEnabled: boolean;
|
||||
checkIfDataExists?: (isDataAvailable: boolean) => void;
|
||||
}): JSX.Element {
|
||||
const history = useHistory();
|
||||
const { pathname } = useLocation();
|
||||
const { pathname } = useAppLocation();
|
||||
const dispatch = useDispatch();
|
||||
const urlQuery = useUrlQuery();
|
||||
const isDarkMode = useIsDarkMode();
|
||||
@@ -68,13 +68,13 @@ function CeleryTaskBar({
|
||||
urlQuery.set(QueryParams.startTime, startTimestamp.toString());
|
||||
urlQuery.set(QueryParams.endTime, endTimestamp.toString());
|
||||
const generatedUrl = `${pathname}?${urlQuery.toString()}`;
|
||||
history.push(generatedUrl);
|
||||
navigate(generatedUrl);
|
||||
|
||||
if (startTimestamp !== endTimestamp) {
|
||||
dispatch(UpdateTimeInterval('custom', [startTimestamp, endTimestamp]));
|
||||
}
|
||||
},
|
||||
[dispatch, history, pathname, urlQuery],
|
||||
[dispatch, pathname, urlQuery],
|
||||
);
|
||||
|
||||
const [barState, setBarState] = useState<CeleryTaskState>(CeleryTaskState.All);
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import { useCallback, useMemo } from 'react';
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import { useDispatch } from 'react-redux';
|
||||
import { useHistory, useLocation } from 'react-router-dom';
|
||||
import { ENTITY_VERSION_V4 } from 'constants/app';
|
||||
import { navigate } from 'lib/router/navigation';
|
||||
import { useAppLocation } from 'lib/router/useAppLocation';
|
||||
import { QueryParams } from 'constants/query';
|
||||
import { PANEL_TYPES } from 'constants/queryBuilder';
|
||||
import { ViewMenuAction } from 'container/WidgetCard/config';
|
||||
@@ -54,8 +55,7 @@ function CeleryTaskGraph({
|
||||
checkIfDataExists?: (isDataAvailable: boolean) => void;
|
||||
analyticsEvent?: string;
|
||||
}): JSX.Element {
|
||||
const history = useHistory();
|
||||
const { pathname } = useLocation();
|
||||
const { pathname } = useAppLocation();
|
||||
const dispatch = useDispatch();
|
||||
const urlQuery = useUrlQuery();
|
||||
const isDarkMode = useIsDarkMode();
|
||||
@@ -83,13 +83,13 @@ function CeleryTaskGraph({
|
||||
urlQuery.set(QueryParams.startTime, startTimestamp.toString());
|
||||
urlQuery.set(QueryParams.endTime, endTimestamp.toString());
|
||||
const generatedUrl = `${pathname}?${urlQuery.toString()}`;
|
||||
history.push(generatedUrl);
|
||||
navigate(generatedUrl);
|
||||
|
||||
if (startTimestamp !== endTimestamp) {
|
||||
dispatch(UpdateTimeInterval('custom', [startTimestamp, endTimestamp]));
|
||||
}
|
||||
},
|
||||
[dispatch, history, pathname, urlQuery],
|
||||
[dispatch, pathname, urlQuery],
|
||||
);
|
||||
|
||||
return (
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import { useCallback, useMemo, useState } from 'react';
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
import { useHistory, useLocation } from 'react-router-dom';
|
||||
import { Col, Row } from 'antd';
|
||||
import { navigate } from 'lib/router/navigation';
|
||||
import { useAppLocation } from 'lib/router/useAppLocation';
|
||||
import logEvent from 'api/common/logEvent';
|
||||
import { QueryParams } from 'constants/query';
|
||||
import { PANEL_TYPES } from 'constants/queryBuilder';
|
||||
@@ -48,8 +49,7 @@ function CeleryTaskLatencyGraph({
|
||||
queryEnabled: boolean;
|
||||
checkIfDataExists?: (isDataAvailable: boolean) => void;
|
||||
}): JSX.Element {
|
||||
const history = useHistory();
|
||||
const { pathname } = useLocation();
|
||||
const { pathname } = useAppLocation();
|
||||
const dispatch = useDispatch();
|
||||
const urlQuery = useUrlQuery();
|
||||
const isDarkMode = useIsDarkMode();
|
||||
@@ -80,13 +80,13 @@ function CeleryTaskLatencyGraph({
|
||||
urlQuery.set(QueryParams.startTime, startTimestamp.toString());
|
||||
urlQuery.set(QueryParams.endTime, endTimestamp.toString());
|
||||
const generatedUrl = `${pathname}?${urlQuery.toString()}`;
|
||||
history.push(generatedUrl);
|
||||
navigate(generatedUrl);
|
||||
|
||||
if (startTimestamp !== endTimestamp) {
|
||||
dispatch(UpdateTimeInterval('custom', [startTimestamp, endTimestamp]));
|
||||
}
|
||||
},
|
||||
[dispatch, history, pathname, urlQuery],
|
||||
[dispatch, pathname, urlQuery],
|
||||
);
|
||||
|
||||
const selectedFilters = useMemo(
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { QueryParams } from 'constants/query';
|
||||
import { History, Location } from 'history';
|
||||
import type { AppLocation } from 'lib/router/types';
|
||||
import getRenderer from 'lib/uPlotLib/utils/getRenderer';
|
||||
import { Widgets } from 'types/api/widgets/widget';
|
||||
import { DataTypes } from 'types/api/queryBuilder/queryAutocompleteResponse';
|
||||
@@ -17,13 +17,13 @@ export function getValuesFromQueryParams(
|
||||
export function setQueryParamsFromOptions(
|
||||
value: string[],
|
||||
urlQuery: URLSearchParams,
|
||||
history: History<unknown>,
|
||||
location: Location<unknown>,
|
||||
navigate: (to: string, options?: { replace?: boolean }) => void,
|
||||
location: AppLocation,
|
||||
queryParams: QueryParams,
|
||||
): void {
|
||||
urlQuery.set(queryParams, value.join(','));
|
||||
const generatedUrl = `${location.pathname}?${urlQuery.toString()}`;
|
||||
history.replace(generatedUrl);
|
||||
navigate(generatedUrl, { replace: true });
|
||||
}
|
||||
|
||||
export function getFiltersFromQueryParams(
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { useState } from 'react';
|
||||
import { useMutation } from 'react-query';
|
||||
import { useLocation } from 'react-router-dom';
|
||||
import { Button, Modal } from 'antd';
|
||||
import { useAppLocation } from 'lib/router/useAppLocation';
|
||||
import { Typography } from '@signozhq/ui/typography';
|
||||
import logEvent from 'api/common/logEvent';
|
||||
import { createSubscription } from 'api/generated/services/subscriptions';
|
||||
@@ -45,7 +45,7 @@ export default function ChatSupportGateway(): JSX.Element {
|
||||
onError: handleBillingOnError,
|
||||
},
|
||||
);
|
||||
const { pathname } = useLocation();
|
||||
const { pathname } = useAppLocation();
|
||||
|
||||
const handleAddCreditCard = (): void => {
|
||||
logEvent('Add Credit card modal: Clicked', {
|
||||
|
||||
@@ -5,8 +5,8 @@ import * as timeUtils from 'utils/timeUtils';
|
||||
|
||||
import CustomTimePicker from './CustomTimePicker';
|
||||
|
||||
jest.mock('react-router-dom', () => {
|
||||
const actual = jest.requireActual('react-router-dom');
|
||||
jest.mock('react-router', () => {
|
||||
const actual = jest.requireActual('react-router');
|
||||
|
||||
return {
|
||||
...actual,
|
||||
|
||||
@@ -6,7 +6,7 @@ import {
|
||||
useRef,
|
||||
useState,
|
||||
} from 'react';
|
||||
import { useLocation } from 'react-router-dom';
|
||||
import { useAppLocation } from 'lib/router/useAppLocation';
|
||||
import { Button } from '@signozhq/ui/button';
|
||||
import { Input, InputRef, Popover, Tooltip } from 'antd';
|
||||
import cx from 'classnames';
|
||||
@@ -72,6 +72,8 @@ interface CustomTimePickerProps {
|
||||
maxTime: number;
|
||||
/** When true, zoom-out button is hidden (e.g. in drawer/modal time selection) */
|
||||
isModalTimeSelection?: boolean;
|
||||
/** Lands on the trigger input. Not spread — the component takes no rest props. */
|
||||
'data-testid'?: string;
|
||||
}
|
||||
|
||||
function CustomTimePicker({
|
||||
@@ -95,6 +97,7 @@ function CustomTimePicker({
|
||||
minTime,
|
||||
maxTime,
|
||||
isModalTimeSelection = false,
|
||||
'data-testid': dataTestId,
|
||||
}: CustomTimePickerProps): JSX.Element {
|
||||
const [selectedTimePlaceholderValue, setSelectedTimePlaceholderValue] =
|
||||
useState('Select / Enter Time Range');
|
||||
@@ -106,7 +109,7 @@ function CustomTimePicker({
|
||||
const [inputErrorDetails, setInputErrorDetails] = useState<
|
||||
TimeRangeValidationResult['errorDetails'] | null
|
||||
>(null);
|
||||
const location = useLocation();
|
||||
const location = useAppLocation();
|
||||
|
||||
const inputRef = useRef<InputRef>(null);
|
||||
const initialInputValueOnOpenRef = useRef<string>('');
|
||||
@@ -596,6 +599,7 @@ function CustomTimePicker({
|
||||
>
|
||||
<Input
|
||||
ref={inputRef}
|
||||
data-testid={dataTestId}
|
||||
autoComplete="off"
|
||||
className={cx(
|
||||
'timeSelection-input',
|
||||
@@ -682,4 +686,5 @@ CustomTimePicker.defaultProps = {
|
||||
onExitLiveLogs: noop,
|
||||
showLiveLogs: false,
|
||||
showRecentlyUsed: true,
|
||||
'data-testid': undefined,
|
||||
};
|
||||
|
||||
@@ -6,7 +6,7 @@ import {
|
||||
useMemo,
|
||||
useState,
|
||||
} from 'react';
|
||||
import { useLocation } from 'react-router-dom';
|
||||
import { useAppLocation } from 'lib/router/useAppLocation';
|
||||
import { Color } from '@signozhq/design-tokens';
|
||||
import { Button } from 'antd';
|
||||
import logEvent from 'api/common/logEvent';
|
||||
@@ -114,7 +114,7 @@ function CustomTimePickerPopoverContent({
|
||||
customDateTimeInputStatus = CustomTimePickerInputStatus.UNSET,
|
||||
inputErrorDetails,
|
||||
}: CustomTimePickerPopoverContentProps): JSX.Element {
|
||||
const { pathname } = useLocation();
|
||||
const { pathname } = useAppLocation();
|
||||
|
||||
const isLogsExplorerPage = useMemo(
|
||||
() => pathname === ROUTES.LOGS_EXPLORER,
|
||||
@@ -180,6 +180,7 @@ function CustomTimePickerPopoverContent({
|
||||
type="text"
|
||||
className="time-btns"
|
||||
key={option.label + option.value}
|
||||
data-testid={`time-chip-${option.value}`}
|
||||
onClick={(): void => {
|
||||
handleExitLiveLogs();
|
||||
onSelectHandler(option.label, option.value);
|
||||
@@ -259,6 +260,7 @@ function CustomTimePickerPopoverContent({
|
||||
<Button
|
||||
type="text"
|
||||
key={option.label + option.value}
|
||||
data-testid={`time-option-${option.value}`}
|
||||
onClick={(e: React.MouseEvent<HTMLButtonElement>): void => {
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
|
||||
@@ -59,7 +59,7 @@ jest.mock('providers/Timezone', () => ({
|
||||
}),
|
||||
}));
|
||||
|
||||
jest.mock('react-router-dom', () => ({
|
||||
jest.mock('react-router', () => ({
|
||||
useLocation: (): { pathname: string } => ({ pathname: '/logs-explorer' }),
|
||||
}));
|
||||
|
||||
|
||||
@@ -7,17 +7,11 @@ import { DataSource } from 'types/common/queryBuilder';
|
||||
import { viewMockData } from '../__mock__/viewData';
|
||||
import ExplorerCard from '../ExplorerCard';
|
||||
|
||||
const historyReplace = jest.fn();
|
||||
|
||||
jest.mock('react-router-dom', () => ({
|
||||
...jest.requireActual('react-router-dom'),
|
||||
jest.mock('react-router', () => ({
|
||||
...jest.requireActual('react-router'),
|
||||
useLocation: (): { pathname: string } => ({
|
||||
pathname: `${process.env.FRONTEND_API_ENDPOINT}/${ROUTES.TRACES_EXPLORER}/`,
|
||||
}),
|
||||
useHistory: (): any => ({
|
||||
...jest.requireActual('react-router-dom').useHistory(),
|
||||
replace: historyReplace,
|
||||
}),
|
||||
}));
|
||||
|
||||
jest.mock('hooks/useSafeNavigate', () => ({
|
||||
|
||||
@@ -6,8 +6,8 @@ import { DataSource } from 'types/common/queryBuilder';
|
||||
import { viewMockData } from '../__mock__/viewData';
|
||||
import MenuItemGenerator from '../MenuItemGenerator';
|
||||
|
||||
jest.mock('react-router-dom', () => ({
|
||||
...jest.requireActual('react-router-dom'),
|
||||
jest.mock('react-router', () => ({
|
||||
...jest.requireActual('react-router'),
|
||||
useLocation: (): { pathname: string } => ({
|
||||
pathname: `${process.env.FRONTEND_API_ENDPOINT}${ROUTES.APPLICATION}/`,
|
||||
}),
|
||||
|
||||
@@ -5,8 +5,8 @@ import { DataSource } from 'types/common/queryBuilder';
|
||||
|
||||
import SaveViewWithName from '../SaveViewWithName';
|
||||
|
||||
jest.mock('react-router-dom', () => ({
|
||||
...jest.requireActual('react-router-dom'),
|
||||
jest.mock('react-router', () => ({
|
||||
...jest.requireActual('react-router'),
|
||||
useLocation: (): { pathname: string } => ({
|
||||
pathname: `${process.env.FRONTEND_API_ENDPOINT}${ROUTES.APPLICATION}/`,
|
||||
}),
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { useCallback, useEffect, useState } from 'react';
|
||||
import { useLocation } from 'react-router-dom';
|
||||
import { useAppLocation } from 'lib/router/useAppLocation';
|
||||
import { toast } from '@signozhq/ui/sonner';
|
||||
import { Button, Input } from 'antd';
|
||||
import { ToggleGroupSimple } from '@signozhq/ui/toggle-group';
|
||||
@@ -11,7 +11,7 @@ import { useGetTenantLicense } from 'hooks/useGetTenantLicense';
|
||||
function FeedbackModal({ onClose }: { onClose: () => void }): JSX.Element {
|
||||
const [activeTab, setActiveTab] = useState('feedback');
|
||||
const [feedback, setFeedback] = useState('');
|
||||
const location = useLocation();
|
||||
const location = useAppLocation();
|
||||
const { isCloudUser: isCloudUserVal } = useGetTenantLicense();
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { useCallback, useState } from 'react';
|
||||
import { useLocation } from 'react-router-dom';
|
||||
import { useAppLocation } from 'lib/router/useAppLocation';
|
||||
import { Dot } from '@signozhq/icons';
|
||||
import { Button } from '@signozhq/ui/button';
|
||||
import { TooltipSimple } from '@signozhq/ui/tooltip';
|
||||
@@ -39,7 +39,7 @@ function HeaderRightSection({
|
||||
enableFeedback,
|
||||
shareModalExtraOption,
|
||||
}: HeaderRightSectionProps): JSX.Element | null {
|
||||
const location = useLocation();
|
||||
const location = useAppLocation();
|
||||
|
||||
const [openFeedbackModal, setOpenFeedbackModal] = useState(false);
|
||||
const [openShareURLModal, setOpenShareURLModal] = useState(false);
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import { useMemo, useState } from 'react';
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import { useSelector } from 'react-redux';
|
||||
import { matchPath, useLocation } from 'react-router-dom';
|
||||
import { useAppLocation } from 'lib/router/useAppLocation';
|
||||
import { matchRoute } from 'lib/router/matchRoute';
|
||||
import { useCopyToClipboard } from 'react-use';
|
||||
import { Color } from '@signozhq/design-tokens';
|
||||
import { Button } from 'antd';
|
||||
@@ -41,7 +42,7 @@ interface ShareURLModalProps {
|
||||
|
||||
function ShareURLModal({ extraOption }: ShareURLModalProps): JSX.Element {
|
||||
const urlQuery = useUrlQuery();
|
||||
const location = useLocation();
|
||||
const location = useAppLocation();
|
||||
const { selectedTime } = useSelector<AppState, GlobalReducer>(
|
||||
(state) => state.globalTime,
|
||||
);
|
||||
@@ -75,7 +76,7 @@ function ShareURLModal({ extraOption }: ShareURLModalProps): JSX.Element {
|
||||
const isRouteToBeSharedWithTime = useMemo(
|
||||
() =>
|
||||
routesToBeSharedWithTime.some((route) =>
|
||||
matchPath(location.pathname, { path: route, exact: true }),
|
||||
matchRoute(location.pathname, route, { exact: true }),
|
||||
),
|
||||
[location.pathname],
|
||||
);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// Mock dependencies before imports
|
||||
import { useLocation } from 'react-router-dom';
|
||||
import { useAppLocation } from 'lib/router/useAppLocation';
|
||||
import { toast } from '@signozhq/ui/sonner';
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
@@ -9,9 +9,8 @@ import { useGetTenantLicense } from 'hooks/useGetTenantLicense';
|
||||
|
||||
import FeedbackModal from '../FeedbackModal';
|
||||
|
||||
jest.mock('react-router-dom', () => ({
|
||||
...jest.requireActual('react-router-dom'),
|
||||
useLocation: jest.fn(),
|
||||
jest.mock('lib/router/useAppLocation', () => ({
|
||||
useAppLocation: jest.fn(),
|
||||
}));
|
||||
|
||||
jest.mock('@signozhq/ui/sonner', () => ({
|
||||
@@ -30,7 +29,7 @@ jest.mock('container/Integrations/utils', () => ({
|
||||
handleContactSupport: jest.fn(),
|
||||
}));
|
||||
|
||||
const mockUseLocation = useLocation as jest.Mock;
|
||||
const mockUseAppLocation = useAppLocation as jest.Mock;
|
||||
const mockUseGetTenantLicense = useGetTenantLicense as jest.Mock;
|
||||
const mockHandleContactSupport = handleContactSupport as jest.Mock;
|
||||
const mockToast = toast as jest.Mocked<typeof toast>;
|
||||
@@ -45,7 +44,7 @@ describe('FeedbackModal', () => {
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
logEventMock.mockReturnValue(Promise.resolve() as never);
|
||||
mockUseLocation.mockReturnValue(mockLocation);
|
||||
mockUseAppLocation.mockReturnValue(mockLocation);
|
||||
mockUseGetTenantLicense.mockReturnValue({
|
||||
isCloudUser: false,
|
||||
});
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// Mock dependencies before imports
|
||||
import { useLocation } from 'react-router-dom';
|
||||
import { useAppLocation } from 'lib/router/useAppLocation';
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
import { logEventMock } from '__tests__/logEventMock';
|
||||
@@ -7,9 +7,8 @@ import { useGetTenantLicense } from 'hooks/useGetTenantLicense';
|
||||
|
||||
import HeaderRightSection from '../HeaderRightSection';
|
||||
|
||||
jest.mock('react-router-dom', () => ({
|
||||
...jest.requireActual('react-router-dom'),
|
||||
useLocation: jest.fn(),
|
||||
jest.mock('lib/router/useAppLocation', () => ({
|
||||
useAppLocation: jest.fn(),
|
||||
}));
|
||||
|
||||
jest.mock('../FeedbackModal', () => ({
|
||||
@@ -45,7 +44,7 @@ jest.mock('hooks/useIsAIAssistantEnabled', () => ({
|
||||
useIsAIAssistantEnabled: (): boolean => false,
|
||||
}));
|
||||
|
||||
const mockUseLocation = useLocation as jest.Mock;
|
||||
const mockUseAppLocation = useAppLocation as jest.Mock;
|
||||
const mockUseGetTenantLicense = useGetTenantLicense as jest.Mock;
|
||||
|
||||
const defaultProps = {
|
||||
@@ -61,7 +60,7 @@ const mockLocation = {
|
||||
describe('HeaderRightSection', () => {
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
mockUseLocation.mockReturnValue(mockLocation);
|
||||
mockUseAppLocation.mockReturnValue(mockLocation);
|
||||
// Default to licensed user (Enterprise or Cloud)
|
||||
mockUseGetTenantLicense.mockReturnValue({
|
||||
isCloudUser: true,
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
// Mock dependencies before imports
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import { useSelector } from 'react-redux';
|
||||
import { matchPath, useLocation } from 'react-router-dom';
|
||||
import { useAppLocation } from 'lib/router/useAppLocation';
|
||||
import { matchRoute } from 'lib/router/matchRoute';
|
||||
import { useCopyToClipboard } from 'react-use';
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
@@ -12,10 +13,12 @@ import GetMinMax from 'lib/getMinMax';
|
||||
|
||||
import ShareURLModal from '../ShareURLModal';
|
||||
|
||||
jest.mock('react-router-dom', () => ({
|
||||
...jest.requireActual('react-router-dom'),
|
||||
useLocation: jest.fn(),
|
||||
matchPath: jest.fn(),
|
||||
jest.mock('lib/router/useAppLocation', () => ({
|
||||
useAppLocation: jest.fn(),
|
||||
}));
|
||||
|
||||
jest.mock('lib/router/matchRoute', () => ({
|
||||
matchRoute: jest.fn(),
|
||||
}));
|
||||
|
||||
jest.mock('hooks/useUrlQuery', () => ({
|
||||
@@ -48,12 +51,12 @@ Object.defineProperty(window, 'location', {
|
||||
writable: true,
|
||||
});
|
||||
|
||||
const mockUseLocation = useLocation as jest.Mock;
|
||||
const mockUseAppLocation = useAppLocation as jest.Mock;
|
||||
const mockUseUrlQuery = useUrlQuery as jest.Mock;
|
||||
const mockUseSelector = useSelector as jest.Mock;
|
||||
const mockGetMinMax = GetMinMax as jest.Mock;
|
||||
const mockUseCopyToClipboard = useCopyToClipboard as jest.Mock;
|
||||
const mockMatchPath = matchPath as jest.Mock;
|
||||
const mockMatchRoute = matchRoute as jest.Mock;
|
||||
|
||||
const mockUrlQuery = {
|
||||
get: jest.fn(),
|
||||
@@ -71,7 +74,7 @@ describe('ShareURLModal', () => {
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
|
||||
mockUseLocation.mockReturnValue({
|
||||
mockUseAppLocation.mockReturnValue({
|
||||
pathname: TEST_PATH,
|
||||
});
|
||||
|
||||
@@ -88,7 +91,7 @@ describe('ShareURLModal', () => {
|
||||
|
||||
mockUseCopyToClipboard.mockReturnValue([null, mockHandleCopyToClipboard]);
|
||||
|
||||
mockMatchPath.mockReturnValue(false);
|
||||
mockMatchRoute.mockReturnValue(null);
|
||||
|
||||
// Reset URL query mocks - all return null by default
|
||||
mockUrlQuery.get.mockReturnValue(null);
|
||||
@@ -126,7 +129,7 @@ describe('ShareURLModal', () => {
|
||||
});
|
||||
|
||||
it('should show absolute time toggle when on time-enabled route', () => {
|
||||
mockMatchPath.mockReturnValue(true); // Simulate being on a route that supports time
|
||||
mockMatchRoute.mockReturnValue({}); // Simulate being on a route that supports time
|
||||
|
||||
render(<ShareURLModal />);
|
||||
|
||||
@@ -146,7 +149,7 @@ describe('ShareURLModal', () => {
|
||||
|
||||
it('should toggle absolute time switch', async () => {
|
||||
const user = userEvent.setup();
|
||||
mockMatchPath.mockReturnValue(true);
|
||||
mockMatchRoute.mockReturnValue({});
|
||||
mockUseSelector.mockReturnValue({
|
||||
selectedTime: '5min', // Non-custom time should enable absolute time by default
|
||||
});
|
||||
@@ -169,7 +172,7 @@ describe('ShareURLModal', () => {
|
||||
// Invalid - missing start and end time for custom
|
||||
mockUrlQuery.get.mockReturnValue(null);
|
||||
|
||||
mockMatchPath.mockReturnValue(true);
|
||||
mockMatchRoute.mockReturnValue({});
|
||||
|
||||
render(<ShareURLModal />);
|
||||
|
||||
@@ -181,7 +184,7 @@ describe('ShareURLModal', () => {
|
||||
|
||||
it('should process URL with absolute time for non-custom time', async () => {
|
||||
const user = userEvent.setup();
|
||||
mockMatchPath.mockReturnValue(true);
|
||||
mockMatchRoute.mockReturnValue({});
|
||||
mockUseSelector.mockReturnValue({
|
||||
selectedTime: '5min',
|
||||
});
|
||||
@@ -200,7 +203,7 @@ describe('ShareURLModal', () => {
|
||||
|
||||
it('should process URL with custom time parameters', async () => {
|
||||
const user = userEvent.setup();
|
||||
mockMatchPath.mockReturnValue(true);
|
||||
mockMatchRoute.mockReturnValue({});
|
||||
mockUseSelector.mockReturnValue({
|
||||
selectedTime: 'custom',
|
||||
});
|
||||
@@ -228,7 +231,7 @@ describe('ShareURLModal', () => {
|
||||
|
||||
it('should process URL with relative time when absolute time is disabled', async () => {
|
||||
const user = userEvent.setup();
|
||||
mockMatchPath.mockReturnValue(true);
|
||||
mockMatchRoute.mockReturnValue({});
|
||||
mockUseSelector.mockReturnValue({
|
||||
selectedTime: '5min',
|
||||
});
|
||||
@@ -249,12 +252,12 @@ describe('ShareURLModal', () => {
|
||||
|
||||
it('should handle routes that should be shared with time', async () => {
|
||||
const user = userEvent.setup();
|
||||
mockUseLocation.mockReturnValue({
|
||||
mockUseAppLocation.mockReturnValue({
|
||||
pathname: ROUTES.LOGS_EXPLORER,
|
||||
});
|
||||
|
||||
mockMatchPath.mockImplementation(
|
||||
(pathname: string, options: any) => options.path === ROUTES.LOGS_EXPLORER,
|
||||
mockMatchRoute.mockImplementation((pathname: string, route: string) =>
|
||||
route === ROUTES.LOGS_EXPLORER ? {} : null,
|
||||
);
|
||||
|
||||
render(<ShareURLModal />);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { useMemo, useState } from 'react';
|
||||
import { useMutation } from 'react-query';
|
||||
import { useLocation } from 'react-router-dom';
|
||||
import { useAppLocation } from 'lib/router/useAppLocation';
|
||||
import { Button, Modal, Tooltip } from 'antd';
|
||||
import { Typography } from '@signozhq/ui/typography';
|
||||
import logEvent from 'api/common/logEvent';
|
||||
@@ -52,7 +52,7 @@ function LaunchChatSupport({
|
||||
const [isAddCreditCardModalOpen, setIsAddCreditCardModalOpen] =
|
||||
useState(false);
|
||||
|
||||
const { pathname } = useLocation();
|
||||
const { pathname } = useAppLocation();
|
||||
|
||||
const isChatSupportEnabled = useMemo(() => {
|
||||
if (!isFetchingFeatureFlags && (featureFlags || featureFlagsFetchError)) {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Link } from 'react-router-dom';
|
||||
import { AppLink } from 'lib/router/AppLink';
|
||||
|
||||
import styles from './LogHighlights.module.scss';
|
||||
|
||||
@@ -8,7 +8,7 @@ interface TraceIdFieldProps {
|
||||
|
||||
function TraceIdField({ traceId }: TraceIdFieldProps): JSX.Element {
|
||||
return (
|
||||
<Link
|
||||
<AppLink
|
||||
to={{ pathname: `/trace/${traceId}` }}
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
@@ -16,7 +16,7 @@ function TraceIdField({ traceId }: TraceIdFieldProps): JSX.Element {
|
||||
title={traceId}
|
||||
>
|
||||
{traceId}
|
||||
</Link>
|
||||
</AppLink>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import ROUTES from 'constants/routes';
|
||||
import { useLocation } from 'react-router-dom';
|
||||
import { useAppLocation } from 'lib/router/useAppLocation';
|
||||
|
||||
export function useIsLogDetailsV2(): boolean {
|
||||
const { pathname } = useLocation();
|
||||
const { pathname } = useAppLocation();
|
||||
return (
|
||||
pathname === ROUTES.LOGS_EXPLORER ||
|
||||
pathname.startsWith(ROUTES.INFRASTRUCTURE_MONITORING_BASE) ||
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { Typography } from '@signozhq/ui/typography';
|
||||
import { ReactNode, useEffect, useState } from 'react';
|
||||
import { useHistory } from 'react-router-dom';
|
||||
import {
|
||||
OctagonAlert,
|
||||
Bolt,
|
||||
@@ -14,7 +13,7 @@ import { Modal, Select, Spin, Tooltip, Tree, TreeDataNode } from 'antd';
|
||||
import { OnboardingStatusResponse } from 'api/messagingQueues/onboarding/getOnboardingStatus';
|
||||
import { QueryParams } from 'constants/query';
|
||||
import ROUTES from 'constants/routes';
|
||||
import { History } from 'history';
|
||||
import { navigate } from 'lib/router/navigation';
|
||||
import { useGetTenantLicense } from 'hooks/useGetTenantLicense';
|
||||
import {
|
||||
KAFKA_SETUP_DOC_LINK,
|
||||
@@ -45,7 +44,6 @@ export enum AttributesFilters {
|
||||
function ErrorTitleAndKey({
|
||||
title,
|
||||
parentTitle,
|
||||
history,
|
||||
isCloudUserVal,
|
||||
errorMsg,
|
||||
isLeaf,
|
||||
@@ -53,7 +51,6 @@ function ErrorTitleAndKey({
|
||||
title: string;
|
||||
parentTitle: string;
|
||||
isCloudUserVal: boolean;
|
||||
history: History<unknown>;
|
||||
errorMsg?: string;
|
||||
isLeaf?: boolean;
|
||||
}): TreeDataNode {
|
||||
@@ -75,7 +72,7 @@ function ErrorTitleAndKey({
|
||||
}
|
||||
|
||||
if (isCloudUserVal && !!link) {
|
||||
history.push(link);
|
||||
navigate(link);
|
||||
} else {
|
||||
openInNewTab(KAFKA_SETUP_DOC_LINK);
|
||||
}
|
||||
@@ -149,7 +146,6 @@ function generateTreeDataNodes(
|
||||
response: OnboardingStatusResponse['data'],
|
||||
parentTitle: string,
|
||||
isCloudUserVal: boolean,
|
||||
history: History<unknown>,
|
||||
): TreeDataNode[] {
|
||||
return response
|
||||
.map((item) => {
|
||||
@@ -162,7 +158,6 @@ function generateTreeDataNodes(
|
||||
title: item.attribute,
|
||||
errorMsg: item.error_message || '',
|
||||
parentTitle,
|
||||
history,
|
||||
isCloudUserVal,
|
||||
});
|
||||
}
|
||||
@@ -185,7 +180,6 @@ function AttributeCheckList({
|
||||
setFilter(value);
|
||||
};
|
||||
const { isCloudUser: isCloudUserVal } = useGetTenantLicense();
|
||||
const history = useHistory();
|
||||
|
||||
useEffect(() => {
|
||||
const filteredData = onboardingStatusResponses.map((response) => {
|
||||
@@ -195,7 +189,6 @@ function AttributeCheckList({
|
||||
errorMsg: response.errorMsg,
|
||||
isLeaf: true,
|
||||
parentTitle: response.title,
|
||||
history,
|
||||
isCloudUserVal,
|
||||
});
|
||||
}
|
||||
@@ -213,7 +206,6 @@ function AttributeCheckList({
|
||||
filteredData,
|
||||
response.title,
|
||||
isCloudUserVal,
|
||||
history,
|
||||
),
|
||||
};
|
||||
});
|
||||
|
||||
@@ -1,19 +1,19 @@
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import { Provider } from 'react-redux';
|
||||
import { MemoryRouter } from 'react-router-dom';
|
||||
import { render } from '@testing-library/react';
|
||||
import store from 'store';
|
||||
import { TestRouter } from 'tests/router';
|
||||
|
||||
import NotFound from './index';
|
||||
|
||||
describe('Not Found page test', () => {
|
||||
it('should render Not Found page without errors', () => {
|
||||
const { asFragment } = render(
|
||||
<MemoryRouter>
|
||||
<TestRouter>
|
||||
<Provider store={store}>
|
||||
<NotFound />
|
||||
</Provider>
|
||||
</MemoryRouter>,
|
||||
</TestRouter>,
|
||||
);
|
||||
expect(asFragment()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
@@ -98,6 +98,7 @@ exports[`Not Found page test should render Not Found page without errors 1`] = `
|
||||
|
||||
<div
|
||||
class="c0"
|
||||
data-testid="not-found"
|
||||
>
|
||||
<img
|
||||
alt="not-found"
|
||||
@@ -120,6 +121,7 @@ exports[`Not Found page test should render Not Found page without errors 1`] = `
|
||||
</div>
|
||||
<a
|
||||
class="c3"
|
||||
data-discover="true"
|
||||
href="/home"
|
||||
tabindex="0"
|
||||
>
|
||||
|
||||
@@ -6,7 +6,7 @@ import { Button, Container, Text, TextContainer } from './styles';
|
||||
|
||||
function NotFound({ text = defaultText }: Props): JSX.Element {
|
||||
return (
|
||||
<Container>
|
||||
<Container data-testid="not-found">
|
||||
<NotFoundImage />
|
||||
|
||||
<TextContainer>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { Link } from 'react-router-dom';
|
||||
import { AppLink } from 'lib/router/AppLink';
|
||||
import styled from 'styled-components';
|
||||
|
||||
export const Button = styled(Link)`
|
||||
export const Button = styled(AppLink)`
|
||||
border: 2px solid #2f80ed;
|
||||
box-sizing: border-box;
|
||||
border-radius: 10px;
|
||||
|
||||
@@ -47,23 +47,29 @@ export function QuerySearchV2Provider({
|
||||
store.getState().setInitialExpression(initialExpression);
|
||||
}, [initialExpression, store]);
|
||||
|
||||
const isInitialized = useRef(false);
|
||||
// The URL owns the expression, in both directions. A provider can outlive the
|
||||
// navigation away from the page it belongs to (a route-driven tab keeps the
|
||||
// leaving pane mounted until its animation ends), so treating a param that
|
||||
// disappeared as something to restore republishes it onto the URL of the page
|
||||
// being entered.
|
||||
useEffect(() => {
|
||||
if (!isInitialized.current && urlExpression) {
|
||||
const cleanedExpression = getUserExpressionFromCombined(
|
||||
initialExpression,
|
||||
urlExpression,
|
||||
);
|
||||
store.getState().initializeFromUrl(cleanedExpression);
|
||||
isInitialized.current = true;
|
||||
const userExpression = getUserExpressionFromCombined(
|
||||
initialExpression,
|
||||
urlExpression,
|
||||
);
|
||||
if (userExpression !== store.getState().committedExpression) {
|
||||
store.getState().initializeFromUrl(userExpression);
|
||||
}
|
||||
}, [urlExpression, initialExpression, store]);
|
||||
|
||||
const publishedExpression = useRef(committedExpression);
|
||||
useEffect(() => {
|
||||
if (isInitialized.current || !urlExpression) {
|
||||
setUrlExpression(committedExpression || null);
|
||||
if (committedExpression === publishedExpression.current) {
|
||||
return;
|
||||
}
|
||||
}, [committedExpression, setUrlExpression, urlExpression]);
|
||||
publishedExpression.current = committedExpression;
|
||||
setUrlExpression(committedExpression || null);
|
||||
}, [committedExpression, setUrlExpression]);
|
||||
|
||||
useEffect(() => {
|
||||
return (): void => {
|
||||
|
||||
@@ -110,6 +110,22 @@ describe('QuerySearchExpressionProvider', () => {
|
||||
expect(result.current.expression).toBe('status = 500');
|
||||
});
|
||||
|
||||
it('should follow the URL when the param is dropped', () => {
|
||||
mockUrlValue = 'status = 500';
|
||||
|
||||
const { result, rerender } = renderHook(() => useTestHooks(), {
|
||||
wrapper: createWrapper(),
|
||||
});
|
||||
expect(result.current.expression).toBe('status = 500');
|
||||
|
||||
mockSetQueryState.mockClear();
|
||||
mockUrlValue = null;
|
||||
rerender();
|
||||
|
||||
expect(result.current.expression).toBe('');
|
||||
expect(mockSetQueryState).not.toHaveBeenCalledWith('status = 500');
|
||||
});
|
||||
|
||||
it('should throw error when used outside provider', () => {
|
||||
expect(() => {
|
||||
renderHook(() => useExpression());
|
||||
|
||||
@@ -1,10 +1,14 @@
|
||||
import { Router } from 'react-router-dom';
|
||||
import { createMemoryHistory } from 'history';
|
||||
import { navigate } from 'lib/router/navigation';
|
||||
import { fireEvent, render, screen } from 'tests/test-utils';
|
||||
|
||||
import RouteTab from './index';
|
||||
import { RouteTabProps } from './types';
|
||||
|
||||
jest.mock('lib/router/navigation', () => ({
|
||||
...jest.requireActual('lib/router/navigation'),
|
||||
navigate: jest.fn(),
|
||||
}));
|
||||
|
||||
function DummyComponent1(): JSX.Element {
|
||||
return <div>Dummy Component 1</div>;
|
||||
}
|
||||
@@ -28,64 +32,44 @@ const testRoutes: RouteTabProps['routes'] = [
|
||||
];
|
||||
|
||||
describe('RouteTab component', () => {
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
it('renders correctly', () => {
|
||||
const history = createMemoryHistory();
|
||||
render(
|
||||
<Router history={history}>
|
||||
<RouteTab history={history} routes={testRoutes} activeKey="Tab1" />
|
||||
</Router>,
|
||||
);
|
||||
render(<RouteTab routes={testRoutes} activeKey="Tab1" />);
|
||||
expect(screen.getByRole('tab', { name: 'Tab1' })).toBeInTheDocument();
|
||||
expect(screen.getByRole('tab', { name: 'Tab2' })).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('renders correct number of tabs', () => {
|
||||
const history = createMemoryHistory();
|
||||
render(
|
||||
<Router history={history}>
|
||||
<RouteTab history={history} routes={testRoutes} activeKey="Tab1" />
|
||||
</Router>,
|
||||
);
|
||||
render(<RouteTab routes={testRoutes} activeKey="Tab1" />);
|
||||
const tabs = screen.getAllByRole('tab');
|
||||
expect(tabs).toHaveLength(testRoutes.length);
|
||||
});
|
||||
|
||||
it('sets provided activeKey as active tab', () => {
|
||||
const history = createMemoryHistory();
|
||||
render(
|
||||
<Router history={history}>
|
||||
<RouteTab history={history} routes={testRoutes} activeKey="Tab2" />
|
||||
</Router>,
|
||||
);
|
||||
render(<RouteTab routes={testRoutes} activeKey="Tab2" />);
|
||||
expect(
|
||||
screen.getByRole('tab', { name: 'Tab2', selected: true }),
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('navigates to correct route on tab click', () => {
|
||||
const history = createMemoryHistory();
|
||||
render(
|
||||
<Router history={history}>
|
||||
<RouteTab history={history} routes={testRoutes} activeKey="Tab1" />
|
||||
</Router>,
|
||||
);
|
||||
expect(history.location.pathname).toBe('/');
|
||||
render(<RouteTab routes={testRoutes} activeKey="Tab1" />);
|
||||
expect(navigate).not.toHaveBeenCalled();
|
||||
fireEvent.click(screen.getByRole('tab', { name: 'Tab2' }));
|
||||
expect(history.location.pathname).toBe('/tab2');
|
||||
expect(navigate).toHaveBeenCalledWith('/tab2');
|
||||
});
|
||||
|
||||
it('calls onChangeHandler on tab change', () => {
|
||||
const onChangeHandler = jest.fn();
|
||||
const history = createMemoryHistory();
|
||||
render(
|
||||
<Router history={history}>
|
||||
<RouteTab
|
||||
routes={testRoutes}
|
||||
activeKey="Tab1"
|
||||
onChangeHandler={onChangeHandler}
|
||||
history={history}
|
||||
/>
|
||||
</Router>,
|
||||
<RouteTab
|
||||
routes={testRoutes}
|
||||
activeKey="Tab1"
|
||||
onChangeHandler={onChangeHandler}
|
||||
/>,
|
||||
);
|
||||
fireEvent.click(screen.getByRole('tab', { name: 'Tab2' }));
|
||||
expect(onChangeHandler).toHaveBeenCalled();
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
import {
|
||||
generatePath,
|
||||
matchPath,
|
||||
useLocation,
|
||||
useParams,
|
||||
} from 'react-router-dom';
|
||||
import { Tabs, TabsProps } from 'antd';
|
||||
import HeaderRightSection from 'components/HeaderRightSection/HeaderRightSection';
|
||||
import { buildRoutePath } from 'lib/router/buildRoutePath';
|
||||
import { matchRoute } from 'lib/router/matchRoute';
|
||||
import { navigate } from 'lib/router/navigation';
|
||||
import { useAppLocation } from 'lib/router/useAppLocation';
|
||||
import { useAppParams } from 'lib/router/useAppParams';
|
||||
|
||||
import { RouteTabProps } from './types';
|
||||
|
||||
@@ -17,20 +16,16 @@ function RouteTab({
|
||||
routes,
|
||||
activeKey,
|
||||
onChangeHandler,
|
||||
history,
|
||||
showRightSection,
|
||||
...rest
|
||||
}: RouteTabProps & TabsProps): JSX.Element {
|
||||
const params = useParams<Params>();
|
||||
const location = useLocation();
|
||||
const params = useAppParams<Params>();
|
||||
const location = useAppLocation();
|
||||
|
||||
// Find the matching route for the current pathname
|
||||
const currentRoute = routes.find((route) => {
|
||||
const routePath = route.route.split('?')[0];
|
||||
return matchPath(location.pathname, {
|
||||
path: routePath,
|
||||
exact: true,
|
||||
});
|
||||
return matchRoute(location.pathname, routePath, { exact: true });
|
||||
});
|
||||
|
||||
const onChange = (activeRoute: string): void => {
|
||||
@@ -41,8 +36,13 @@ function RouteTab({
|
||||
const selectedRoute = routes.find((e) => e.key === activeRoute);
|
||||
|
||||
if (selectedRoute) {
|
||||
const resolvedRoute = generatePath(selectedRoute.route, params);
|
||||
history.push(resolvedRoute);
|
||||
const resolvedRoute = buildRoutePath(
|
||||
selectedRoute.route,
|
||||
Object.fromEntries(
|
||||
Object.entries(params).filter(([, v]) => v !== undefined),
|
||||
) as Record<string, string>,
|
||||
);
|
||||
navigate(resolvedRoute);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { ComponentType } from 'react';
|
||||
import { TabsProps } from 'antd';
|
||||
import { History } from 'history';
|
||||
|
||||
export type TabRoutes = {
|
||||
name: React.ReactNode;
|
||||
@@ -13,6 +12,5 @@ export interface RouteTabProps {
|
||||
routes: TabRoutes[];
|
||||
activeKey: TabsProps['activeKey'];
|
||||
onChangeHandler?: (key: string) => void;
|
||||
history: History<unknown>;
|
||||
showRightSection: boolean;
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
*/
|
||||
// ---- Mocks (must run BEFORE importing the component) ----
|
||||
import ROUTES from 'constants/routes';
|
||||
import history from 'lib/history';
|
||||
import { navigate } from 'lib/router/navigation';
|
||||
import { render, screen, userEvent } from 'tests/test-utils';
|
||||
|
||||
import '@testing-library/jest-dom/extend-expect';
|
||||
@@ -24,61 +24,12 @@ afterAll(() => {
|
||||
delete (HTMLElement.prototype as any).scrollIntoView;
|
||||
});
|
||||
|
||||
// mock history.push / replace / go / location
|
||||
jest.mock('lib/history', () => {
|
||||
const location = { pathname: '/', search: '', hash: '' };
|
||||
jest.mock('lib/router/navigation', () => ({
|
||||
...jest.requireActual('lib/router/navigation'),
|
||||
navigate: jest.fn(),
|
||||
}));
|
||||
|
||||
const stack: { pathname: string; search: string }[] = [
|
||||
{ pathname: '/', search: '' },
|
||||
];
|
||||
|
||||
const push = jest.fn((path: string) => {
|
||||
const [rawPath, rawQuery] = path.split('?');
|
||||
const pathname = rawPath || '/';
|
||||
const search = path.includes('?') ? `?${rawQuery || ''}` : '';
|
||||
|
||||
location.pathname = pathname;
|
||||
location.search = search;
|
||||
|
||||
stack.push({ pathname, search });
|
||||
return undefined;
|
||||
});
|
||||
|
||||
const replace = jest.fn((path: string) => {
|
||||
const [rawPath, rawQuery] = path.split('?');
|
||||
const pathname = rawPath || '/';
|
||||
const search = path.includes('?') ? `?${rawQuery || ''}` : '';
|
||||
|
||||
location.pathname = pathname;
|
||||
location.search = search;
|
||||
|
||||
if (stack.length > 0) {
|
||||
stack[stack.length - 1] = { pathname, search };
|
||||
} else {
|
||||
stack.push({ pathname, search });
|
||||
}
|
||||
return undefined;
|
||||
});
|
||||
|
||||
const listen = jest.fn();
|
||||
const go = jest.fn((n: number) => {
|
||||
if (n < 0 && stack.length > 1) {
|
||||
stack.pop();
|
||||
}
|
||||
const top = stack[stack.length - 1] || { pathname: '/', search: '' };
|
||||
location.pathname = top.pathname;
|
||||
location.search = top.search;
|
||||
});
|
||||
|
||||
return {
|
||||
push,
|
||||
replace,
|
||||
listen,
|
||||
go,
|
||||
location,
|
||||
__stack: stack,
|
||||
};
|
||||
});
|
||||
const mockNavigate = navigate as jest.MockedFunction<typeof navigate>;
|
||||
|
||||
// Mock ResizeObserver for Jest/jsdom
|
||||
class ResizeObserver {
|
||||
@@ -159,14 +110,14 @@ describe('CmdKPalette', () => {
|
||||
expect(screen.getByText('Switch to Dark Mode')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('clicking a navigation item calls history.push with correct route', async () => {
|
||||
it('clicking a navigation item navigates to the correct route', async () => {
|
||||
const user = userEvent.setup({ pointerEventsCheck: 0 });
|
||||
render(<CmdKPalette userRole="ADMIN" />);
|
||||
|
||||
const homeItem = screen.getByText(HOME_LABEL);
|
||||
await user.click(homeItem);
|
||||
|
||||
expect(history.push).toHaveBeenCalledWith(ROUTES.HOME);
|
||||
expect(mockNavigate).toHaveBeenCalledWith(ROUTES.HOME);
|
||||
});
|
||||
|
||||
it('role-based filtering (basic smoke)', () => {
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import React, { useEffect } from 'react';
|
||||
import cx from 'classnames';
|
||||
import { useLocation } from 'react-router-dom';
|
||||
import {
|
||||
CommandDialog,
|
||||
CommandEmpty,
|
||||
@@ -23,7 +22,8 @@ import {
|
||||
import { useThemeMode } from 'hooks/useDarkMode';
|
||||
import { useIsAIAssistantEnabled } from 'hooks/useIsAIAssistantEnabled';
|
||||
import { IS_DEV } from 'lib/env';
|
||||
import history from 'lib/history';
|
||||
import { navigate } from 'lib/router/navigation';
|
||||
import { useAppLocation } from 'lib/router/useAppLocation';
|
||||
import { ROLES as UserRole } from 'types/roles';
|
||||
|
||||
import { createShortcutActions } from '../../constants/shortcutActions';
|
||||
@@ -77,7 +77,7 @@ export function CmdKPalette({
|
||||
const { open, setOpen } = useCmdK();
|
||||
|
||||
const { setAutoSwitch, setTheme, theme } = useThemeMode();
|
||||
const location = useLocation();
|
||||
const location = useAppLocation();
|
||||
const isAIAssistantEnabled = useIsAIAssistantEnabled();
|
||||
const startNewConversation = useAIAssistantStore(
|
||||
(s) => s.startNewConversation,
|
||||
@@ -120,7 +120,7 @@ export function CmdKPalette({
|
||||
}
|
||||
|
||||
function onClickHandler(key: string): void {
|
||||
history.push(key);
|
||||
navigate(key);
|
||||
}
|
||||
|
||||
const handleOpenAIAssistant = (): void => {
|
||||
|
||||
@@ -8,6 +8,5 @@ export enum FeatureKeys {
|
||||
PREMIUM_SUPPORT = 'premium_support',
|
||||
ANOMALY_DETECTION = 'anomaly_detection',
|
||||
USE_JSON_BODY = 'use_json_body',
|
||||
ENABLE_AI_OBSERVABILITY = 'enable_ai_observability',
|
||||
ENABLE_METRICS_REDUCTION = 'enable_metrics_reduction',
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { useCallback } from 'react';
|
||||
import { useHistory } from 'react-router-dom';
|
||||
import { Button } from '@signozhq/ui/button';
|
||||
import { navigate } from 'lib/router/navigation';
|
||||
import { TooltipSimple } from '@signozhq/ui/tooltip';
|
||||
import { Drawer } from 'antd';
|
||||
import ROUTES from 'constants/routes';
|
||||
@@ -12,8 +12,6 @@ import { useAIAssistantStore } from '../store/useAIAssistantStore';
|
||||
import { VariantContext } from '../VariantContext';
|
||||
|
||||
export default function AIAssistantDrawer(): JSX.Element {
|
||||
const history = useHistory();
|
||||
|
||||
const isDrawerOpen = useAIAssistantStore((s) => s.isDrawerOpen);
|
||||
const activeConversationId = useAIAssistantStore(
|
||||
(s) => s.activeConversationId,
|
||||
@@ -28,10 +26,10 @@ export default function AIAssistantDrawer(): JSX.Element {
|
||||
return;
|
||||
}
|
||||
closeDrawer();
|
||||
history.push(
|
||||
navigate(
|
||||
ROUTES.AI_ASSISTANT.replace(':conversationId', activeConversationId),
|
||||
);
|
||||
}, [activeConversationId, closeDrawer, history]);
|
||||
}, [activeConversationId, closeDrawer]);
|
||||
|
||||
const handleNewConversation = useCallback(() => {
|
||||
startNewConversation();
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import { useCallback, useEffect, useState } from 'react';
|
||||
import { createPortal } from 'react-dom';
|
||||
import { useHistory, useLocation } from 'react-router-dom';
|
||||
import { Button } from '@signozhq/ui/button';
|
||||
import { navigate } from 'lib/router/navigation';
|
||||
import { useAppLocation } from 'lib/router/useAppLocation';
|
||||
import { TooltipSimple } from '@signozhq/ui/tooltip';
|
||||
import ROUTES from 'constants/routes';
|
||||
import { History, Maximize2, Minus, Plus, X } from '@signozhq/icons';
|
||||
@@ -31,8 +32,7 @@ import styles from './AIAssistantModal.module.scss';
|
||||
*/
|
||||
// eslint-disable-next-line sonarjs/cognitive-complexity
|
||||
export default function AIAssistantModal(): JSX.Element | null {
|
||||
const history = useHistory();
|
||||
const { pathname } = useLocation();
|
||||
const { pathname } = useAppLocation();
|
||||
const [showHistory, setShowHistory] = useState(false);
|
||||
|
||||
const isOpen = useAIAssistantStore((s) => s.isModalOpen);
|
||||
@@ -94,11 +94,11 @@ export default function AIAssistantModal(): JSX.Element | null {
|
||||
// Router state tells AIAssistantPage to skip its mount-time Opened fire:
|
||||
// the assistant was already open in the modal, so this is a surface
|
||||
// switch, not a new open.
|
||||
history.push(
|
||||
navigate(
|
||||
ROUTES.AI_ASSISTANT.replace(':conversationId', activeConversationId),
|
||||
{ fromInApp: true },
|
||||
{ state: { fromInApp: true } },
|
||||
);
|
||||
}, [activeConversationId, closeModal, history]);
|
||||
}, [activeConversationId, closeModal]);
|
||||
|
||||
const handleNew = useCallback(() => {
|
||||
void logEvent(AIAssistantEvents.NewChatClicked, {
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import { useCallback, useLayoutEffect, useRef, useState } from 'react';
|
||||
import { matchPath, useHistory, useLocation } from 'react-router-dom';
|
||||
import { Button } from '@signozhq/ui/button';
|
||||
import { matchRoute } from 'lib/router/matchRoute';
|
||||
import { navigate } from 'lib/router/navigation';
|
||||
import { useAppLocation } from 'lib/router/useAppLocation';
|
||||
import { TooltipSimple } from '@signozhq/ui/tooltip';
|
||||
import ROUTES from 'constants/routes';
|
||||
import { History, Maximize2, Plus, X } from '@signozhq/icons';
|
||||
@@ -21,13 +23,11 @@ const AI_ASSISTANT_PANEL_OPEN_CLASS = 'ai-assistant-panel-open';
|
||||
const AI_ASSISTANT_PANEL_WIDTH_VAR = '--ai-assistant-panel-width';
|
||||
|
||||
export default function AIAssistantPanel(): JSX.Element | null {
|
||||
const history = useHistory();
|
||||
const { pathname } = useLocation();
|
||||
const { pathname } = useAppLocation();
|
||||
const [showHistory, setShowHistory] = useState(false);
|
||||
|
||||
const isOpen = useAIAssistantStore((s) => s.isDrawerOpen);
|
||||
const isFullScreenPage = !!matchPath(pathname, {
|
||||
path: ROUTES.AI_ASSISTANT,
|
||||
const isFullScreenPage = !!matchRoute(pathname, ROUTES.AI_ASSISTANT, {
|
||||
exact: true,
|
||||
});
|
||||
const activeConversationId = useAIAssistantStore(
|
||||
@@ -47,11 +47,11 @@ export default function AIAssistantPanel(): JSX.Element | null {
|
||||
// Router state tells AIAssistantPage to skip its mount-time Opened fire:
|
||||
// the assistant was already open in the drawer, so this is a surface
|
||||
// switch, not a new open.
|
||||
history.push(
|
||||
navigate(
|
||||
ROUTES.AI_ASSISTANT.replace(':conversationId', activeConversationId),
|
||||
{ fromInApp: true },
|
||||
{ state: { fromInApp: true } },
|
||||
);
|
||||
}, [activeConversationId, closeDrawer, history]);
|
||||
}, [activeConversationId, closeDrawer]);
|
||||
|
||||
const handleNew = useCallback(() => {
|
||||
void logEvent(AIAssistantEvents.NewChatClicked, {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { useCallback } from 'react';
|
||||
import { matchPath, useLocation } from 'react-router-dom';
|
||||
import { Button } from '@signozhq/ui/button';
|
||||
import { matchRoute } from 'lib/router/matchRoute';
|
||||
import { useAppLocation } from 'lib/router/useAppLocation';
|
||||
import { TooltipSimple } from '@signozhq/ui/tooltip';
|
||||
import logEvent from 'api/common/logEvent';
|
||||
import ROUTES from 'constants/routes';
|
||||
@@ -21,12 +22,11 @@ import styles from './AIAssistantTrigger.module.scss';
|
||||
* Hidden when the panel is already open or when on the full-screen AI Assistant page.
|
||||
*/
|
||||
export default function AIAssistantTrigger(): JSX.Element | null {
|
||||
const { pathname } = useLocation();
|
||||
const { pathname } = useAppLocation();
|
||||
const isDrawerOpen = useAIAssistantStore((s) => s.isDrawerOpen);
|
||||
const isModalOpen = useAIAssistantStore((s) => s.isModalOpen);
|
||||
|
||||
const isFullScreenPage = !!matchPath(pathname, {
|
||||
path: ROUTES.AI_ASSISTANT,
|
||||
const isFullScreenPage = !!matchRoute(pathname, ROUTES.AI_ASSISTANT, {
|
||||
exact: true,
|
||||
});
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||
import { useLocation } from 'react-router-dom';
|
||||
import cx from 'classnames';
|
||||
import { useAppLocation } from 'lib/router/useAppLocation';
|
||||
|
||||
import logEvent from 'api/common/logEvent';
|
||||
|
||||
@@ -26,7 +26,7 @@ export default function ConversationView({
|
||||
}: ConversationViewProps): JSX.Element {
|
||||
const variant = useVariant();
|
||||
const isCompact = variant === 'panel';
|
||||
const location = useLocation();
|
||||
const location = useAppLocation();
|
||||
|
||||
const conversation = useAIAssistantStore(
|
||||
(s) => s.conversations[conversationId],
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
import { MemoryRouter } from 'react-router-dom';
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import { fireEvent, render } from '@testing-library/react';
|
||||
import { MessageContext } from 'api/ai-assistant/chat';
|
||||
import { useAIAssistantStore } from 'container/AIAssistant/store/useAIAssistantStore';
|
||||
import { VariantContext } from 'container/AIAssistant/VariantContext';
|
||||
import { TestRouter } from 'tests/router';
|
||||
|
||||
const CHIP_ID = 'recent-errors';
|
||||
const CHIP_TEXT = 'Show me recent errors';
|
||||
@@ -87,11 +86,11 @@ function renderView(variant: 'panel' | 'page' | 'modal'): {
|
||||
getByTestId: (id: string) => HTMLElement;
|
||||
} {
|
||||
return render(
|
||||
<MemoryRouter initialEntries={['/dashboard/dashboard-123']}>
|
||||
<TestRouter initialRoute="/dashboard/dashboard-123">
|
||||
<VariantContext.Provider value={variant}>
|
||||
<ConversationView conversationId={CONVERSATION_ID} />
|
||||
</VariantContext.Provider>
|
||||
</MemoryRouter>,
|
||||
</TestRouter>,
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
import { matchPath, useHistory, useLocation } from 'react-router-dom';
|
||||
import { convertFiltersToExpression } from 'components/QueryBuilderV2/utils';
|
||||
import { matchRoute } from 'lib/router/matchRoute';
|
||||
import { navigate } from 'lib/router/navigation';
|
||||
import { useAppLocation } from 'lib/router/useAppLocation';
|
||||
import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder';
|
||||
import { mapQueryDataFromApi } from 'lib/newQueryBuilder/queryBuilderMappers/mapQueryDataFromApi';
|
||||
import { ICompositeMetricQuery } from 'types/api/alerts/compositeQuery';
|
||||
@@ -215,20 +217,11 @@ function signalMatchesPathname(
|
||||
): boolean {
|
||||
switch (signal) {
|
||||
case ApplyFilterSignalDTO.logs:
|
||||
return Boolean(
|
||||
matchPath(pathname, { path: ROUTES.LOGS_EXPLORER, exact: false }),
|
||||
);
|
||||
return Boolean(matchRoute(pathname, ROUTES.LOGS_EXPLORER));
|
||||
case ApplyFilterSignalDTO.traces:
|
||||
return Boolean(
|
||||
matchPath(pathname, { path: ROUTES.TRACES_EXPLORER, exact: false }),
|
||||
);
|
||||
return Boolean(matchRoute(pathname, ROUTES.TRACES_EXPLORER));
|
||||
case ApplyFilterSignalDTO.metrics:
|
||||
return Boolean(
|
||||
matchPath(pathname, {
|
||||
path: ROUTES.METRICS_EXPLORER_EXPLORER,
|
||||
exact: false,
|
||||
}),
|
||||
);
|
||||
return Boolean(matchRoute(pathname, ROUTES.METRICS_EXPLORER_EXPLORER));
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
@@ -277,7 +270,6 @@ function explorerRouteForSignal(signal: ApplyFilterSignalDTO): string | null {
|
||||
}
|
||||
|
||||
interface ApplyFilterDeps {
|
||||
history: ReturnType<typeof useHistory>;
|
||||
pathname: string;
|
||||
redirectWithQueryBuilderData: ReturnType<
|
||||
typeof useQueryBuilder
|
||||
@@ -362,9 +354,9 @@ function applyFilter(action: MessageActionDTO, deps: ApplyFilterDeps): void {
|
||||
return;
|
||||
}
|
||||
// eslint-disable-next-line no-console
|
||||
console.log('[apply_filter] off-page → history.push', base);
|
||||
console.log('[apply_filter] off-page → navigate', base);
|
||||
const encoded = encodeURIComponent(JSON.stringify(normalized));
|
||||
deps.history.push(`${base}?${QueryParams.compositeQuery}=${encoded}`);
|
||||
navigate(`${base}?${QueryParams.compositeQuery}=${encoded}`);
|
||||
}
|
||||
|
||||
/** Picks the right rollback API call for a given action kind. */
|
||||
@@ -393,8 +385,7 @@ export default function ActionsSection({
|
||||
actions,
|
||||
messageId,
|
||||
}: ActionsSectionProps): JSX.Element | null {
|
||||
const history = useHistory();
|
||||
const { pathname } = useLocation();
|
||||
const { pathname } = useAppLocation();
|
||||
const sendMessage = useAIAssistantStore((s) => s.sendMessage);
|
||||
const { threadId, page, mode } = useAIAssistantAnalyticsContext();
|
||||
const { redirectWithQueryBuilderData, handleSetQueryData } = useQueryBuilder();
|
||||
@@ -426,19 +417,12 @@ export default function ActionsSection({
|
||||
}
|
||||
autoAppliedFilterKeys.add(key);
|
||||
applyFilter(action, {
|
||||
history,
|
||||
pathname,
|
||||
redirectWithQueryBuilderData,
|
||||
handleSetQueryData,
|
||||
});
|
||||
});
|
||||
}, [
|
||||
actions,
|
||||
pathname,
|
||||
history,
|
||||
redirectWithQueryBuilderData,
|
||||
handleSetQueryData,
|
||||
]);
|
||||
}, [actions, pathname, redirectWithQueryBuilderData, handleSetQueryData]);
|
||||
|
||||
if (actions.length === 0) {
|
||||
return null;
|
||||
@@ -458,11 +442,7 @@ export default function ActionsSection({
|
||||
}
|
||||
setResult(key, { state: 'loading' });
|
||||
try {
|
||||
await openSavedViewByKey(
|
||||
resourceId,
|
||||
resolveSavedViewSourceHint(action),
|
||||
history,
|
||||
);
|
||||
await openSavedViewByKey(resourceId, resolveSavedViewSourceHint(action));
|
||||
void logEvent(AIAssistantEvents.ResourceOpened, {
|
||||
threadId,
|
||||
messageId,
|
||||
@@ -517,7 +497,7 @@ export default function ActionsSection({
|
||||
targetModule: targetModuleForResource(resourceType),
|
||||
resourceId,
|
||||
});
|
||||
history.push(path);
|
||||
navigate(path);
|
||||
};
|
||||
|
||||
const handleClick = (key: string, action: MessageActionDTO): void => {
|
||||
@@ -578,7 +558,6 @@ export default function ActionsSection({
|
||||
});
|
||||
}
|
||||
applyFilter(action, {
|
||||
history,
|
||||
pathname,
|
||||
redirectWithQueryBuilderData,
|
||||
handleSetQueryData,
|
||||
|
||||
@@ -6,13 +6,13 @@ import {
|
||||
import { getAllViews } from 'api/saveView/getAllViews';
|
||||
import { getViewById } from 'api/saveView/getViewById';
|
||||
import ROUTES from 'constants/routes';
|
||||
import { navigate } from 'lib/router/navigation';
|
||||
import { QueryParams } from 'constants/query';
|
||||
import { PANEL_TYPES } from 'constants/queryBuilder';
|
||||
import { ICompositeMetricQuery } from 'types/api/alerts/compositeQuery';
|
||||
import { AllViewsProps, ViewProps } from 'types/api/saveViews/types';
|
||||
import { DataSource } from 'types/common/queryBuilder';
|
||||
import { AxiosResponse } from 'axios';
|
||||
import type { History } from 'history';
|
||||
|
||||
import {
|
||||
buildExplorerNavigationUrl,
|
||||
@@ -33,6 +33,9 @@ import { resourceRoute, ResourceType } from '../resourceRoute';
|
||||
|
||||
jest.mock('api/saveView/getAllViews');
|
||||
jest.mock('api/saveView/getViewById');
|
||||
jest.mock('lib/router/navigation', () => ({
|
||||
navigate: jest.fn(),
|
||||
}));
|
||||
|
||||
jest.mock(
|
||||
'lib/newQueryBuilder/queryBuilderMappers/mapQueryDataFromApi',
|
||||
@@ -54,6 +57,7 @@ const mockedGetAllViews = getAllViews as jest.MockedFunction<
|
||||
const mockedGetViewById = getViewById as jest.MockedFunction<
|
||||
typeof getViewById
|
||||
>;
|
||||
const mockedNavigate = navigate as jest.MockedFunction<typeof navigate>;
|
||||
|
||||
function makeView(id: string, sourcePage: DataSource): ViewProps {
|
||||
return {
|
||||
@@ -224,15 +228,17 @@ describe('buildExplorerNavigationUrl', () => {
|
||||
});
|
||||
|
||||
describe('openSavedView', () => {
|
||||
it('navigates with history.push and view query params', () => {
|
||||
const push = jest.fn();
|
||||
const history = { push } as unknown as History;
|
||||
beforeEach(() => {
|
||||
mockedNavigate.mockClear();
|
||||
});
|
||||
|
||||
it('navigates with the view query params', () => {
|
||||
const view = makeView('view-logs', DataSource.LOGS);
|
||||
|
||||
openSavedView(view, history);
|
||||
openSavedView(view);
|
||||
|
||||
expect(push).toHaveBeenCalledTimes(1);
|
||||
const pushedUrl = push.mock.calls[0][0] as string;
|
||||
expect(mockedNavigate).toHaveBeenCalledTimes(1);
|
||||
const pushedUrl = mockedNavigate.mock.calls[0][0] as string;
|
||||
expect(pushedUrl).toContain(ROUTES.LOGS_EXPLORER);
|
||||
expect(pushedUrl).toContain(QueryParams.viewKey);
|
||||
});
|
||||
@@ -242,42 +248,37 @@ describe('openSavedViewByKey', () => {
|
||||
beforeEach(() => {
|
||||
mockedGetAllViews.mockReset();
|
||||
mockedGetViewById.mockReset();
|
||||
mockedNavigate.mockClear();
|
||||
});
|
||||
|
||||
it('prefers the direct view lookup endpoint', async () => {
|
||||
const view = makeView('view-logs', DataSource.LOGS);
|
||||
mockedGetViewById.mockResolvedValueOnce(mockViewByIdResponse(view));
|
||||
const push = jest.fn();
|
||||
const history = { push } as unknown as History;
|
||||
|
||||
await openSavedViewByKey('view-logs', DataSource.LOGS, history);
|
||||
await openSavedViewByKey('view-logs', DataSource.LOGS);
|
||||
|
||||
expect(mockedGetViewById).toHaveBeenCalledWith('view-logs');
|
||||
expect(mockedGetAllViews).not.toHaveBeenCalled();
|
||||
expect(push).toHaveBeenCalled();
|
||||
expect(mockedNavigate).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('falls back to list probing when direct lookup fails', async () => {
|
||||
const view = makeView('view-traces', DataSource.TRACES);
|
||||
mockedGetViewById.mockRejectedValueOnce(new Error('not found'));
|
||||
mockedGetAllViews.mockResolvedValueOnce(mockViewsResponse([view]));
|
||||
const push = jest.fn();
|
||||
const history = { push } as unknown as History;
|
||||
|
||||
await openSavedViewByKey('view-traces', DataSource.TRACES, history);
|
||||
await openSavedViewByKey('view-traces', DataSource.TRACES);
|
||||
|
||||
expect(mockedGetAllViews).toHaveBeenCalledWith(DataSource.TRACES);
|
||||
expect(push).toHaveBeenCalled();
|
||||
expect(mockedNavigate).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('throws when the saved view does not exist', async () => {
|
||||
mockedGetViewById.mockRejectedValueOnce(new Error('not found'));
|
||||
mockedGetAllViews.mockResolvedValue(mockViewsResponse([]));
|
||||
|
||||
await expect(
|
||||
openSavedViewByKey('missing', DataSource.LOGS, {
|
||||
push: jest.fn(),
|
||||
} as unknown as History),
|
||||
).rejects.toThrow('Saved view not found');
|
||||
await expect(openSavedViewByKey('missing', DataSource.LOGS)).rejects.toThrow(
|
||||
'Saved view not found',
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -3,11 +3,11 @@ import { getViewById } from 'api/saveView/getViewById';
|
||||
import { QueryParams } from 'constants/query';
|
||||
import { PANEL_TYPES } from 'constants/queryBuilder';
|
||||
import { mapQueryDataFromApi } from 'lib/newQueryBuilder/queryBuilderMappers/mapQueryDataFromApi';
|
||||
import { navigate } from 'lib/router/navigation';
|
||||
import { SOURCEPAGE_VS_ROUTES } from 'pages/SaveView/constants';
|
||||
import { ViewProps } from 'types/api/saveViews/types';
|
||||
import { DataSource } from 'types/common/queryBuilder';
|
||||
import { Query } from 'types/api/queryBuilder/queryBuilderData';
|
||||
import { History } from 'history';
|
||||
|
||||
type SavedViewSourceHint = DataSource | 'meter';
|
||||
|
||||
@@ -85,7 +85,7 @@ export function buildExplorerNavigationUrl(
|
||||
return `${route}?${params.toString()}`;
|
||||
}
|
||||
|
||||
export function openSavedView(view: ViewProps, history: History): void {
|
||||
export function openSavedView(view: ViewProps): void {
|
||||
const route = explorerRouteForSourcePage(view.sourcePage);
|
||||
if (!route) {
|
||||
throw new Error('Unsupported saved view source');
|
||||
@@ -101,16 +101,15 @@ export function openSavedView(view: ViewProps, history: History): void {
|
||||
[QueryParams.viewName]: view.name,
|
||||
[QueryParams.viewKey]: view.id,
|
||||
});
|
||||
history.push(url);
|
||||
navigate(url);
|
||||
}
|
||||
|
||||
export async function openSavedViewByKey(
|
||||
viewKey: string,
|
||||
sourceHint: SavedViewSourceHint | null | undefined,
|
||||
history: History,
|
||||
): Promise<void> {
|
||||
const view = await loadSavedView(viewKey, sourceHint);
|
||||
openSavedView(view, history);
|
||||
openSavedView(view);
|
||||
}
|
||||
|
||||
/** @deprecated Use findSavedViewInLists — kept for tests. */
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import type { MessageContext } from 'api/ai-assistant/chat';
|
||||
import { QueryParams } from 'constants/query';
|
||||
import ROUTES from 'constants/routes';
|
||||
import { matchRoute } from 'lib/router/matchRoute';
|
||||
import { AlertListTabs } from 'pages/AlertList/types';
|
||||
import { NEW_PANEL_ID } from 'pages/DashboardPage/DashboardContainer/PanelEditor/newPanelRoute';
|
||||
import { matchPath } from 'react-router-dom';
|
||||
|
||||
/**
|
||||
* Resolves the page the user is currently on into structured `MessageContext`
|
||||
@@ -33,9 +33,10 @@ export function getAutoContexts(
|
||||
|
||||
// Panel editor (V2). `panel/new` has no widget id yet and the schema requires
|
||||
// a non-empty `panel_edit.widgetId`, so it reports `panel_create` instead.
|
||||
const panelEditorMatch = matchPath<{ dashboardId: string; panelId: string }>(
|
||||
const panelEditorMatch = matchRoute<'dashboardId' | 'panelId'>(
|
||||
pathname,
|
||||
{ path: ROUTES.DASHBOARD_PANEL_EDITOR, exact: true },
|
||||
ROUTES.DASHBOARD_PANEL_EDITOR,
|
||||
{ exact: true },
|
||||
);
|
||||
if (panelEditorMatch) {
|
||||
const { dashboardId, panelId } = panelEditorMatch.params;
|
||||
@@ -55,8 +56,7 @@ export function getAutoContexts(
|
||||
// Dashboard detail — `/dashboard/:dashboardId`. The `expandedWidgetId`
|
||||
// query param signals the panel-fullscreen overlay; otherwise it's the
|
||||
// plain dashboard view.
|
||||
const dashboardMatch = matchPath<{ dashboardId: string }>(pathname, {
|
||||
path: ROUTES.DASHBOARD,
|
||||
const dashboardMatch = matchRoute<'dashboardId'>(pathname, ROUTES.DASHBOARD, {
|
||||
exact: true,
|
||||
});
|
||||
if (dashboardMatch) {
|
||||
@@ -89,7 +89,7 @@ export function getAutoContexts(
|
||||
}
|
||||
|
||||
// Dashboard list — `/dashboard`.
|
||||
if (matchPath(pathname, { path: ROUTES.ALL_DASHBOARD, exact: true })) {
|
||||
if (matchRoute(pathname, ROUTES.ALL_DASHBOARD, { exact: true })) {
|
||||
return [
|
||||
{
|
||||
source: 'auto',
|
||||
@@ -106,8 +106,8 @@ export function getAutoContexts(
|
||||
// or `/alerts/history?ruleId=…`. Mirrors dashboard_detail: resourceId is the
|
||||
// rule id and shared metadata carries the URL time range when present.
|
||||
if (
|
||||
matchPath(pathname, { path: ROUTES.ALERT_OVERVIEW, exact: true }) ||
|
||||
matchPath(pathname, { path: ROUTES.ALERT_HISTORY, exact: true })
|
||||
matchRoute(pathname, ROUTES.ALERT_OVERVIEW, { exact: true }) ||
|
||||
matchRoute(pathname, ROUTES.ALERT_HISTORY, { exact: true })
|
||||
) {
|
||||
const ruleId = params.get(QueryParams.ruleId);
|
||||
if (ruleId) {
|
||||
@@ -129,7 +129,7 @@ export function getAutoContexts(
|
||||
// Alert edit — `/alerts/edit?ruleId=…`. The form syncs its query-builder
|
||||
// state to the URL (`useShareBuilderUrl`), so shared metadata carries the
|
||||
// alert's query + time range, mirroring the dashboard panel editor.
|
||||
if (matchPath(pathname, { path: ROUTES.EDIT_ALERTS, exact: true })) {
|
||||
if (matchRoute(pathname, ROUTES.EDIT_ALERTS, { exact: true })) {
|
||||
const ruleId = params.get(QueryParams.ruleId);
|
||||
if (ruleId) {
|
||||
return [
|
||||
@@ -145,7 +145,7 @@ export function getAutoContexts(
|
||||
|
||||
// Alert new — `/alerts/new`. No rule id yet (draft), but the query-builder
|
||||
// state is on the URL, so shared metadata carries the in-progress query.
|
||||
if (matchPath(pathname, { path: ROUTES.ALERTS_NEW, exact: true })) {
|
||||
if (matchRoute(pathname, ROUTES.ALERTS_NEW, { exact: true })) {
|
||||
return [
|
||||
{
|
||||
source: 'auto',
|
||||
@@ -157,7 +157,7 @@ export function getAutoContexts(
|
||||
}
|
||||
|
||||
// Triggered-alerts index — `/alerts/history` without a rule id.
|
||||
if (matchPath(pathname, { path: ROUTES.ALERT_HISTORY, exact: true })) {
|
||||
if (matchRoute(pathname, ROUTES.ALERT_HISTORY, { exact: true })) {
|
||||
return [
|
||||
{
|
||||
source: 'auto',
|
||||
@@ -172,7 +172,7 @@ export function getAutoContexts(
|
||||
}
|
||||
|
||||
// Alerts index — `/alerts` with tab query param (defaults to Alert Rules).
|
||||
if (matchPath(pathname, { path: ROUTES.LIST_ALL_ALERT, exact: true })) {
|
||||
if (matchRoute(pathname, ROUTES.LIST_ALL_ALERT, { exact: true })) {
|
||||
const page = resolveAlertsIndexPage(params.get(QueryParams.tab));
|
||||
return [
|
||||
{
|
||||
@@ -191,10 +191,10 @@ export function getAutoContexts(
|
||||
|
||||
// Service detail (covers sub-routes like top-level-operations) —
|
||||
// `/services/:servicename[/...]`.
|
||||
const serviceMatch = matchPath<{ servicename: string }>(pathname, {
|
||||
path: ROUTES.SERVICE_METRICS,
|
||||
exact: false,
|
||||
});
|
||||
const serviceMatch = matchRoute<'servicename'>(
|
||||
pathname,
|
||||
ROUTES.SERVICE_METRICS,
|
||||
);
|
||||
if (serviceMatch?.params.servicename) {
|
||||
return [
|
||||
{
|
||||
@@ -210,7 +210,7 @@ export function getAutoContexts(
|
||||
}
|
||||
|
||||
// Services list — `/services`.
|
||||
if (matchPath(pathname, { path: ROUTES.APPLICATION, exact: true })) {
|
||||
if (matchRoute(pathname, ROUTES.APPLICATION, { exact: true })) {
|
||||
return [
|
||||
{
|
||||
source: 'auto',
|
||||
@@ -226,7 +226,7 @@ export function getAutoContexts(
|
||||
|
||||
// ── Logs ──────────────────────────────────────────────────────────────────
|
||||
|
||||
if (matchPath(pathname, { path: ROUTES.LOGS_EXPLORER, exact: false })) {
|
||||
if (matchRoute(pathname, ROUTES.LOGS_EXPLORER)) {
|
||||
const activeLogId = params.get(QueryParams.activeLogId);
|
||||
// `?activeLogId=…` indicates a log-detail panel is open. Per the
|
||||
// schema, log_detail requires payload fields (timestamp, service,
|
||||
@@ -251,8 +251,7 @@ export function getAutoContexts(
|
||||
|
||||
// Trace detail — `/trace/:id`. Treated as a detail-as-metadata page
|
||||
// (resourceId null, `traceId` lives in metadata).
|
||||
const traceMatch = matchPath<{ id: string }>(pathname, {
|
||||
path: ROUTES.TRACE_DETAIL,
|
||||
const traceMatch = matchRoute<'id'>(pathname, ROUTES.TRACE_DETAIL, {
|
||||
exact: true,
|
||||
});
|
||||
if (traceMatch?.params.id) {
|
||||
@@ -272,7 +271,7 @@ export function getAutoContexts(
|
||||
];
|
||||
}
|
||||
|
||||
if (matchPath(pathname, { path: ROUTES.TRACES_EXPLORER, exact: false })) {
|
||||
if (matchRoute(pathname, ROUTES.TRACES_EXPLORER)) {
|
||||
return [
|
||||
{
|
||||
source: 'auto',
|
||||
@@ -289,9 +288,7 @@ export function getAutoContexts(
|
||||
// ── Metrics ───────────────────────────────────────────────────────────────
|
||||
|
||||
// Metrics explorer — `/metrics-explorer` and sub-routes (summary, explorer, views).
|
||||
if (
|
||||
matchPath(pathname, { path: ROUTES.METRICS_EXPLORER_BASE, exact: false })
|
||||
) {
|
||||
if (matchRoute(pathname, ROUTES.METRICS_EXPLORER_BASE)) {
|
||||
return [
|
||||
{
|
||||
source: 'auto',
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { matchPath, useLocation } from 'react-router-dom';
|
||||
|
||||
import ROUTES from 'constants/routes';
|
||||
import { matchRoute } from 'lib/router/matchRoute';
|
||||
import { useAppLocation } from 'lib/router/useAppLocation';
|
||||
|
||||
import { useAIAssistantStore } from '../store/useAIAssistantStore';
|
||||
import { useVariant } from '../VariantContext';
|
||||
@@ -28,7 +28,7 @@ const ROUTE_TEMPLATES = Object.values(ROUTES).sort(
|
||||
|
||||
export function normalizePage(pathname: string): string {
|
||||
for (const template of ROUTE_TEMPLATES) {
|
||||
if (matchPath(pathname, { path: template, exact: true })) {
|
||||
if (matchRoute(pathname, template, { exact: true })) {
|
||||
return template;
|
||||
}
|
||||
}
|
||||
@@ -46,7 +46,7 @@ export function normalizePage(pathname: string): string {
|
||||
export function useAIAssistantAnalyticsContext(
|
||||
conversationId?: string,
|
||||
): AIAssistantAnalyticsContext {
|
||||
const { pathname } = useLocation();
|
||||
const { pathname } = useAppLocation();
|
||||
const variant = useVariant();
|
||||
const threadId = useAIAssistantStore((s) => {
|
||||
const id = conversationId ?? s.activeConversationId;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { PageTypeDTO } from 'api/ai-assistant/sigNozAIAssistantAPI.schemas';
|
||||
import { QueryParams } from 'constants/query';
|
||||
import ROUTES from 'constants/routes';
|
||||
import { matchPath } from 'react-router-dom';
|
||||
import { matchRoute } from 'lib/router/matchRoute';
|
||||
|
||||
import { getAutoContexts } from './getAutoContexts';
|
||||
|
||||
@@ -45,15 +45,10 @@ export function resolvePageType(
|
||||
// Pseudo-pages with no attachable resource: resolved straight from the
|
||||
// route. They deliberately emit no auto-context chip (see `getAutoContexts`),
|
||||
// so they can't be derived from `metadata.page` like the pages below.
|
||||
if (matchPath(pathname, { path: ROUTES.HOME, exact: true })) {
|
||||
if (matchRoute(pathname, ROUTES.HOME, { exact: true })) {
|
||||
return PageTypeDTO.homepage;
|
||||
}
|
||||
if (
|
||||
matchPath(pathname, {
|
||||
path: ROUTES.INFRASTRUCTURE_MONITORING_BASE,
|
||||
exact: false,
|
||||
})
|
||||
) {
|
||||
if (matchRoute(pathname, ROUTES.INFRASTRUCTURE_MONITORING_BASE)) {
|
||||
return PageTypeDTO.infra_entity_detail;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import React from 'react';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { AppLink } from 'lib/router/AppLink';
|
||||
import { Color } from '@signozhq/design-tokens';
|
||||
import { Popover } from 'antd';
|
||||
import LogsIcon from 'assets/AlertHistory/LogsIcon';
|
||||
@@ -26,7 +26,7 @@ function PopoverContent({
|
||||
return (
|
||||
<div className="contributor-row-popover-buttons">
|
||||
{!!relatedLogsLink && (
|
||||
<Link
|
||||
<AppLink
|
||||
to={`${ROUTES.LOGS_EXPLORER}?${relatedLogsLink}`}
|
||||
className="contributor-row-popover-buttons__button"
|
||||
data-testid="alert-popover-view-logs"
|
||||
@@ -35,10 +35,10 @@ function PopoverContent({
|
||||
<LogsIcon />
|
||||
</div>
|
||||
<div className="text">View Logs</div>
|
||||
</Link>
|
||||
</AppLink>
|
||||
)}
|
||||
{!!relatedTracesLink && (
|
||||
<Link
|
||||
<AppLink
|
||||
to={`${ROUTES.TRACES_EXPLORER}?${relatedTracesLink}`}
|
||||
className="contributor-row-popover-buttons__button"
|
||||
data-testid="alert-popover-view-traces"
|
||||
@@ -50,7 +50,7 @@ function PopoverContent({
|
||||
/>
|
||||
</div>
|
||||
<div className="text">View Traces</div>
|
||||
</Link>
|
||||
</AppLink>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { useMemo, useState } from 'react';
|
||||
import { useLocation } from 'react-router-dom';
|
||||
import { Color } from '@signozhq/design-tokens';
|
||||
import { Button } from 'antd';
|
||||
import { useIsDarkMode } from 'hooks/useDarkMode';
|
||||
import history from 'lib/history';
|
||||
import { useAppLocation } from 'lib/router/useAppLocation';
|
||||
import { navigate } from 'lib/router/navigation';
|
||||
import { ArrowRight } from '@signozhq/icons';
|
||||
|
||||
import TopContributorsContent from './TopContributorsContent';
|
||||
@@ -16,7 +16,7 @@ function TopContributorsCard({
|
||||
topContributorsData,
|
||||
totalCurrentTriggers,
|
||||
}: TopContributorsCardProps): JSX.Element {
|
||||
const { search } = useLocation();
|
||||
const { search } = useAppLocation();
|
||||
const searchParams = useMemo(() => new URLSearchParams(search), [search]);
|
||||
|
||||
const viewAllTopContributorsParam = searchParams.get('viewAllTopContributors');
|
||||
@@ -43,7 +43,7 @@ function TopContributorsCard({
|
||||
|
||||
return newState;
|
||||
});
|
||||
history.push({ search: searchParams.toString() });
|
||||
navigate({ search: searchParams.toString() });
|
||||
};
|
||||
|
||||
return (
|
||||
|
||||
@@ -7,7 +7,7 @@ import { QueryParams } from 'constants/query';
|
||||
import { useIsDarkMode } from 'hooks/useDarkMode';
|
||||
import { useResizeObserver } from 'hooks/useDimensions';
|
||||
import useUrlQuery from 'hooks/useUrlQuery';
|
||||
import history from 'lib/history';
|
||||
import { navigate } from 'lib/router/navigation';
|
||||
import heatmapPlugin from 'lib/uPlotLib/plugins/heatmapPlugin';
|
||||
import timelinePlugin from 'lib/uPlotLib/plugins/timelinePlugin';
|
||||
import { uPlotXAxisValuesFormat } from 'lib/uPlotLib/utils/constants';
|
||||
@@ -103,7 +103,7 @@ function HorizontalTimelineGraph({
|
||||
urlQuery.set(QueryParams.startTime, startTimestamp.toString());
|
||||
urlQuery.set(QueryParams.endTime, endTimestamp.toString());
|
||||
|
||||
history.push({
|
||||
navigate({
|
||||
search: urlQuery.toString(),
|
||||
});
|
||||
}
|
||||
|
||||