mirror of
https://github.com/logto-io/logto.git
synced 2024-12-16 20:26:19 -05:00
feat(app-insights): add React context provider and hook
and fix AppInsights init issue for frontend projects.
This commit is contained in:
parent
4331deb6f2
commit
748878ce5b
35 changed files with 246 additions and 93 deletions
7
.changeset/witty-beers-complain.md
Normal file
7
.changeset/witty-beers-complain.md
Normal file
|
@ -0,0 +1,7 @@
|
|||
---
|
||||
"@logto/app-insights": minor
|
||||
"@logto/console": patch
|
||||
"@logto/ui": patch
|
||||
---
|
||||
|
||||
add React context and hook to app-insights, fix init issue for frontend projects
|
|
@ -12,11 +12,15 @@
|
|||
"./*": {
|
||||
"import": "./lib/*.js",
|
||||
"types": "./lib/*.d.ts"
|
||||
},
|
||||
"./react": {
|
||||
"import": "./lib/react/index.js",
|
||||
"types": "./lib/react/index.d.ts"
|
||||
}
|
||||
},
|
||||
"//": "This field is for parcel. Remove after https://github.com/parcel-bundler/parcel/pull/8807 published.",
|
||||
"alias": {
|
||||
"./react": "./lib/react.js"
|
||||
"./react": "./lib/react/index.js"
|
||||
},
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
|
@ -32,7 +36,9 @@
|
|||
},
|
||||
"devDependencies": {
|
||||
"@silverhand/eslint-config": "3.0.1",
|
||||
"@silverhand/eslint-config-react": "3.0.1",
|
||||
"@silverhand/ts-config": "3.0.0",
|
||||
"@silverhand/ts-config-react": "3.0.0",
|
||||
"@types/node": "^18.11.18",
|
||||
"@types/react": "^18.0.31",
|
||||
"eslint": "^8.34.0",
|
||||
|
@ -47,7 +53,7 @@
|
|||
"node": "^18.12.0"
|
||||
},
|
||||
"eslintConfig": {
|
||||
"extends": "@silverhand"
|
||||
"extends": "@silverhand/react"
|
||||
},
|
||||
"prettier": "@silverhand/eslint-config/.prettierrc",
|
||||
"dependencies": {
|
||||
|
|
|
@ -8,9 +8,10 @@ import { type ComponentType } from 'react';
|
|||
export type SetupConfig = {
|
||||
connectionString?: string;
|
||||
clickPlugin?: IClickAnalyticsConfiguration;
|
||||
cookieDomain?: string;
|
||||
};
|
||||
|
||||
class AppInsightsReact {
|
||||
export class AppInsightsReact {
|
||||
protected reactPlugin?: ReactPlugin;
|
||||
protected clickAnalyticsPlugin?: ClickAnalyticsPlugin;
|
||||
protected withAITracking?: typeof withAITracking;
|
||||
|
@ -48,7 +49,8 @@ class AppInsightsReact {
|
|||
const { ApplicationInsights } = await import('@microsoft/applicationinsights-web');
|
||||
|
||||
// Conditionally load ClickAnalytics plugin
|
||||
const clickAnalyticsConfig = conditional(typeof config === 'object' && config.clickPlugin);
|
||||
const configObject = conditional(typeof config === 'object' && config) ?? {};
|
||||
const { cookieDomain, clickPlugin } = configObject;
|
||||
const initClickAnalyticsPlugin = async () => {
|
||||
const { ClickAnalyticsPlugin } = await import(
|
||||
'@microsoft/applicationinsights-clickanalytics-js'
|
||||
|
@ -62,13 +64,12 @@ class AppInsightsReact {
|
|||
this.reactPlugin = new ReactPlugin();
|
||||
|
||||
// Assign ClickAnalytics prop
|
||||
this.clickAnalyticsPlugin = conditional(
|
||||
clickAnalyticsConfig && (await initClickAnalyticsPlugin())
|
||||
);
|
||||
this.clickAnalyticsPlugin = conditional(clickPlugin && (await initClickAnalyticsPlugin()));
|
||||
|
||||
// Init ApplicationInsights instance
|
||||
this.appInsights = new ApplicationInsights({
|
||||
config: {
|
||||
cookieDomain,
|
||||
connectionString,
|
||||
enableAutoRouteTracking: false,
|
||||
extensions: conditionalArray<ITelemetryPlugin>(
|
||||
|
@ -77,18 +78,25 @@ class AppInsightsReact {
|
|||
),
|
||||
extensionConfig: conditional(
|
||||
this.clickAnalyticsPlugin && {
|
||||
[this.clickAnalyticsPlugin.identifier]: clickAnalyticsConfig,
|
||||
[this.clickAnalyticsPlugin.identifier]: clickPlugin,
|
||||
}
|
||||
),
|
||||
},
|
||||
});
|
||||
|
||||
this.appInsights.addTelemetryInitializer((item) => {
|
||||
// The key 'ai.cloud.role' is extracted from Node SDK
|
||||
// @see https://learn.microsoft.com/en-us/azure/azure-monitor/app/nodejs#multiple-roles-for-multi-component-applications
|
||||
// @see https://github.com/microsoft/ApplicationInsights-JS#example-setting-cloud-role-name
|
||||
// @see https://github.com/microsoft/ApplicationInsights-node.js/blob/a573e40fc66981c6a3106bdc5b783d1d94f64231/Schema/PublicSchema/ContextTagKeys.bond#L83
|
||||
// eslint-disable-next-line @silverhand/fp/no-mutation
|
||||
/* eslint-disable @silverhand/fp/no-mutation */
|
||||
item.tags = [...(item.tags ?? []), { 'ai.cloud.role': cloudRole }];
|
||||
|
||||
// Extract UTM parameters
|
||||
const searchParams = [...new URLSearchParams(window.location.search).entries()];
|
||||
item.data = {
|
||||
...item.data,
|
||||
...Object.fromEntries(searchParams.filter(([key]) => key.startsWith('utm_'))),
|
||||
};
|
||||
/* eslint-enable @silverhand/fp/no-mutation */
|
||||
});
|
||||
|
||||
this.appInsights.loadAppInsights();
|
||||
|
@ -112,3 +120,5 @@ class AppInsightsReact {
|
|||
}
|
||||
|
||||
export const appInsightsReact = new AppInsightsReact();
|
||||
|
||||
export const withAppInsights = appInsightsReact.withAppInsights.bind(appInsightsReact);
|
30
packages/app-insights/src/react/context.tsx
Normal file
30
packages/app-insights/src/react/context.tsx
Normal file
|
@ -0,0 +1,30 @@
|
|||
import { type ReactNode, createContext, useMemo, useState } from 'react';
|
||||
|
||||
type Context = {
|
||||
initialized: boolean;
|
||||
setInitialized: React.Dispatch<React.SetStateAction<boolean>>;
|
||||
};
|
||||
|
||||
export const AppInsightsContext = createContext<Context>({
|
||||
initialized: false,
|
||||
setInitialized: () => {
|
||||
throw new Error('Not implemented');
|
||||
},
|
||||
});
|
||||
|
||||
type Properties = {
|
||||
children: ReactNode;
|
||||
};
|
||||
|
||||
export const AppInsightsProvider = ({ children }: Properties) => {
|
||||
const [initialized, setInitialized] = useState(false);
|
||||
const context = useMemo<Context>(
|
||||
() => ({
|
||||
initialized,
|
||||
setInitialized,
|
||||
}),
|
||||
[initialized]
|
||||
);
|
||||
|
||||
return <AppInsightsContext.Provider value={context}>{children}</AppInsightsContext.Provider>;
|
||||
};
|
4
packages/app-insights/src/react/index.ts
Normal file
4
packages/app-insights/src/react/index.ts
Normal file
|
@ -0,0 +1,4 @@
|
|||
export { AppInsightsReact, type SetupConfig, withAppInsights } from './AppInsightsReact.js';
|
||||
export * from './context.js';
|
||||
export * from './use-app-insights.js';
|
||||
export * from './utils.js';
|
28
packages/app-insights/src/react/use-app-insights.ts
Normal file
28
packages/app-insights/src/react/use-app-insights.ts
Normal file
|
@ -0,0 +1,28 @@
|
|||
import { useCallback, useContext } from 'react';
|
||||
|
||||
import { type AppInsightsReact, appInsightsReact } from './AppInsightsReact.js';
|
||||
import { AppInsightsContext } from './context.js';
|
||||
|
||||
export type UseAppInsights = {
|
||||
initialized: boolean;
|
||||
setup: typeof appInsightsReact.setup;
|
||||
appInsights: AppInsightsReact;
|
||||
};
|
||||
|
||||
export const useAppInsights = () => {
|
||||
const { initialized, setInitialized } = useContext(AppInsightsContext);
|
||||
|
||||
const setup = useCallback(
|
||||
async (...args: Parameters<typeof appInsightsReact.setup>) => {
|
||||
const result = await appInsightsReact.setup(...args);
|
||||
|
||||
if (result) {
|
||||
console.debug('Initialized ApplicationInsights');
|
||||
setInitialized(true);
|
||||
}
|
||||
},
|
||||
[setInitialized]
|
||||
);
|
||||
|
||||
return { initialized, setup, appInsights: appInsightsReact };
|
||||
};
|
1
packages/app-insights/src/react/utils.ts
Normal file
1
packages/app-insights/src/react/utils.ts
Normal file
|
@ -0,0 +1 @@
|
|||
export const getPrimaryDomain = () => window.location.hostname.split('.').slice(-2).join('.');
|
|
@ -1,4 +1,7 @@
|
|||
{
|
||||
"extends": "./tsconfig",
|
||||
"compilerOptions": {
|
||||
"noEmit": false
|
||||
},
|
||||
"include": ["src"]
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
{
|
||||
"extends": "@silverhand/ts-config/tsconfig.base",
|
||||
"extends": "@silverhand/ts-config-react/tsconfig.base",
|
||||
"compilerOptions": {
|
||||
"outDir": "lib",
|
||||
"types": ["node"]
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
import { appInsightsReact } from '@logto/app-insights/react';
|
||||
import { AppInsightsProvider, getPrimaryDomain, useAppInsights } from '@logto/app-insights/react';
|
||||
import { UserScope } from '@logto/core-kit';
|
||||
import { LogtoProvider } from '@logto/react';
|
||||
import { adminConsoleApplicationId, PredefinedScope } from '@logto/schemas';
|
||||
import { conditionalArray, deduplicate } from '@silverhand/essentials';
|
||||
import { useContext } from 'react';
|
||||
import { useContext, useEffect } from 'react';
|
||||
import { Helmet } from 'react-helmet';
|
||||
|
||||
import 'overlayscrollbars/styles/overlayscrollbars.css';
|
||||
|
@ -26,17 +26,15 @@ import AppEndpointsProvider from './contexts/AppEndpointsProvider';
|
|||
import { AppThemeProvider } from './contexts/AppThemeProvider';
|
||||
import TenantsProvider, { TenantsContext } from './contexts/TenantsProvider';
|
||||
|
||||
// Use `.then()` for better compatibility, update to top-level await some day
|
||||
// eslint-disable-next-line unicorn/prefer-top-level-await
|
||||
void appInsightsReact.setup('console').then((success) => {
|
||||
if (success) {
|
||||
console.debug('Initialized ApplicationInsights');
|
||||
}
|
||||
});
|
||||
void initI18n();
|
||||
|
||||
function Content() {
|
||||
const { tenants, isSettle, currentTenantId } = useContext(TenantsContext);
|
||||
const { setup } = useAppInsights();
|
||||
|
||||
useEffect(() => {
|
||||
void setup('console', { cookieDomain: getPrimaryDomain() });
|
||||
}, [setup]);
|
||||
|
||||
const resources = deduplicate(
|
||||
conditionalArray(
|
||||
|
@ -90,9 +88,11 @@ function Content() {
|
|||
|
||||
function App() {
|
||||
return (
|
||||
<TenantsProvider>
|
||||
<Content />
|
||||
</TenantsProvider>
|
||||
<AppInsightsProvider>
|
||||
<TenantsProvider>
|
||||
<Content />
|
||||
</TenantsProvider>
|
||||
</AppInsightsProvider>
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { appInsightsReact } from '@logto/app-insights/react';
|
||||
import { useAppInsights } from '@logto/app-insights/react';
|
||||
import type { AdminConsoleKey } from '@logto/phrases';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { Helmet } from 'react-helmet';
|
||||
|
@ -14,6 +14,7 @@ type Props = {
|
|||
|
||||
function PageMeta({ titleKey, trackPageView = true }: Props) {
|
||||
const { t } = useTranslation(undefined, { keyPrefix: 'admin_console' });
|
||||
const { initialized, appInsights } = useAppInsights();
|
||||
const [pageViewTracked, setPageViewTracked] = useState(false);
|
||||
const keys = typeof titleKey === 'string' ? [titleKey] : titleKey;
|
||||
const rawTitle = keys.map((key) => t(key, { lng: 'en' })).join(' - ');
|
||||
|
@ -21,11 +22,11 @@ function PageMeta({ titleKey, trackPageView = true }: Props) {
|
|||
|
||||
useEffect(() => {
|
||||
// Only track once for the same page
|
||||
if (trackPageView && !pageViewTracked) {
|
||||
appInsightsReact.trackPageView?.({ name: [rawTitle, mainTitle].join(' - ') });
|
||||
if (initialized && trackPageView && !pageViewTracked) {
|
||||
appInsights.trackPageView?.({ name: [rawTitle, mainTitle].join(' - ') });
|
||||
setPageViewTracked(true);
|
||||
}
|
||||
}, [pageViewTracked, rawTitle, trackPageView]);
|
||||
}, [appInsights, initialized, pageViewTracked, rawTitle, trackPageView]);
|
||||
|
||||
return <Helmet title={title} />;
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { appInsightsReact } from '@logto/app-insights/react';
|
||||
import { useAppInsights } from '@logto/app-insights/react';
|
||||
import { useLogto } from '@logto/react';
|
||||
import { trySafe } from '@silverhand/essentials';
|
||||
import { useEffect } from 'react';
|
||||
|
@ -10,17 +10,19 @@ class NoIdTokenClaimsError extends Error {
|
|||
|
||||
const useTrackUserId = () => {
|
||||
const { isAuthenticated, getIdTokenClaims } = useLogto();
|
||||
const {
|
||||
initialized,
|
||||
appInsights: { instance },
|
||||
} = useAppInsights();
|
||||
|
||||
useEffect(() => {
|
||||
const setUserId = async () => {
|
||||
const { instance: appInsights } = appInsightsReact;
|
||||
|
||||
if (!appInsights) {
|
||||
if (!instance) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!isAuthenticated) {
|
||||
appInsights.clearAuthenticatedUserContext();
|
||||
instance.clearAuthenticatedUserContext();
|
||||
|
||||
return;
|
||||
}
|
||||
|
@ -28,14 +30,16 @@ const useTrackUserId = () => {
|
|||
const claims = await trySafe(getIdTokenClaims());
|
||||
|
||||
if (claims) {
|
||||
appInsights.setAuthenticatedUserContext(claims.sub, claims.sub, true);
|
||||
instance.setAuthenticatedUserContext(claims.sub, claims.sub, true);
|
||||
} else {
|
||||
appInsights.trackException({ exception: new NoIdTokenClaimsError() });
|
||||
instance.trackException({ exception: new NoIdTokenClaimsError() });
|
||||
}
|
||||
};
|
||||
|
||||
void setUserId();
|
||||
}, [getIdTokenClaims, isAuthenticated]);
|
||||
if (initialized) {
|
||||
void setUserId();
|
||||
}
|
||||
}, [getIdTokenClaims, initialized, instance, isAuthenticated]);
|
||||
};
|
||||
|
||||
export default useTrackUserId;
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { appInsightsReact } from '@logto/app-insights/react';
|
||||
import { withAppInsights } from '@logto/app-insights/react';
|
||||
import { conditional } from '@silverhand/essentials';
|
||||
import { useEffect } from 'react';
|
||||
import { Controller, useForm } from 'react-hook-form';
|
||||
|
@ -144,4 +144,4 @@ function About() {
|
|||
);
|
||||
}
|
||||
|
||||
export default appInsightsReact.withAppInsights(About);
|
||||
export default withAppInsights(About);
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { appInsightsReact } from '@logto/app-insights/react';
|
||||
import { withAppInsights } from '@logto/app-insights/react';
|
||||
import classNames from 'classnames';
|
||||
import { useContext } from 'react';
|
||||
import { Trans, useTranslation } from 'react-i18next';
|
||||
|
@ -63,4 +63,4 @@ function Congrats() {
|
|||
);
|
||||
}
|
||||
|
||||
export default appInsightsReact.withAppInsights(Congrats);
|
||||
export default withAppInsights(Congrats);
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { appInsightsReact } from '@logto/app-insights/react';
|
||||
import { withAppInsights } from '@logto/app-insights/react';
|
||||
import type { SignInExperience as SignInExperienceType } from '@logto/schemas';
|
||||
import { SignInIdentifier } from '@logto/schemas';
|
||||
import { useCallback, useEffect, useMemo } from 'react';
|
||||
|
@ -255,4 +255,4 @@ function SignInExperience() {
|
|||
);
|
||||
}
|
||||
|
||||
export default appInsightsReact.withAppInsights(SignInExperience);
|
||||
export default withAppInsights(SignInExperience);
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { appInsightsReact } from '@logto/app-insights/react';
|
||||
import { withAppInsights } from '@logto/app-insights/react';
|
||||
import classNames from 'classnames';
|
||||
import { useEffect } from 'react';
|
||||
import { Controller, useForm } from 'react-hook-form';
|
||||
|
@ -111,4 +111,4 @@ function Welcome() {
|
|||
);
|
||||
}
|
||||
|
||||
export default appInsightsReact.withAppInsights(Welcome);
|
||||
export default withAppInsights(Welcome);
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { appInsightsReact } from '@logto/app-insights/react';
|
||||
import { withAppInsights } from '@logto/app-insights/react';
|
||||
import type { Resource } from '@logto/schemas';
|
||||
import { isManagementApi, Theme } from '@logto/schemas';
|
||||
import classNames from 'classnames';
|
||||
|
@ -148,4 +148,4 @@ function ApiResourceDetails() {
|
|||
);
|
||||
}
|
||||
|
||||
export default appInsightsReact.withAppInsights(ApiResourceDetails);
|
||||
export default withAppInsights(ApiResourceDetails);
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { appInsightsReact } from '@logto/app-insights/react';
|
||||
import { withAppInsights } from '@logto/app-insights/react';
|
||||
import type { Resource } from '@logto/schemas';
|
||||
import { Theme } from '@logto/schemas';
|
||||
import { toast } from 'react-hot-toast';
|
||||
|
@ -151,4 +151,4 @@ function ApiResources() {
|
|||
);
|
||||
}
|
||||
|
||||
export default appInsightsReact.withAppInsights(ApiResources);
|
||||
export default withAppInsights(ApiResources);
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { appInsightsReact } from '@logto/app-insights/react';
|
||||
import { withAppInsights } from '@logto/app-insights/react';
|
||||
import type { Application, ApplicationResponse, SnakeCaseOidcConfig } from '@logto/schemas';
|
||||
import { ApplicationType } from '@logto/schemas';
|
||||
import { useEffect, useState } from 'react';
|
||||
|
@ -235,4 +235,4 @@ function ApplicationDetails() {
|
|||
);
|
||||
}
|
||||
|
||||
export default appInsightsReact.withAppInsights(ApplicationDetails);
|
||||
export default withAppInsights(ApplicationDetails);
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { appInsightsReact } from '@logto/app-insights/react';
|
||||
import { withAppInsights } from '@logto/app-insights/react';
|
||||
import type { Application } from '@logto/schemas';
|
||||
import { ApplicationType } from '@logto/schemas';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
@ -147,4 +147,4 @@ function Applications() {
|
|||
);
|
||||
}
|
||||
|
||||
export default appInsightsReact.withAppInsights(Applications);
|
||||
export default withAppInsights(Applications);
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { appInsightsReact } from '@logto/app-insights/react';
|
||||
import { withAppInsights } from '@logto/app-insights/react';
|
||||
import type { User, Log } from '@logto/schemas';
|
||||
import { demoAppApplicationId } from '@logto/schemas';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
@ -122,4 +122,4 @@ function AuditLogDetails() {
|
|||
);
|
||||
}
|
||||
|
||||
export default appInsightsReact.withAppInsights(AuditLogDetails);
|
||||
export default withAppInsights(AuditLogDetails);
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { appInsightsReact } from '@logto/app-insights/react';
|
||||
import { withAppInsights } from '@logto/app-insights/react';
|
||||
|
||||
import AuditLogTable from '@/components/AuditLogTable';
|
||||
import CardTitle from '@/components/CardTitle';
|
||||
|
@ -17,4 +17,4 @@ function AuditLogs() {
|
|||
);
|
||||
}
|
||||
|
||||
export default appInsightsReact.withAppInsights(AuditLogs);
|
||||
export default withAppInsights(AuditLogs);
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { appInsightsReact } from '@logto/app-insights/react';
|
||||
import { withAppInsights } from '@logto/app-insights/react';
|
||||
import { ConnectorType } from '@logto/schemas';
|
||||
import type { ConnectorFactoryResponse, ConnectorResponse } from '@logto/schemas';
|
||||
import { useEffect, useState } from 'react';
|
||||
|
@ -235,4 +235,4 @@ function ConnectorDetails() {
|
|||
);
|
||||
}
|
||||
|
||||
export default appInsightsReact.withAppInsights(ConnectorDetails);
|
||||
export default withAppInsights(ConnectorDetails);
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { appInsightsReact } from '@logto/app-insights/react';
|
||||
import { withAppInsights } from '@logto/app-insights/react';
|
||||
import type { ConnectorFactoryResponse } from '@logto/schemas';
|
||||
import { ConnectorType } from '@logto/schemas';
|
||||
import { conditional } from '@silverhand/essentials';
|
||||
|
@ -225,4 +225,4 @@ function Connectors() {
|
|||
);
|
||||
}
|
||||
|
||||
export default appInsightsReact.withAppInsights(Connectors);
|
||||
export default withAppInsights(Connectors);
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { appInsightsReact } from '@logto/app-insights/react';
|
||||
import { withAppInsights } from '@logto/app-insights/react';
|
||||
import { format } from 'date-fns';
|
||||
import type { ChangeEventHandler } from 'react';
|
||||
import { useState } from 'react';
|
||||
|
@ -153,4 +153,4 @@ function Dashboard() {
|
|||
);
|
||||
}
|
||||
|
||||
export default appInsightsReact.withAppInsights(Dashboard);
|
||||
export default withAppInsights(Dashboard);
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { appInsightsReact } from '@logto/app-insights/react';
|
||||
import { withAppInsights } from '@logto/app-insights/react';
|
||||
import { useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
|
@ -99,4 +99,4 @@ function GetStarted() {
|
|||
);
|
||||
}
|
||||
|
||||
export default appInsightsReact.withAppInsights(GetStarted);
|
||||
export default withAppInsights(GetStarted);
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { appInsightsReact } from '@logto/app-insights/react';
|
||||
import { withAppInsights } from '@logto/app-insights/react';
|
||||
import type { ConnectorResponse } from '@logto/schemas';
|
||||
import { useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
@ -99,4 +99,4 @@ function Profile() {
|
|||
);
|
||||
}
|
||||
|
||||
export default appInsightsReact.withAppInsights(Profile);
|
||||
export default withAppInsights(Profile);
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { appInsightsReact } from '@logto/app-insights/react';
|
||||
import { withAppInsights } from '@logto/app-insights/react';
|
||||
import type { Role } from '@logto/schemas';
|
||||
import classNames from 'classnames';
|
||||
import { useEffect, useState } from 'react';
|
||||
|
@ -135,4 +135,4 @@ function RoleDetails() {
|
|||
);
|
||||
}
|
||||
|
||||
export default appInsightsReact.withAppInsights(RoleDetails);
|
||||
export default withAppInsights(RoleDetails);
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { appInsightsReact } from '@logto/app-insights/react';
|
||||
import { withAppInsights } from '@logto/app-insights/react';
|
||||
import type { RoleResponse } from '@logto/schemas';
|
||||
import { conditional } from '@silverhand/essentials';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
@ -163,4 +163,4 @@ function Roles() {
|
|||
);
|
||||
}
|
||||
|
||||
export default appInsightsReact.withAppInsights(Roles);
|
||||
export default withAppInsights(Roles);
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { appInsightsReact } from '@logto/app-insights/react';
|
||||
import { withAppInsights } from '@logto/app-insights/react';
|
||||
import type { SignInExperience as SignInExperienceType } from '@logto/schemas';
|
||||
import classNames from 'classnames';
|
||||
import type { ReactNode } from 'react';
|
||||
|
@ -252,4 +252,4 @@ function SignInExperience() {
|
|||
);
|
||||
}
|
||||
|
||||
export default appInsightsReact.withAppInsights(SignInExperience);
|
||||
export default withAppInsights(SignInExperience);
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { appInsightsReact } from '@logto/app-insights/react';
|
||||
import { withAppInsights } from '@logto/app-insights/react';
|
||||
import type { User } from '@logto/schemas';
|
||||
import classNames from 'classnames';
|
||||
import { useEffect, useState } from 'react';
|
||||
|
@ -250,4 +250,4 @@ function UserDetails() {
|
|||
);
|
||||
}
|
||||
|
||||
export default appInsightsReact.withAppInsights(UserDetails);
|
||||
export default withAppInsights(UserDetails);
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { appInsightsReact } from '@logto/app-insights/react';
|
||||
import { withAppInsights } from '@logto/app-insights/react';
|
||||
import type { User } from '@logto/schemas';
|
||||
import { conditional } from '@silverhand/essentials';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
@ -179,4 +179,4 @@ function Users() {
|
|||
);
|
||||
}
|
||||
|
||||
export default appInsightsReact.withAppInsights(Users);
|
||||
export default withAppInsights(Users);
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import { appInsightsReact } from '@logto/app-insights/react';
|
||||
import { AppInsightsProvider, getPrimaryDomain, useAppInsights } from '@logto/app-insights/react';
|
||||
import { useEffect } from 'react';
|
||||
import { Route, Routes, BrowserRouter } from 'react-router-dom';
|
||||
|
||||
import AppLayout from './Layout/AppLayout';
|
||||
|
@ -26,19 +27,17 @@ import { handleSearchParametersData } from './utils/search-parameters';
|
|||
|
||||
import './scss/normalized.scss';
|
||||
|
||||
if (shouldTrack) {
|
||||
// Use `.then()` for better compatibility, update to top-level await some day
|
||||
// eslint-disable-next-line unicorn/prefer-top-level-await, promise/prefer-await-to-then
|
||||
void appInsightsReact.setup('ui').then((success) => {
|
||||
if (success) {
|
||||
console.debug('Initialized ApplicationInsights');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
handleSearchParametersData();
|
||||
|
||||
const App = () => {
|
||||
const Content = () => {
|
||||
const { setup } = useAppInsights();
|
||||
|
||||
useEffect(() => {
|
||||
if (shouldTrack) {
|
||||
void setup('ui', { cookieDomain: getPrimaryDomain() });
|
||||
}
|
||||
}, [setup]);
|
||||
|
||||
return (
|
||||
<BrowserRouter>
|
||||
<PageContextProvider>
|
||||
|
@ -99,4 +98,12 @@ const App = () => {
|
|||
);
|
||||
};
|
||||
|
||||
const App = () => {
|
||||
return (
|
||||
<AppInsightsProvider>
|
||||
<Content />
|
||||
</AppInsightsProvider>
|
||||
);
|
||||
};
|
||||
|
||||
export default App;
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { appInsightsReact } from '@logto/app-insights/react';
|
||||
import { useAppInsights } from '@logto/app-insights/react';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { Helmet } from 'react-helmet';
|
||||
import { type TFuncKey, useTranslation } from 'react-i18next';
|
||||
|
@ -13,6 +13,7 @@ type Props = {
|
|||
|
||||
const PageMeta = ({ titleKey, trackPageView = true }: Props) => {
|
||||
const { t } = useTranslation();
|
||||
const { initialized, appInsights } = useAppInsights();
|
||||
const [pageViewTracked, setPageViewTracked] = useState(false);
|
||||
const keys = typeof titleKey === 'string' ? [titleKey] : titleKey;
|
||||
const rawTitle = keys.map((key) => t(key, { lng: 'en' })).join(' - ');
|
||||
|
@ -20,11 +21,11 @@ const PageMeta = ({ titleKey, trackPageView = true }: Props) => {
|
|||
|
||||
useEffect(() => {
|
||||
// Only track once for the same page
|
||||
if (shouldTrack && trackPageView && !pageViewTracked) {
|
||||
appInsightsReact.trackPageView?.({ name: [rawTitle, 'SIE'].join(' - ') });
|
||||
if (shouldTrack && initialized && trackPageView && !pageViewTracked) {
|
||||
appInsights.trackPageView?.({ name: [rawTitle, 'SIE'].join(' - ') });
|
||||
setPageViewTracked(true);
|
||||
}
|
||||
}, [pageViewTracked, rawTitle, trackPageView]);
|
||||
}, [appInsights, initialized, pageViewTracked, rawTitle, trackPageView]);
|
||||
|
||||
return <Helmet title={title} />;
|
||||
};
|
||||
|
|
|
@ -54,9 +54,15 @@ importers:
|
|||
'@silverhand/eslint-config':
|
||||
specifier: 3.0.1
|
||||
version: 3.0.1(eslint@8.34.0)(prettier@2.8.4)(typescript@5.0.2)
|
||||
'@silverhand/eslint-config-react':
|
||||
specifier: 3.0.1
|
||||
version: 3.0.1(eslint@8.34.0)(postcss@8.4.21)(prettier@2.8.4)(stylelint@15.0.0)(typescript@5.0.2)
|
||||
'@silverhand/ts-config':
|
||||
specifier: 3.0.0
|
||||
version: 3.0.0(typescript@5.0.2)
|
||||
'@silverhand/ts-config-react':
|
||||
specifier: 3.0.0
|
||||
version: 3.0.0(typescript@5.0.2)
|
||||
'@types/node':
|
||||
specifier: ^18.11.18
|
||||
version: 18.11.18
|
||||
|
@ -8527,6 +8533,28 @@ packages:
|
|||
- typescript
|
||||
dev: true
|
||||
|
||||
/@silverhand/eslint-config-react@3.0.1(eslint@8.34.0)(postcss@8.4.21)(prettier@2.8.4)(stylelint@15.0.0)(typescript@5.0.2):
|
||||
resolution: {integrity: sha512-8roPq3t5qgi4pxYh3It2BhS91LVsp740vhNXVn56RTID0ZdW27LMrV2yp5uTT6kVzLdFYis4nIPlOVFCnv+VKA==}
|
||||
engines: {node: ^18.12.0}
|
||||
peerDependencies:
|
||||
stylelint: ^15.0.0
|
||||
dependencies:
|
||||
'@silverhand/eslint-config': 3.0.1(eslint@8.34.0)(prettier@2.8.4)(typescript@5.0.2)
|
||||
eslint-config-xo-react: 0.27.0(eslint-plugin-react-hooks@4.6.0)(eslint-plugin-react@7.31.10)(eslint@8.34.0)
|
||||
eslint-plugin-jsx-a11y: 6.6.1(eslint@8.34.0)
|
||||
eslint-plugin-react: 7.31.10(eslint@8.34.0)
|
||||
eslint-plugin-react-hooks: 4.6.0(eslint@8.34.0)
|
||||
stylelint: 15.0.0
|
||||
stylelint-config-xo-scss: 0.15.0(postcss@8.4.21)(stylelint@15.0.0)
|
||||
transitivePeerDependencies:
|
||||
- eslint
|
||||
- eslint-import-resolver-webpack
|
||||
- postcss
|
||||
- prettier
|
||||
- supports-color
|
||||
- typescript
|
||||
dev: true
|
||||
|
||||
/@silverhand/eslint-config-react@3.0.1(eslint@8.34.0)(postcss@8.4.6)(prettier@2.8.4)(stylelint@15.0.0)(typescript@5.0.2):
|
||||
resolution: {integrity: sha512-8roPq3t5qgi4pxYh3It2BhS91LVsp740vhNXVn56RTID0ZdW27LMrV2yp5uTT6kVzLdFYis4nIPlOVFCnv+VKA==}
|
||||
engines: {node: ^18.12.0}
|
||||
|
@ -16914,6 +16942,15 @@ packages:
|
|||
postcss: 8.4.14
|
||||
dev: true
|
||||
|
||||
/postcss-scss@4.0.5(postcss@8.4.21):
|
||||
resolution: {integrity: sha512-F7xpB6TrXyqUh3GKdyB4Gkp3QL3DDW1+uI+gxx/oJnUt/qXI4trj5OGlp9rOKdoABGULuqtqeG+3HEVQk4DjmA==}
|
||||
engines: {node: '>=12.0'}
|
||||
peerDependencies:
|
||||
postcss: ^8.3.3
|
||||
dependencies:
|
||||
postcss: 8.4.21
|
||||
dev: true
|
||||
|
||||
/postcss-scss@4.0.5(postcss@8.4.6):
|
||||
resolution: {integrity: sha512-F7xpB6TrXyqUh3GKdyB4Gkp3QL3DDW1+uI+gxx/oJnUt/qXI4trj5OGlp9rOKdoABGULuqtqeG+3HEVQk4DjmA==}
|
||||
engines: {node: '>=12.0'}
|
||||
|
@ -18821,6 +18858,20 @@ packages:
|
|||
- postcss
|
||||
dev: true
|
||||
|
||||
/stylelint-config-xo-scss@0.15.0(postcss@8.4.21)(stylelint@15.0.0):
|
||||
resolution: {integrity: sha512-X9WD8cDofWFWy3uaKdwwm+DjEvgI/+h7AtlaPagkhNAeOWH/GFQoeciBvNvyJ8tB1p00SoIzCn2IIOIKXCbxYA==}
|
||||
engines: {node: '>=12'}
|
||||
peerDependencies:
|
||||
stylelint: '>=14.5.1 || ^15.0.0'
|
||||
dependencies:
|
||||
postcss-scss: 4.0.5(postcss@8.4.21)
|
||||
stylelint: 15.0.0
|
||||
stylelint-config-xo: 0.21.1(stylelint@15.0.0)
|
||||
stylelint-scss: 4.3.0(stylelint@15.0.0)
|
||||
transitivePeerDependencies:
|
||||
- postcss
|
||||
dev: true
|
||||
|
||||
/stylelint-config-xo-scss@0.15.0(postcss@8.4.6)(stylelint@15.0.0):
|
||||
resolution: {integrity: sha512-X9WD8cDofWFWy3uaKdwwm+DjEvgI/+h7AtlaPagkhNAeOWH/GFQoeciBvNvyJ8tB1p00SoIzCn2IIOIKXCbxYA==}
|
||||
engines: {node: '>=12'}
|
||||
|
|
Loading…
Reference in a new issue