chore: refactoring to async
This commit is contained in:
@@ -86,7 +86,7 @@ export class GrafanaBootConfig {
|
||||
snapshotEnabled = true;
|
||||
datasources: { [str: string]: DataSourceInstanceSettings } = {};
|
||||
panels: { [key: string]: PanelPluginMeta } = {};
|
||||
/** @deprecated it will be removed in a future release, use getAppPluginMetas() function instead */
|
||||
/** @deprecated it will be removed in a future release, use getAppPluginMetas function or useAppPluginMetas hook instead */
|
||||
apps: Record<string, AppPluginConfigGrafanaData> = {};
|
||||
auth: AuthSettings = {};
|
||||
minRefreshInterval = '';
|
||||
|
||||
@@ -1,41 +0,0 @@
|
||||
import { cloneDeep } from 'lodash';
|
||||
|
||||
import { AppPluginConfig } from '@grafana/data';
|
||||
|
||||
import { config } from '../config';
|
||||
|
||||
export type AppPluginMetas = Record<string, AppPluginConfig>;
|
||||
|
||||
let apps: AppPluginMetas = {};
|
||||
|
||||
export async function initPluginMetas(): Promise<void> {
|
||||
if (config.featureToggles.useMTPlugins) {
|
||||
// add loading app configs from MT API here
|
||||
apps = {};
|
||||
return;
|
||||
}
|
||||
|
||||
// eslint-disable-next-line no-restricted-syntax
|
||||
apps = config.apps;
|
||||
}
|
||||
|
||||
export function getAppPluginMetas(): AppPluginMetas {
|
||||
return cloneDeep(apps);
|
||||
}
|
||||
|
||||
export function getAppPluginMeta(id: string): AppPluginConfig | undefined {
|
||||
if (!apps[id]) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return cloneDeep(apps[id]);
|
||||
}
|
||||
|
||||
export function setAppPluginMetas(override: AppPluginMetas) {
|
||||
// We allow overriding apps in tests
|
||||
if (override && process.env.NODE_ENV !== 'test') {
|
||||
throw new Error('setAppPluginMetas() function can only be called from tests.');
|
||||
}
|
||||
|
||||
apps = { ...override };
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
import { cloneDeep } from 'lodash';
|
||||
import { useAsync } from 'react-use';
|
||||
|
||||
import { AppPluginConfig } from '@grafana/data';
|
||||
|
||||
import { config } from '../config';
|
||||
|
||||
export type AppPluginMetas = Record<string, AppPluginConfig>;
|
||||
|
||||
let apps: AppPluginMetas = {};
|
||||
let appsPromise: Promise<void> | undefined = undefined;
|
||||
|
||||
function areAppsInitialized(): boolean {
|
||||
return Boolean(Object.keys(apps).length);
|
||||
}
|
||||
|
||||
export async function initPluginMetas(): Promise<void> {
|
||||
if (appsPromise) {
|
||||
return appsPromise;
|
||||
}
|
||||
|
||||
appsPromise = new Promise((resolve) => {
|
||||
if (config.featureToggles.useMTPlugins) {
|
||||
// add loading app configs from MT API here
|
||||
apps = {};
|
||||
resolve();
|
||||
return;
|
||||
}
|
||||
|
||||
// eslint-disable-next-line no-restricted-syntax
|
||||
apps = config.apps;
|
||||
resolve();
|
||||
return;
|
||||
});
|
||||
|
||||
return appsPromise;
|
||||
}
|
||||
|
||||
export async function getAppPluginMetas(): Promise<AppPluginConfig[]> {
|
||||
if (!areAppsInitialized()) {
|
||||
await initPluginMetas();
|
||||
}
|
||||
|
||||
return Object.values(cloneDeep(apps));
|
||||
}
|
||||
|
||||
export function getAppPluginMeta(id: string): AppPluginConfig | undefined {
|
||||
if (!apps[id]) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return cloneDeep(apps[id]);
|
||||
}
|
||||
|
||||
export async function getAppPluginConfig(id: string): Promise<AppPluginConfig | undefined> {
|
||||
if (!areAppsInitialized()) {
|
||||
await initPluginMetas();
|
||||
}
|
||||
|
||||
if (!apps[id]) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return cloneDeep(apps[id]);
|
||||
}
|
||||
|
||||
export function setAppPluginMetas(override: AppPluginMetas) {
|
||||
// We allow overriding apps in tests
|
||||
if (override && process.env.NODE_ENV !== 'test') {
|
||||
throw new Error('setAppPluginMetas() function can only be called from tests.');
|
||||
}
|
||||
|
||||
apps = { ...override };
|
||||
}
|
||||
|
||||
export interface UseAppPluginMetasResult {
|
||||
isAppPluginMetasLoading: boolean;
|
||||
error: Error | undefined;
|
||||
apps: AppPluginConfig[];
|
||||
}
|
||||
|
||||
export function useAppPluginMetas(filterByIds: string[] = []): UseAppPluginMetasResult {
|
||||
const { loading, error, value: apps = [] } = useAsync(getAppPluginMetas);
|
||||
const filtered = apps.filter((app) => filterByIds.includes(app.id));
|
||||
|
||||
return { isAppPluginMetasLoading: loading, error, apps: filtered };
|
||||
}
|
||||
@@ -12,4 +12,11 @@
|
||||
// This is a dummy export so typescript doesn't error importing an "empty module"
|
||||
export const unstable = {};
|
||||
|
||||
export { getAppPluginMetas, getAppPluginMeta, type AppPluginMetas } from './services/plugins';
|
||||
export {
|
||||
type AppPluginMetas,
|
||||
type UseAppPluginMetasResult as UseAppPluginMetasCollectionResult,
|
||||
getAppPluginMeta,
|
||||
getAppPluginConfig,
|
||||
getAppPluginMetas,
|
||||
useAppPluginMetas,
|
||||
} from './services/plugins';
|
||||
|
||||
+4
-2
@@ -51,6 +51,7 @@ import {
|
||||
setPanelRenderer,
|
||||
setPluginPage,
|
||||
} from '@grafana/runtime/internal';
|
||||
import { getAppPluginMetas } from '@grafana/runtime/unstable';
|
||||
import { loadResources as loadScenesResources, sceneUtils } from '@grafana/scenes';
|
||||
import config, { updateConfig } from 'app/core/config';
|
||||
import { getStandardTransformers } from 'app/features/transformers/standardTransformers';
|
||||
@@ -265,8 +266,9 @@ export class GrafanaApp {
|
||||
const skipAppPluginsPreload =
|
||||
config.featureToggles.rendererDisableAppPluginsPreload && contextSrv.user.authenticatedBy === 'render';
|
||||
if (contextSrv.user.orgRole !== '' && !skipAppPluginsPreload) {
|
||||
const appPluginsToAwait = getAppPluginsToAwait();
|
||||
const appPluginsToPreload = getAppPluginsToPreload();
|
||||
const apps = await getAppPluginMetas();
|
||||
const appPluginsToAwait = getAppPluginsToAwait(apps);
|
||||
const appPluginsToPreload = getAppPluginsToPreload(apps);
|
||||
|
||||
preloadPlugins(appPluginsToPreload);
|
||||
await preloadPlugins(appPluginsToAwait);
|
||||
|
||||
@@ -3,6 +3,7 @@ import { useLocalStorage } from 'react-use';
|
||||
|
||||
import { PluginExtensionPoints, store } from '@grafana/data';
|
||||
import { getAppEvents, reportInteraction, usePluginLinks, locationService } from '@grafana/runtime';
|
||||
import { useAppPluginMetas } from '@grafana/runtime/unstable';
|
||||
import { ExtensionPointPluginMeta, getExtensionPointPluginMeta } from 'app/features/plugins/extensions/utils';
|
||||
import { CloseExtensionSidebarEvent, OpenExtensionSidebarEvent, ToggleExtensionSidebarEvent } from 'app/types/events';
|
||||
|
||||
@@ -90,19 +91,21 @@ export const ExtensionSidebarContextProvider = ({ children }: ExtensionSidebarCo
|
||||
// that means, a plugin would need to register both, a link and a component to
|
||||
// `grafana/extension-sidebar/v0-alpha` and the link's `configure` method would control
|
||||
// whether the component is rendered or not
|
||||
const { links, isLoading } = usePluginLinks({
|
||||
const { links, isLoading: isPluginLinksLoading } = usePluginLinks({
|
||||
extensionPointId: PluginExtensionPoints.ExtensionSidebar,
|
||||
context: {
|
||||
path: currentPath,
|
||||
},
|
||||
});
|
||||
|
||||
const { apps, isAppPluginMetasLoading: isAppPluginConfigsLoading } = useAppPluginMetas();
|
||||
const isLoading = isPluginLinksLoading || isAppPluginConfigsLoading;
|
||||
// get all components for this extension point, but only for the permitted plugins
|
||||
// if the extension sidebar is not enabled, we will return an empty map
|
||||
const availableComponents = useMemo(
|
||||
() =>
|
||||
new Map(
|
||||
Array.from(getExtensionPointPluginMeta(PluginExtensionPoints.ExtensionSidebar).entries()).filter(
|
||||
Array.from(getExtensionPointPluginMeta(apps, PluginExtensionPoints.ExtensionSidebar).entries()).filter(
|
||||
([pluginId, pluginMeta]) =>
|
||||
PERMITTED_EXTENSION_SIDEBAR_PLUGINS.includes(pluginId) &&
|
||||
links.some(
|
||||
@@ -112,7 +115,7 @@ export const ExtensionSidebarContextProvider = ({ children }: ExtensionSidebarCo
|
||||
)
|
||||
)
|
||||
),
|
||||
[links]
|
||||
[links, apps]
|
||||
);
|
||||
|
||||
// check if the stored docked component is still available
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { getIrmIfPresentOrIncidentPluginId } from '../utils/config';
|
||||
import { SupportedPlugin } from '../types/pluginBridges';
|
||||
|
||||
import { alertingApi } from './alertingApi';
|
||||
|
||||
@@ -7,17 +7,18 @@ interface IncidentsPluginConfigDto {
|
||||
isIncidentCreated: boolean;
|
||||
}
|
||||
|
||||
const getProxyApiUrl = (path: string) => `/api/plugins/${getIrmIfPresentOrIncidentPluginId()}/resources${path}`;
|
||||
const getProxyApiUrl = (path: string, pluginId: SupportedPlugin) => `/api/plugins/${pluginId}/resources${path}`;
|
||||
|
||||
export const incidentsApi = alertingApi.injectEndpoints({
|
||||
endpoints: (build) => ({
|
||||
getIncidentsPluginConfig: build.query<IncidentsPluginConfigDto, void>({
|
||||
query: () => ({
|
||||
url: getProxyApiUrl('/api/ConfigurationTrackerService.GetConfigurationTracker'),
|
||||
data: {},
|
||||
method: 'POST',
|
||||
showErrorAlert: false,
|
||||
export const incidentsApi = (pluginId: SupportedPlugin) =>
|
||||
alertingApi.injectEndpoints({
|
||||
endpoints: (build) => ({
|
||||
getIncidentsPluginConfig: build.query<IncidentsPluginConfigDto, void>({
|
||||
query: () => ({
|
||||
url: getProxyApiUrl('/api/ConfigurationTrackerService.GetConfigurationTracker', pluginId),
|
||||
data: {},
|
||||
method: 'POST',
|
||||
showErrorAlert: false,
|
||||
}),
|
||||
}),
|
||||
}),
|
||||
}),
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { FetchError, isFetchError } from '@grafana/runtime';
|
||||
|
||||
import { GRAFANA_ONCALL_INTEGRATION_TYPE } from '../components/receivers/grafanaAppReceivers/onCall/onCall';
|
||||
import { getIrmIfPresentOrOnCallPluginId } from '../utils/config';
|
||||
import { SupportedPlugin } from '../types/pluginBridges';
|
||||
|
||||
import { alertingApi } from './alertingApi';
|
||||
|
||||
@@ -38,62 +38,63 @@ export interface OnCallConfigChecks {
|
||||
is_integration_chatops_connected: boolean;
|
||||
}
|
||||
|
||||
export function getProxyApiUrl(path: string) {
|
||||
return `/api/plugins/${getIrmIfPresentOrOnCallPluginId()}/resources${path}`;
|
||||
export function getProxyApiUrl(path: string, pluginId: SupportedPlugin) {
|
||||
return `/api/plugins/${pluginId}/resources${path}`;
|
||||
}
|
||||
|
||||
export const onCallApi = alertingApi.injectEndpoints({
|
||||
endpoints: (build) => ({
|
||||
grafanaOnCallIntegrations: build.query<OnCallIntegrationDTO[], void>({
|
||||
query: () => ({
|
||||
url: getProxyApiUrl('/alert_receive_channels/'),
|
||||
// legacy_grafana_alerting is necessary for OnCall.
|
||||
// We do NOT need to differentiate between these two on our side
|
||||
params: {
|
||||
filters: true,
|
||||
integration: [GRAFANA_ONCALL_INTEGRATION_TYPE, 'legacy_grafana_alerting'],
|
||||
skip_pagination: true,
|
||||
export const onCallApi = (pluginId: SupportedPlugin) =>
|
||||
alertingApi.injectEndpoints({
|
||||
endpoints: (build) => ({
|
||||
grafanaOnCallIntegrations: build.query<OnCallIntegrationDTO[], void>({
|
||||
query: () => ({
|
||||
url: getProxyApiUrl('/alert_receive_channels/', pluginId),
|
||||
// legacy_grafana_alerting is necessary for OnCall.
|
||||
// We do NOT need to differentiate between these two on our side
|
||||
params: {
|
||||
filters: true,
|
||||
integration: [GRAFANA_ONCALL_INTEGRATION_TYPE, 'legacy_grafana_alerting'],
|
||||
skip_pagination: true,
|
||||
},
|
||||
showErrorAlert: false,
|
||||
}),
|
||||
transformResponse: (response: AlertReceiveChannelsResult) => {
|
||||
if (isPaginatedResponse(response)) {
|
||||
return response.results;
|
||||
}
|
||||
return response;
|
||||
},
|
||||
showErrorAlert: false,
|
||||
providesTags: ['OnCallIntegrations'],
|
||||
}),
|
||||
transformResponse: (response: AlertReceiveChannelsResult) => {
|
||||
if (isPaginatedResponse(response)) {
|
||||
return response.results;
|
||||
}
|
||||
return response;
|
||||
},
|
||||
providesTags: ['OnCallIntegrations'],
|
||||
}),
|
||||
validateIntegrationName: build.query<boolean, string>({
|
||||
query: (name) => ({
|
||||
url: getProxyApiUrl('/alert_receive_channels/validate_name/'),
|
||||
params: { verbal_name: name },
|
||||
showErrorAlert: false,
|
||||
validateIntegrationName: build.query<boolean, string>({
|
||||
query: (name) => ({
|
||||
url: getProxyApiUrl('/alert_receive_channels/validate_name/', pluginId),
|
||||
params: { verbal_name: name },
|
||||
showErrorAlert: false,
|
||||
}),
|
||||
}),
|
||||
createIntegration: build.mutation<NewOnCallIntegrationDTO, CreateIntegrationDTO>({
|
||||
query: (integration) => ({
|
||||
url: getProxyApiUrl('/alert_receive_channels/', pluginId),
|
||||
data: integration,
|
||||
method: 'POST',
|
||||
showErrorAlert: true,
|
||||
}),
|
||||
invalidatesTags: ['OnCallIntegrations'],
|
||||
}),
|
||||
features: build.query<OnCallFeature[], void>({
|
||||
query: () => ({
|
||||
url: getProxyApiUrl('/features/', pluginId),
|
||||
showErrorAlert: false,
|
||||
}),
|
||||
}),
|
||||
onCallConfigChecks: build.query<OnCallConfigChecks, void>({
|
||||
query: () => ({
|
||||
url: getProxyApiUrl('/organization/config-checks/', pluginId),
|
||||
showErrorAlert: false,
|
||||
}),
|
||||
}),
|
||||
}),
|
||||
createIntegration: build.mutation<NewOnCallIntegrationDTO, CreateIntegrationDTO>({
|
||||
query: (integration) => ({
|
||||
url: getProxyApiUrl('/alert_receive_channels/'),
|
||||
data: integration,
|
||||
method: 'POST',
|
||||
showErrorAlert: true,
|
||||
}),
|
||||
invalidatesTags: ['OnCallIntegrations'],
|
||||
}),
|
||||
features: build.query<OnCallFeature[], void>({
|
||||
query: () => ({
|
||||
url: getProxyApiUrl('/features/'),
|
||||
showErrorAlert: false,
|
||||
}),
|
||||
}),
|
||||
onCallConfigChecks: build.query<OnCallConfigChecks, void>({
|
||||
query: () => ({
|
||||
url: getProxyApiUrl('/organization/config-checks/'),
|
||||
showErrorAlert: false,
|
||||
}),
|
||||
}),
|
||||
}),
|
||||
});
|
||||
});
|
||||
|
||||
function isPaginatedResponse(
|
||||
response: AlertReceiveChannelsResult
|
||||
@@ -101,8 +102,6 @@ function isPaginatedResponse(
|
||||
return 'results' in response && Array.isArray(response.results);
|
||||
}
|
||||
|
||||
export const { useGrafanaOnCallIntegrationsQuery } = onCallApi;
|
||||
|
||||
export function isOnCallFetchError(error: unknown): error is FetchError<{ detail: string }> {
|
||||
return isFetchError(error) && 'detail' in error.data;
|
||||
}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { Trans, t } from '@grafana/i18n';
|
||||
import { Button, LinkButton, Menu, Tooltip } from '@grafana/ui';
|
||||
import { useIrmConfig } from 'app/features/gops/configuration-tracker/irmHooks';
|
||||
|
||||
import { usePluginBridge } from '../../hooks/usePluginBridge';
|
||||
import { getIrmIfPresentOrIncidentPluginId } from '../../utils/config';
|
||||
import { createBridgeURL } from '../PluginBridge';
|
||||
|
||||
interface Props {
|
||||
@@ -11,16 +11,19 @@ interface Props {
|
||||
url?: string;
|
||||
}
|
||||
|
||||
const pluginId = getIrmIfPresentOrIncidentPluginId();
|
||||
|
||||
export const DeclareIncidentButton = ({ title = '', severity = '', url = '' }: Props) => {
|
||||
const bridgeURL = createBridgeURL(pluginId, '/incidents/declare', {
|
||||
const {
|
||||
irmConfig: { incidentPluginId },
|
||||
isIrmConfigLoading,
|
||||
} = useIrmConfig();
|
||||
const bridgeURL = createBridgeURL(incidentPluginId, '/incidents/declare', {
|
||||
title,
|
||||
severity,
|
||||
url,
|
||||
});
|
||||
|
||||
const { loading, installed, settings } = usePluginBridge(pluginId);
|
||||
const { loading: isPluginBridgeLoading, installed, settings } = usePluginBridge(incidentPluginId);
|
||||
const loading = isIrmConfigLoading || isPluginBridgeLoading;
|
||||
|
||||
return (
|
||||
<>
|
||||
@@ -51,13 +54,18 @@ export const DeclareIncidentButton = ({ title = '', severity = '', url = '' }: P
|
||||
};
|
||||
|
||||
export const DeclareIncidentMenuItem = ({ title = '', severity = '', url = '' }: Props) => {
|
||||
const bridgeURL = createBridgeURL(pluginId, '/incidents/declare', {
|
||||
const {
|
||||
irmConfig: { incidentPluginId },
|
||||
isIrmConfigLoading,
|
||||
} = useIrmConfig();
|
||||
const bridgeURL = createBridgeURL(incidentPluginId, '/incidents/declare', {
|
||||
title,
|
||||
severity,
|
||||
url,
|
||||
});
|
||||
|
||||
const { loading, installed, settings } = usePluginBridge(pluginId);
|
||||
const { loading: isPluginBridgeLoading, installed, settings } = usePluginBridge(incidentPluginId);
|
||||
const loading = isIrmConfigLoading || isPluginBridgeLoading;
|
||||
|
||||
return (
|
||||
<>
|
||||
|
||||
@@ -12,6 +12,7 @@ import { BaseAlertmanagerArgs, Skippable } from 'app/features/alerting/unified/t
|
||||
import { cloudNotifierTypes } from 'app/features/alerting/unified/utils/cloud-alertmanager-notifier-types';
|
||||
import { GRAFANA_RULES_SOURCE_NAME } from 'app/features/alerting/unified/utils/datasource';
|
||||
import { isK8sEntityProvisioned, shouldUseK8sApi } from 'app/features/alerting/unified/utils/k8s/utils';
|
||||
import { useIrmConfig } from 'app/features/gops/configuration-tracker/irmHooks';
|
||||
import { GrafanaManagedContactPoint, Receiver } from 'app/plugins/datasource/alertmanager/types';
|
||||
|
||||
import { getAPINamespace } from '../../../../../api/utils';
|
||||
@@ -21,7 +22,7 @@ import { useAsync } from '../../hooks/useAsync';
|
||||
import { usePluginBridge } from '../../hooks/usePluginBridge';
|
||||
import { useProduceNewAlertmanagerConfiguration } from '../../hooks/useProduceNewAlertmanagerConfig';
|
||||
import { addReceiverAction, deleteReceiverAction, updateReceiverAction } from '../../reducers/alertmanager/receivers';
|
||||
import { getIrmIfPresentOrOnCallPluginId } from '../../utils/config';
|
||||
import { SupportedPlugin } from '../../types/pluginBridges';
|
||||
|
||||
import { enhanceContactPointsWithMetadata } from './utils';
|
||||
|
||||
@@ -41,7 +42,7 @@ const {
|
||||
useGrafanaNotifiersQuery,
|
||||
useLazyGetAlertmanagerConfigurationQuery,
|
||||
} = alertmanagerApi;
|
||||
const { useGrafanaOnCallIntegrationsQuery } = onCallApi;
|
||||
|
||||
const {
|
||||
useListNamespacedReceiverQuery,
|
||||
useReadNamespacedReceiverQuery,
|
||||
@@ -61,8 +62,14 @@ const defaultOptions = {
|
||||
* Otherwise, returns no data
|
||||
*/
|
||||
const useOnCallIntegrations = ({ skip }: Skippable = {}) => {
|
||||
const { installed, loading } = usePluginBridge(getIrmIfPresentOrOnCallPluginId());
|
||||
const {
|
||||
irmConfig: { onCallPluginId },
|
||||
isIrmConfigLoading,
|
||||
} = useIrmConfig();
|
||||
const { installed, loading: isPluginBridgeLoading } = usePluginBridge(onCallPluginId);
|
||||
const { useGrafanaOnCallIntegrationsQuery } = onCallApi(onCallPluginId);
|
||||
const oncallIntegrationsResponse = useGrafanaOnCallIntegrationsQuery(undefined, { skip: skip || !installed });
|
||||
const loading = isIrmConfigLoading || isPluginBridgeLoading;
|
||||
|
||||
return useMemo(() => {
|
||||
if (installed) {
|
||||
@@ -138,9 +145,11 @@ export const useGrafanaContactPoints = ({
|
||||
const alertmanagerConfigResponse = useGetAlertmanagerConfigurationQuery(GRAFANA_RULES_SOURCE_NAME, {
|
||||
skip: skip || !fetchPolicies,
|
||||
});
|
||||
const { irmConfig, isIrmConfigLoading } = useIrmConfig();
|
||||
|
||||
return useMemo(() => {
|
||||
const isLoading = onCallResponse.isLoading || alertNotifiers.isLoading || contactPointsListResponse.isLoading;
|
||||
const isLoading =
|
||||
onCallResponse.isLoading || alertNotifiers.isLoading || contactPointsListResponse.isLoading || isIrmConfigLoading;
|
||||
|
||||
if (isLoading) {
|
||||
return {
|
||||
@@ -160,6 +169,7 @@ export const useGrafanaContactPoints = ({
|
||||
onCallIntegrations: onCallResponse?.data,
|
||||
contactPoints: contactPointsListResponse.data || [],
|
||||
alertmanagerConfiguration: alertmanagerConfigResponse.data,
|
||||
irmConfig,
|
||||
});
|
||||
|
||||
return {
|
||||
@@ -172,6 +182,8 @@ export const useGrafanaContactPoints = ({
|
||||
contactPointsListResponse,
|
||||
contactPointsStatusResponse,
|
||||
onCallResponse,
|
||||
isIrmConfigLoading,
|
||||
irmConfig,
|
||||
]);
|
||||
};
|
||||
|
||||
@@ -238,9 +250,10 @@ export function useContactPointsWithStatus({
|
||||
fetchPolicies,
|
||||
skip,
|
||||
}: GrafanaFetchOptions & BaseAlertmanagerArgs & Skippable) {
|
||||
const { irmConfig, isIrmConfigLoading } = useIrmConfig();
|
||||
const isGrafanaAlertmanager = alertmanager === GRAFANA_RULES_SOURCE_NAME;
|
||||
const grafanaResponse = useGrafanaContactPoints({
|
||||
skip: skip || !isGrafanaAlertmanager,
|
||||
skip: skip || !isGrafanaAlertmanager || isIrmConfigLoading,
|
||||
fetchStatuses,
|
||||
fetchPolicies,
|
||||
});
|
||||
@@ -254,6 +267,7 @@ export function useContactPointsWithStatus({
|
||||
notifiers: cloudNotifierTypes,
|
||||
contactPoints: result.data.alertmanager_config.receivers ?? [],
|
||||
alertmanagerConfiguration: result.data,
|
||||
irmConfig,
|
||||
})
|
||||
: [],
|
||||
}),
|
||||
|
||||
@@ -5,6 +5,7 @@ import { computeInheritedTree } from '@grafana/alerting';
|
||||
import { t } from '@grafana/i18n';
|
||||
import { NotifierDTO, NotifierStatus, ReceiversStateDTO } from 'app/features/alerting/unified/types/alerting';
|
||||
import { canAdminEntity, shouldUseK8sApi } from 'app/features/alerting/unified/utils/k8s/utils';
|
||||
import { UseIsIrmConfig } from 'app/features/gops/configuration-tracker/irmHooks';
|
||||
import {
|
||||
AlertManagerCortexConfig,
|
||||
GrafanaManagedContactPoint,
|
||||
@@ -116,6 +117,7 @@ type EnhanceContactPointsArgs = {
|
||||
onCallIntegrations?: OnCallIntegrationDTO[] | undefined | null;
|
||||
contactPoints: Receiver[];
|
||||
alertmanagerConfiguration?: AlertManagerCortexConfig;
|
||||
irmConfig: UseIsIrmConfig;
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -132,6 +134,7 @@ export function enhanceContactPointsWithMetadata({
|
||||
onCallIntegrations,
|
||||
contactPoints,
|
||||
alertmanagerConfiguration,
|
||||
irmConfig,
|
||||
}: EnhanceContactPointsArgs): ContactPointWithMetadata[] {
|
||||
// compute the entire inherited tree before finding what notification policies are using a particular contact point
|
||||
const fullyInheritedTree = computeInheritedTree(
|
||||
@@ -162,7 +165,7 @@ export function enhanceContactPointsWithMetadata({
|
||||
[RECEIVER_META_KEY]: getNotifierMetadata(notifiers, receiver),
|
||||
// if OnCall plugin is installed, we'll add it to the receiver's plugin metadata
|
||||
[RECEIVER_PLUGIN_META_KEY]: isOnCallReceiver
|
||||
? getOnCallMetadata(onCallIntegrations, receiver, Boolean(alertmanagerConfiguration))
|
||||
? getOnCallMetadata(onCallIntegrations, receiver, Boolean(alertmanagerConfiguration), irmConfig)
|
||||
: undefined,
|
||||
};
|
||||
}),
|
||||
|
||||
+14
-7
@@ -6,7 +6,7 @@ import { t } from '@grafana/i18n';
|
||||
import { isFetchError } from '@grafana/runtime';
|
||||
import { Badge } from '@grafana/ui';
|
||||
import { NotifierDTO } from 'app/features/alerting/unified/types/alerting';
|
||||
import { getIrmIfPresentOrOnCallPluginId } from 'app/features/alerting/unified/utils/config';
|
||||
import { useIrmConfig } from 'app/features/gops/configuration-tracker/irmHooks';
|
||||
|
||||
import { useAppNotification } from '../../../../../../../core/copy/appNotification';
|
||||
import { Receiver } from '../../../../../../../plugins/datasource/alertmanager/types';
|
||||
@@ -38,17 +38,21 @@ enum OnCallIntegrationStatus {
|
||||
}
|
||||
|
||||
function useOnCallPluginStatus() {
|
||||
const {
|
||||
irmConfig: { onCallPluginId },
|
||||
isIrmConfigLoading,
|
||||
} = useIrmConfig();
|
||||
const {
|
||||
installed: isOnCallEnabled,
|
||||
loading: isPluginBridgeLoading,
|
||||
error: pluginError,
|
||||
} = usePluginBridge(getIrmIfPresentOrOnCallPluginId());
|
||||
} = usePluginBridge(onCallPluginId);
|
||||
|
||||
const {
|
||||
data: onCallFeatures = [],
|
||||
error: onCallFeaturesError,
|
||||
isLoading: isOnCallFeaturesLoading,
|
||||
} = onCallApi.endpoints.features.useQuery(undefined, { skip: !isOnCallEnabled });
|
||||
} = onCallApi(onCallPluginId).endpoints.features.useQuery(undefined, { skip: !isOnCallEnabled });
|
||||
|
||||
const integrationStatus = useMemo((): OnCallIntegrationStatus => {
|
||||
if (!isOnCallEnabled) {
|
||||
@@ -70,19 +74,22 @@ function useOnCallPluginStatus() {
|
||||
isOnCallEnabled,
|
||||
integrationStatus,
|
||||
isAlertingV2IntegrationEnabled,
|
||||
isOnCallStatusLoading: isPluginBridgeLoading || isOnCallFeaturesLoading,
|
||||
isOnCallStatusLoading: isPluginBridgeLoading || isOnCallFeaturesLoading || isIrmConfigLoading,
|
||||
onCallError: pluginError ?? onCallFeaturesError,
|
||||
};
|
||||
}
|
||||
|
||||
export function useOnCallIntegration() {
|
||||
const notifyApp = useAppNotification();
|
||||
|
||||
const {
|
||||
irmConfig: { onCallPluginId },
|
||||
isIrmConfigLoading,
|
||||
} = useIrmConfig();
|
||||
const { isOnCallEnabled, integrationStatus, isAlertingV2IntegrationEnabled, isOnCallStatusLoading, onCallError } =
|
||||
useOnCallPluginStatus();
|
||||
|
||||
const { useCreateIntegrationMutation, useGrafanaOnCallIntegrationsQuery, useLazyValidateIntegrationNameQuery } =
|
||||
onCallApi;
|
||||
onCallApi(onCallPluginId);
|
||||
|
||||
const [validateIntegrationNameQuery, { isFetching: isValidating }] = useLazyValidateIntegrationNameQuery();
|
||||
const [createIntegrationMutation] = useCreateIntegrationMutation();
|
||||
@@ -271,7 +278,7 @@ export function useOnCallIntegration() {
|
||||
extendOnCallReceivers,
|
||||
createOnCallIntegrations,
|
||||
onCallFormValidators,
|
||||
isLoadingOnCallIntegration: isLoadingOnCallIntegrations || isOnCallStatusLoading,
|
||||
isLoadingOnCallIntegration: isLoadingOnCallIntegrations || isOnCallStatusLoading || isIrmConfigLoading,
|
||||
isValidating,
|
||||
hasOnCallError: Boolean(onCallError) || isIntegrationsQueryError,
|
||||
};
|
||||
|
||||
+18
-16
@@ -1,6 +1,9 @@
|
||||
import { t } from '@grafana/i18n';
|
||||
import { UseIsIrmConfig } from 'app/features/gops/configuration-tracker/irmHooks';
|
||||
|
||||
import { GrafanaManagedReceiverConfig } from '../../../../../../plugins/datasource/alertmanager/types';
|
||||
import { OnCallIntegrationDTO } from '../../../api/onCallApi';
|
||||
import { getIrmIfPresentOrOnCallPluginId, getIsIrmPluginPresent } from '../../../utils/config';
|
||||
import { SupportedPlugin } from '../../../types/pluginBridges';
|
||||
import { createBridgeURL } from '../../PluginBridge';
|
||||
|
||||
import { GRAFANA_APP_RECEIVERS_SOURCE_IMAGE } from './types';
|
||||
@@ -13,38 +16,37 @@ export interface ReceiverPluginMetadata {
|
||||
warning?: string;
|
||||
}
|
||||
|
||||
const onCallReceiverICon = GRAFANA_APP_RECEIVERS_SOURCE_IMAGE[getIrmIfPresentOrOnCallPluginId()];
|
||||
const onCallReceiverTitle = 'Grafana OnCall';
|
||||
|
||||
export const onCallReceiverMeta: ReceiverPluginMetadata = {
|
||||
title: onCallReceiverTitle,
|
||||
icon: onCallReceiverICon,
|
||||
};
|
||||
export const onCallReceiverMeta = (pluginId: SupportedPlugin): ReceiverPluginMetadata => ({
|
||||
title: t('alerting.on-call-receiver-meta.title.grafana-on-call', 'Grafana OnCall'),
|
||||
icon: GRAFANA_APP_RECEIVERS_SOURCE_IMAGE[pluginId],
|
||||
});
|
||||
|
||||
export function getOnCallMetadata(
|
||||
onCallIntegrations: OnCallIntegrationDTO[] | undefined | null,
|
||||
receiver: GrafanaManagedReceiverConfig,
|
||||
hasAlertManagerConfigData = true
|
||||
hasAlertManagerConfigData = true,
|
||||
irmConfig: UseIsIrmConfig
|
||||
): ReceiverPluginMetadata {
|
||||
const pluginName = getIsIrmPluginPresent() ? 'IRM' : 'OnCall';
|
||||
const pluginName = irmConfig.isIrmPluginPresent ? 'IRM' : 'OnCall';
|
||||
const pluginId = irmConfig.onCallPluginId;
|
||||
|
||||
if (!hasAlertManagerConfigData) {
|
||||
return onCallReceiverMeta;
|
||||
return onCallReceiverMeta(pluginId);
|
||||
}
|
||||
|
||||
if (!receiver.settings?.url) {
|
||||
return onCallReceiverMeta;
|
||||
return onCallReceiverMeta(pluginId);
|
||||
}
|
||||
|
||||
// oncall status is still loading
|
||||
if (onCallIntegrations === undefined) {
|
||||
return onCallReceiverMeta;
|
||||
return onCallReceiverMeta(pluginId);
|
||||
}
|
||||
|
||||
// indication that onCall is not enabled
|
||||
if (onCallIntegrations == null) {
|
||||
return {
|
||||
...onCallReceiverMeta,
|
||||
...onCallReceiverMeta(pluginId),
|
||||
warning: `Grafana ${pluginName} is not installed or is disabled`,
|
||||
};
|
||||
}
|
||||
@@ -54,10 +56,10 @@ export function getOnCallMetadata(
|
||||
);
|
||||
|
||||
return {
|
||||
...onCallReceiverMeta,
|
||||
...onCallReceiverMeta(pluginId),
|
||||
description: matchingOnCallIntegration?.display_name,
|
||||
externalUrl: matchingOnCallIntegration
|
||||
? createBridgeURL(getIrmIfPresentOrOnCallPluginId(), `/integrations/${matchingOnCallIntegration.value}`)
|
||||
? createBridgeURL(pluginId, `/integrations/${matchingOnCallIntegration.value}`)
|
||||
: undefined,
|
||||
warning: matchingOnCallIntegration ? undefined : `${pluginName} Integration no longer exists`,
|
||||
};
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
import { type DefaultBodyType, HttpResponse, HttpResponseResolver, PathParams, http } from 'msw';
|
||||
|
||||
import { setAppPluginMetas } from '@grafana/runtime/internal';
|
||||
import { getAppPluginMetas } from '@grafana/runtime/unstable';
|
||||
import server from '@grafana/test-utils/server';
|
||||
import { mockDataSource, mockFolder } from 'app/features/alerting/unified/mocks';
|
||||
import {
|
||||
@@ -11,10 +9,7 @@ import {
|
||||
} from 'app/features/alerting/unified/mocks/server/handlers/alertmanagers';
|
||||
import { getFolderHandler } from 'app/features/alerting/unified/mocks/server/handlers/folders';
|
||||
import { listNamespacedTimeIntervalHandler } from 'app/features/alerting/unified/mocks/server/handlers/k8s/timeIntervals.k8s';
|
||||
import {
|
||||
getDisabledPluginHandler,
|
||||
getPluginMissingHandler,
|
||||
} from 'app/features/alerting/unified/mocks/server/handlers/plugins';
|
||||
import { getDisabledPluginHandler } from 'app/features/alerting/unified/mocks/server/handlers/plugins';
|
||||
import {
|
||||
ALERTING_API_SERVER_BASE_URL,
|
||||
getK8sResponse,
|
||||
@@ -213,14 +208,6 @@ export function setGrafanaPromRules(groups: GrafanaPromRuleGroupDTO[]) {
|
||||
server.use(http.get(`/api/prometheus/grafana/api/v1/rules`, paginatedHandlerFor(groups)));
|
||||
}
|
||||
|
||||
/** Make a given plugin ID respond with a 404, as if it isn't installed at all */
|
||||
export const removePlugin = (pluginId: string) => {
|
||||
const apps = getAppPluginMetas();
|
||||
delete apps[pluginId];
|
||||
setAppPluginMetas(apps);
|
||||
server.use(getPluginMissingHandler(pluginId));
|
||||
};
|
||||
|
||||
/** Make a plugin respond with `enabled: false`, as if its installed but disabled */
|
||||
export const disablePlugin = (pluginId: SupportedPlugin) => {
|
||||
clearPluginSettingsCache(pluginId);
|
||||
|
||||
@@ -2,7 +2,7 @@ import { HttpResponse, http } from 'msw';
|
||||
|
||||
import { PluginLoadingStrategy, PluginMeta } from '@grafana/data';
|
||||
import { setAppPluginMetas } from '@grafana/runtime/internal';
|
||||
import { getAppPluginMetas } from '@grafana/runtime/unstable';
|
||||
import { type AppPluginMetas } from '@grafana/runtime/unstable';
|
||||
import { plugins } from 'app/features/alerting/unified/testSetup/plugins';
|
||||
|
||||
const PLUGIN_NOT_FOUND_RESPONSE = { message: 'Plugin not found, no installed plugin with that id' };
|
||||
@@ -12,34 +12,34 @@ const PLUGIN_NOT_FOUND_RESPONSE = { message: 'Plugin not found, no installed plu
|
||||
* config side effects that are expected to come along with this API behaviour
|
||||
*/
|
||||
export const getPluginsHandler = (pluginsArray: PluginMeta[] = plugins) => {
|
||||
plugins.forEach(({ id, baseUrl, info, angular }) => {
|
||||
const apps = getAppPluginMetas();
|
||||
setAppPluginMetas({
|
||||
...apps,
|
||||
[id]: {
|
||||
id,
|
||||
path: baseUrl,
|
||||
preload: true,
|
||||
version: info.version,
|
||||
angular: angular ?? { detected: false, hideDeprecation: false },
|
||||
loadingStrategy: PluginLoadingStrategy.script,
|
||||
const allPlugins: AppPluginMetas = {};
|
||||
plugins.reduce((acc, curr) => {
|
||||
const { id, baseUrl, info, angular } = curr;
|
||||
acc[id] = {
|
||||
id,
|
||||
path: baseUrl,
|
||||
preload: true,
|
||||
version: info.version,
|
||||
angular: angular ?? { detected: false, hideDeprecation: false },
|
||||
loadingStrategy: PluginLoadingStrategy.script,
|
||||
extensions: {
|
||||
addedLinks: [],
|
||||
addedComponents: [],
|
||||
extensionPoints: [],
|
||||
exposedComponents: [],
|
||||
addedFunctions: [],
|
||||
},
|
||||
dependencies: {
|
||||
grafanaVersion: '',
|
||||
plugins: [],
|
||||
extensions: {
|
||||
addedLinks: [],
|
||||
addedComponents: [],
|
||||
extensionPoints: [],
|
||||
exposedComponents: [],
|
||||
addedFunctions: [],
|
||||
},
|
||||
dependencies: {
|
||||
grafanaVersion: '',
|
||||
plugins: [],
|
||||
extensions: {
|
||||
exposedComponents: [],
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
};
|
||||
return acc;
|
||||
}, allPlugins);
|
||||
setAppPluginMetas(allPlugins);
|
||||
|
||||
return http.get<{ pluginId: string }>(`/api/plugins/:pluginId/settings`, ({ params: { pluginId } }) => {
|
||||
const matchingPlugin = pluginsArray.find((plugin) => plugin.id === pluginId);
|
||||
|
||||
@@ -1,15 +1,6 @@
|
||||
import { config } from '@grafana/runtime';
|
||||
import { setAppPluginMetas } from '@grafana/runtime/internal';
|
||||
|
||||
import { pluginMeta, pluginMetaToPluginConfig } from '../testSetup/plugins';
|
||||
import { SupportedPlugin } from '../types/pluginBridges';
|
||||
|
||||
import {
|
||||
checkEvaluationIntervalGlobalLimit,
|
||||
getIrmIfPresentOrIncidentPluginId,
|
||||
getIrmIfPresentOrOnCallPluginId,
|
||||
getIsIrmPluginPresent,
|
||||
} from './config';
|
||||
import { checkEvaluationIntervalGlobalLimit } from './config';
|
||||
|
||||
describe('checkEvaluationIntervalGlobalLimit', () => {
|
||||
it('should NOT exceed limit if evaluate every is not valid duration', () => {
|
||||
@@ -60,48 +51,3 @@ describe('checkEvaluationIntervalGlobalLimit', () => {
|
||||
expect(exceedsLimit).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getIsIrmPluginPresent', () => {
|
||||
it('should return true when IRM plugin is present in config.apps', () => {
|
||||
setAppPluginMetas({ [SupportedPlugin.Irm]: pluginMetaToPluginConfig(pluginMeta[SupportedPlugin.Irm]) });
|
||||
expect(getIsIrmPluginPresent()).toBe(true);
|
||||
});
|
||||
|
||||
it('should return false when IRM plugin is not present in config.apps', () => {
|
||||
setAppPluginMetas({
|
||||
[SupportedPlugin.OnCall]: pluginMetaToPluginConfig(pluginMeta[SupportedPlugin.OnCall]),
|
||||
[SupportedPlugin.Incident]: pluginMetaToPluginConfig(pluginMeta[SupportedPlugin.Incident]),
|
||||
});
|
||||
expect(getIsIrmPluginPresent()).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getIrmIfPresentOrIncidentPluginId', () => {
|
||||
it('should return IRM plugin ID when IRM plugin is present', () => {
|
||||
setAppPluginMetas({ [SupportedPlugin.Irm]: pluginMetaToPluginConfig(pluginMeta[SupportedPlugin.Irm]) });
|
||||
expect(getIrmIfPresentOrIncidentPluginId()).toBe(SupportedPlugin.Irm);
|
||||
});
|
||||
|
||||
it('should return Incident plugin ID when IRM plugin is not present', () => {
|
||||
setAppPluginMetas({
|
||||
[SupportedPlugin.OnCall]: pluginMetaToPluginConfig(pluginMeta[SupportedPlugin.OnCall]),
|
||||
[SupportedPlugin.Incident]: pluginMetaToPluginConfig(pluginMeta[SupportedPlugin.Incident]),
|
||||
});
|
||||
expect(getIrmIfPresentOrIncidentPluginId()).toBe(SupportedPlugin.Incident);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getIrmIfPresentOrOnCallPluginId', () => {
|
||||
it('should return IRM plugin ID when IRM plugin is present', () => {
|
||||
setAppPluginMetas({ [SupportedPlugin.Irm]: pluginMetaToPluginConfig(pluginMeta[SupportedPlugin.Irm]) });
|
||||
expect(getIrmIfPresentOrOnCallPluginId()).toBe(SupportedPlugin.Irm);
|
||||
});
|
||||
|
||||
it('should return OnCall plugin ID when IRM plugin is not present', () => {
|
||||
setAppPluginMetas({
|
||||
[SupportedPlugin.OnCall]: pluginMetaToPluginConfig(pluginMeta[SupportedPlugin.OnCall]),
|
||||
[SupportedPlugin.Incident]: pluginMetaToPluginConfig(pluginMeta[SupportedPlugin.Incident]),
|
||||
});
|
||||
expect(getIrmIfPresentOrOnCallPluginId()).toBe(SupportedPlugin.OnCall);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,8 +1,5 @@
|
||||
import { DataSourceInstanceSettings, DataSourceJsonData } from '@grafana/data';
|
||||
import { config } from '@grafana/runtime';
|
||||
import { getAppPluginMetas } from '@grafana/runtime/unstable';
|
||||
|
||||
import { SupportedPlugin } from '../types/pluginBridges';
|
||||
|
||||
import { isValidPrometheusDuration, safeParsePrometheusDuration } from './time';
|
||||
|
||||
@@ -29,15 +26,3 @@ export function checkEvaluationIntervalGlobalLimit(alertGroupEvaluateEvery?: str
|
||||
|
||||
return { globalLimit: evaluateEveryGlobalLimitMs, exceedsLimit };
|
||||
}
|
||||
|
||||
export function getIsIrmPluginPresent() {
|
||||
return SupportedPlugin.Irm in getAppPluginMetas();
|
||||
}
|
||||
|
||||
export function getIrmIfPresentOrIncidentPluginId() {
|
||||
return getIsIrmPluginPresent() ? SupportedPlugin.Irm : SupportedPlugin.Incident;
|
||||
}
|
||||
|
||||
export function getIrmIfPresentOrOnCallPluginId() {
|
||||
return getIsIrmPluginPresent() ? SupportedPlugin.Irm : SupportedPlugin.OnCall;
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { incidentsApi } from 'app/features/alerting/unified/api/incidentsApi';
|
||||
import { usePluginBridge } from 'app/features/alerting/unified/hooks/usePluginBridge';
|
||||
import { getIrmIfPresentOrIncidentPluginId } from 'app/features/alerting/unified/utils/config';
|
||||
|
||||
import { useIrmConfig } from '../irmHooks';
|
||||
|
||||
interface IncidentsPluginConfig {
|
||||
isInstalled: boolean;
|
||||
@@ -10,16 +11,18 @@ interface IncidentsPluginConfig {
|
||||
}
|
||||
|
||||
export function useGetIncidentPluginConfig(): IncidentsPluginConfig {
|
||||
const { installed: incidentPluginInstalled, loading: loadingPluginSettings } = usePluginBridge(
|
||||
getIrmIfPresentOrIncidentPluginId()
|
||||
);
|
||||
const {
|
||||
irmConfig: { incidentPluginId },
|
||||
isIrmConfigLoading,
|
||||
} = useIrmConfig();
|
||||
const { installed: incidentPluginInstalled, loading: loadingPluginSettings } = usePluginBridge(incidentPluginId);
|
||||
const { data: incidentsConfig, isLoading: loadingPluginConfig } =
|
||||
incidentsApi.endpoints.getIncidentsPluginConfig.useQuery();
|
||||
incidentsApi(incidentPluginId).endpoints.getIncidentsPluginConfig.useQuery();
|
||||
|
||||
return {
|
||||
isInstalled: incidentPluginInstalled ?? false,
|
||||
isChatOpsInstalled: incidentsConfig?.isChatOpsInstalled ?? false,
|
||||
isIncidentCreated: incidentsConfig?.isIncidentCreated ?? false,
|
||||
isLoading: loadingPluginSettings || loadingPluginConfig,
|
||||
isLoading: loadingPluginSettings || loadingPluginConfig || isIrmConfigLoading,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -0,0 +1,75 @@
|
||||
import { renderHook, waitFor } from '@testing-library/react';
|
||||
|
||||
import { setAppPluginMetas } from '@grafana/runtime/internal';
|
||||
import { pluginMeta, pluginMetaToPluginConfig } from 'app/features/alerting/unified/testSetup/plugins';
|
||||
import { SupportedPlugin } from 'app/features/alerting/unified/types/pluginBridges';
|
||||
|
||||
import { useIrmConfig } from './irmHooks';
|
||||
|
||||
describe('useIrmConfig', () => {
|
||||
it('should return default values during load', async () => {
|
||||
const { result } = renderHook(() => useIrmConfig());
|
||||
|
||||
expect(result.current.isIrmConfigLoading).toBe(true);
|
||||
expect(result.current.irmConfig).toEqual({
|
||||
isIrmPluginPresent: false,
|
||||
incidentPluginId: SupportedPlugin.Incident,
|
||||
onCallPluginId: SupportedPlugin.OnCall,
|
||||
});
|
||||
await waitFor(() => expect(result.current.isIrmConfigLoading).toBe(false));
|
||||
});
|
||||
|
||||
describe('when IRM plugin does not exists in apps', () => {
|
||||
beforeEach(() => {
|
||||
setAppPluginMetas({});
|
||||
});
|
||||
|
||||
it('isIrmPluginPresent should be false', async () => {
|
||||
const { result } = renderHook(() => useIrmConfig());
|
||||
|
||||
await waitFor(() => expect(result.current.isIrmConfigLoading).toBe(false));
|
||||
expect(result.current.irmConfig.isIrmPluginPresent).toBe(false);
|
||||
});
|
||||
|
||||
it('incidentPluginId should be Incident plugin ID', async () => {
|
||||
const { result } = renderHook(() => useIrmConfig());
|
||||
|
||||
await waitFor(() => expect(result.current.isIrmConfigLoading).toBe(false));
|
||||
expect(result.current.irmConfig.incidentPluginId).toBe(SupportedPlugin.Incident);
|
||||
});
|
||||
|
||||
it('onCallPluginId should be OnCall plugin ID', async () => {
|
||||
const { result } = renderHook(() => useIrmConfig());
|
||||
|
||||
await waitFor(() => expect(result.current.isIrmConfigLoading).toBe(false));
|
||||
expect(result.current.irmConfig.onCallPluginId).toBe(SupportedPlugin.OnCall);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when IRM plugin exists in apps', () => {
|
||||
beforeEach(() => {
|
||||
setAppPluginMetas({ [SupportedPlugin.Irm]: pluginMetaToPluginConfig(pluginMeta[SupportedPlugin.Irm]) });
|
||||
});
|
||||
|
||||
it('isIrmPluginPresent should be true', async () => {
|
||||
const { result } = renderHook(() => useIrmConfig());
|
||||
|
||||
await waitFor(() => expect(result.current.isIrmConfigLoading).toBe(false));
|
||||
expect(result.current.irmConfig.isIrmPluginPresent).toBe(true);
|
||||
});
|
||||
|
||||
it('incidentPluginId should be IRM plugin ID', async () => {
|
||||
const { result } = renderHook(() => useIrmConfig());
|
||||
|
||||
await waitFor(() => expect(result.current.isIrmConfigLoading).toBe(false));
|
||||
expect(result.current.irmConfig.incidentPluginId).toBe(SupportedPlugin.Irm);
|
||||
});
|
||||
|
||||
it('onCallPluginId should be IRM plugin ID', async () => {
|
||||
const { result } = renderHook(() => useIrmConfig());
|
||||
|
||||
await waitFor(() => expect(result.current.isIrmConfigLoading).toBe(false));
|
||||
expect(result.current.irmConfig.onCallPluginId).toBe(SupportedPlugin.Irm);
|
||||
});
|
||||
});
|
||||
});
|
||||
+59
-25
@@ -1,14 +1,12 @@
|
||||
import { useMemo } from 'react';
|
||||
import { useAsync } from 'react-use';
|
||||
|
||||
import { t } from '@grafana/i18n';
|
||||
import { locationService } from '@grafana/runtime';
|
||||
import { getAppPluginConfig } from '@grafana/runtime/unstable';
|
||||
import { useGrafanaContactPoints } from 'app/features/alerting/unified/components/contact-points/useContactPoints';
|
||||
import { useNotificationPolicyRoute } from 'app/features/alerting/unified/components/notification-policies/useNotificationPolicyRoute';
|
||||
import {
|
||||
getIrmIfPresentOrIncidentPluginId,
|
||||
getIrmIfPresentOrOnCallPluginId,
|
||||
getIsIrmPluginPresent,
|
||||
} from 'app/features/alerting/unified/utils/config';
|
||||
import { SupportedPlugin } from 'app/features/alerting/unified/types/pluginBridges';
|
||||
import { GRAFANA_RULES_SOURCE_NAME } from 'app/features/alerting/unified/utils/datasource';
|
||||
import { RelativeUrl, createRelativeUrl } from 'app/features/alerting/unified/utils/url';
|
||||
|
||||
@@ -111,6 +109,39 @@ function useGetConfigurationForApps() {
|
||||
};
|
||||
}
|
||||
|
||||
export interface UseIsIrmConfig {
|
||||
isIrmPluginPresent: boolean;
|
||||
incidentPluginId: SupportedPlugin;
|
||||
onCallPluginId: SupportedPlugin;
|
||||
}
|
||||
export interface UseIsIrmConfigResult {
|
||||
isIrmConfigLoading: boolean;
|
||||
irmConfig: UseIsIrmConfig;
|
||||
}
|
||||
|
||||
export function useIrmConfig(): UseIsIrmConfigResult {
|
||||
const { loading, value: irmConfig } = useAsync(async () => {
|
||||
const app = await getAppPluginConfig(SupportedPlugin.Irm);
|
||||
const isIrmPluginPresent = Boolean(app);
|
||||
const incidentPluginId = isIrmPluginPresent ? SupportedPlugin.Irm : SupportedPlugin.Incident;
|
||||
const onCallPluginId = isIrmPluginPresent ? SupportedPlugin.Irm : SupportedPlugin.OnCall;
|
||||
return { isIrmPluginPresent, incidentPluginId, onCallPluginId };
|
||||
});
|
||||
|
||||
if (!irmConfig) {
|
||||
return {
|
||||
isIrmConfigLoading: loading,
|
||||
irmConfig: {
|
||||
isIrmPluginPresent: false,
|
||||
incidentPluginId: SupportedPlugin.Incident,
|
||||
onCallPluginId: SupportedPlugin.OnCall,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
return { isIrmConfigLoading: loading, irmConfig };
|
||||
}
|
||||
|
||||
export function useGetEssentialsConfiguration(): EssentialsConfigurationData {
|
||||
const {
|
||||
alerting: { contactPoints, defaultContactpoint, isCreateAlertRuleDone },
|
||||
@@ -119,6 +150,10 @@ export function useGetEssentialsConfiguration(): EssentialsConfigurationData {
|
||||
slo: { hasSlo, hasSloWithAlert },
|
||||
isLoading,
|
||||
} = useGetConfigurationForApps();
|
||||
const {
|
||||
irmConfig: { incidentPluginId, isIrmPluginPresent, onCallPluginId },
|
||||
isIrmConfigLoading,
|
||||
} = useIrmConfig();
|
||||
|
||||
function onIntegrationClick(integrationId: string, url: RelativeUrl) {
|
||||
const urlToGoWithIntegration = createRelativeUrl(`${url} + ${integrationId}`, {
|
||||
@@ -151,7 +186,7 @@ export function useGetEssentialsConfiguration(): EssentialsConfigurationData {
|
||||
},
|
||||
];
|
||||
|
||||
if (!getIsIrmPluginPresent()) {
|
||||
if (isIrmPluginPresent) {
|
||||
steps = [
|
||||
...steps,
|
||||
{
|
||||
@@ -266,8 +301,8 @@ export function useGetEssentialsConfiguration(): EssentialsConfigurationData {
|
||||
},
|
||||
{
|
||||
title: t('gops.use-get-essentials-configuration.essential-content.title.respond', 'Respond'),
|
||||
description: getIsIrmPluginPresent() ? 'Configure IRM' : 'Configure OnCall and Incident',
|
||||
steps: getIsIrmPluginPresent()
|
||||
description: isIrmPluginPresent ? 'Configure IRM' : 'Configure OnCall and Incident',
|
||||
steps: isIrmPluginPresent
|
||||
? [
|
||||
{
|
||||
title: t(
|
||||
@@ -301,11 +336,11 @@ export function useGetEssentialsConfiguration(): EssentialsConfigurationData {
|
||||
button: {
|
||||
type: 'openLink',
|
||||
urlLink: {
|
||||
url: `/a/${getIrmIfPresentOrIncidentPluginId()}/integrations/apps/grate.irm.slack`,
|
||||
url: `/a/${incidentPluginId}/integrations/apps/grate.irm.slack`,
|
||||
},
|
||||
label: t('gops.use-get-essentials-configuration.essential-content.label.connect', 'Connect'),
|
||||
urlLinkOnDone: {
|
||||
url: `/a/${getIrmIfPresentOrIncidentPluginId()}/integrations/apps/grate.irm.slack`,
|
||||
url: `/a/${incidentPluginId}/integrations/apps/grate.irm.slack`,
|
||||
},
|
||||
labelOnDone: 'View',
|
||||
},
|
||||
@@ -323,11 +358,11 @@ export function useGetEssentialsConfiguration(): EssentialsConfigurationData {
|
||||
button: {
|
||||
type: 'openLink',
|
||||
urlLink: {
|
||||
url: `/a/${getIrmIfPresentOrOnCallPluginId()}/integrations/`,
|
||||
url: `/a/${onCallPluginId}/integrations/`,
|
||||
},
|
||||
label: t('gops.use-get-essentials-configuration.essential-content.label.add', 'Add'),
|
||||
urlLinkOnDone: {
|
||||
url: `/a/${getIrmIfPresentOrOnCallPluginId()}/integrations/`,
|
||||
url: `/a/${onCallPluginId}/integrations/`,
|
||||
},
|
||||
labelOnDone: 'View',
|
||||
},
|
||||
@@ -347,11 +382,11 @@ export function useGetEssentialsConfiguration(): EssentialsConfigurationData {
|
||||
button: {
|
||||
type: 'openLink',
|
||||
urlLink: {
|
||||
url: `/a/${getIrmIfPresentOrIncidentPluginId()}/walkthrough/generate-key`,
|
||||
url: `/a/${incidentPluginId}/walkthrough/generate-key`,
|
||||
},
|
||||
label: t('gops.use-get-essentials-configuration.essential-content.label.initialize', 'Initialize'),
|
||||
urlLinkOnDone: {
|
||||
url: `/a/${getIrmIfPresentOrIncidentPluginId()}`,
|
||||
url: `/a/${incidentPluginId}`,
|
||||
},
|
||||
labelOnDone: 'View',
|
||||
},
|
||||
@@ -369,12 +404,12 @@ export function useGetEssentialsConfiguration(): EssentialsConfigurationData {
|
||||
button: {
|
||||
type: 'openLink',
|
||||
urlLink: {
|
||||
url: `/a/${getIrmIfPresentOrOnCallPluginId()}/settings`,
|
||||
url: `/a/${onCallPluginId}/settings`,
|
||||
queryParams: { tab: 'ChatOps', chatOpsTab: 'Slack' },
|
||||
},
|
||||
label: t('gops.use-get-essentials-configuration.essential-content.label.connect', 'Connect'),
|
||||
urlLinkOnDone: {
|
||||
url: `/a/${getIrmIfPresentOrOnCallPluginId()}/settings`,
|
||||
url: `/a/${onCallPluginId}/settings`,
|
||||
queryParams: { tab: 'ChatOps' },
|
||||
},
|
||||
labelOnDone: 'View',
|
||||
@@ -391,11 +426,11 @@ export function useGetEssentialsConfiguration(): EssentialsConfigurationData {
|
||||
button: {
|
||||
type: 'openLink',
|
||||
urlLink: {
|
||||
url: `/a/${getIrmIfPresentOrIncidentPluginId()}/integrations/grate.slack`,
|
||||
url: `/a/${incidentPluginId}/integrations/grate.slack`,
|
||||
},
|
||||
label: t('gops.use-get-essentials-configuration.essential-content.label.connect', 'Connect'),
|
||||
urlLinkOnDone: {
|
||||
url: `/a/${getIrmIfPresentOrIncidentPluginId()}/integrations`,
|
||||
url: `/a/${incidentPluginId}/integrations`,
|
||||
},
|
||||
},
|
||||
done: isChatOpsInstalled,
|
||||
@@ -412,11 +447,11 @@ export function useGetEssentialsConfiguration(): EssentialsConfigurationData {
|
||||
button: {
|
||||
type: 'openLink',
|
||||
urlLink: {
|
||||
url: `/a/${getIrmIfPresentOrOnCallPluginId()}/integrations/`,
|
||||
url: `/a/${onCallPluginId}/integrations/`,
|
||||
},
|
||||
label: t('gops.use-get-essentials-configuration.essential-content.label.add', 'Add'),
|
||||
urlLinkOnDone: {
|
||||
url: `/a/${getIrmIfPresentOrOnCallPluginId()}/integrations/`,
|
||||
url: `/a/${onCallPluginId}/integrations/`,
|
||||
},
|
||||
labelOnDone: 'View',
|
||||
},
|
||||
@@ -432,7 +467,7 @@ export function useGetEssentialsConfiguration(): EssentialsConfigurationData {
|
||||
description: '',
|
||||
steps: [
|
||||
{
|
||||
title: getIsIrmPluginPresent() ? 'Send test alert' : 'Send OnCall demo alert via Alerting integration',
|
||||
title: isIrmPluginPresent ? 'Send test alert' : 'Send OnCall demo alert via Alerting integration',
|
||||
description: 'In the integration page, click Send demo alert, to review your notification',
|
||||
button: {
|
||||
type: 'dropDown',
|
||||
@@ -441,8 +476,7 @@ export function useGetEssentialsConfiguration(): EssentialsConfigurationData {
|
||||
'Select integration'
|
||||
),
|
||||
options: onCallOptions,
|
||||
onClickOption: (value) =>
|
||||
onIntegrationClick(value, `/a/${getIrmIfPresentOrOnCallPluginId()}/integrations/`),
|
||||
onClickOption: (value) => onIntegrationClick(value, `/a/${onCallPluginId}/integrations/`),
|
||||
stepNotAvailableText: 'No integrations available',
|
||||
},
|
||||
},
|
||||
@@ -458,7 +492,7 @@ export function useGetEssentialsConfiguration(): EssentialsConfigurationData {
|
||||
button: {
|
||||
type: 'openLink',
|
||||
urlLink: {
|
||||
url: `/a/${getIrmIfPresentOrIncidentPluginId()}`,
|
||||
url: `/a/${incidentPluginId}`,
|
||||
queryParams: { declare: 'new', drill: '1' },
|
||||
},
|
||||
label: t('gops.use-get-essentials-configuration.essential-content.label.start-drill', 'Start drill'),
|
||||
@@ -479,7 +513,7 @@ export function useGetEssentialsConfiguration(): EssentialsConfigurationData {
|
||||
},
|
||||
{ stepsDone: 0, totalStepsToDo: 0 }
|
||||
);
|
||||
return { essentialContent, stepsDone, totalStepsToDo, isLoading };
|
||||
return { essentialContent, stepsDone, totalStepsToDo, isLoading: isLoading || isIrmConfigLoading };
|
||||
}
|
||||
interface UseConfigurationProps {
|
||||
dataSourceConfigurationData: DataSourceConfigurationData;
|
||||
@@ -1,29 +1,44 @@
|
||||
import { onCallApi } from 'app/features/alerting/unified/api/onCallApi';
|
||||
import { usePluginBridge } from 'app/features/alerting/unified/hooks/usePluginBridge';
|
||||
import { getIrmIfPresentOrOnCallPluginId } from 'app/features/alerting/unified/utils/config';
|
||||
|
||||
import { useIrmConfig } from '../irmHooks';
|
||||
|
||||
export function useGetOnCallIntegrations() {
|
||||
const { installed: onCallPluginInstalled } = usePluginBridge(getIrmIfPresentOrOnCallPluginId());
|
||||
const {
|
||||
irmConfig: { onCallPluginId },
|
||||
isIrmConfigLoading,
|
||||
} = useIrmConfig();
|
||||
const { installed: onCallPluginInstalled } = usePluginBridge(onCallPluginId);
|
||||
|
||||
const { data: onCallIntegrations } = onCallApi.endpoints.grafanaOnCallIntegrations.useQuery(undefined, {
|
||||
skip: !onCallPluginInstalled,
|
||||
refetchOnFocus: true,
|
||||
refetchOnReconnect: true,
|
||||
refetchOnMountOrArgChange: true,
|
||||
});
|
||||
const { data: onCallIntegrations } = onCallApi(onCallPluginId).endpoints.grafanaOnCallIntegrations.useQuery(
|
||||
undefined,
|
||||
{
|
||||
skip: !onCallPluginInstalled || isIrmConfigLoading,
|
||||
refetchOnFocus: true,
|
||||
refetchOnReconnect: true,
|
||||
refetchOnMountOrArgChange: true,
|
||||
}
|
||||
);
|
||||
|
||||
return onCallIntegrations ?? [];
|
||||
}
|
||||
|
||||
function useGetOnCallConfigurationChecks() {
|
||||
const { data: onCallConfigChecks, isLoading } = onCallApi.endpoints.onCallConfigChecks.useQuery(undefined, {
|
||||
refetchOnFocus: true,
|
||||
refetchOnReconnect: true,
|
||||
refetchOnMountOrArgChange: true,
|
||||
});
|
||||
const {
|
||||
irmConfig: { onCallPluginId },
|
||||
isIrmConfigLoading,
|
||||
} = useIrmConfig();
|
||||
const { data: onCallConfigChecks, isLoading } = onCallApi(onCallPluginId).endpoints.onCallConfigChecks.useQuery(
|
||||
undefined,
|
||||
{
|
||||
refetchOnFocus: true,
|
||||
refetchOnReconnect: true,
|
||||
refetchOnMountOrArgChange: true,
|
||||
}
|
||||
);
|
||||
|
||||
return {
|
||||
isLoading,
|
||||
isLoading: isLoading || isIrmConfigLoading,
|
||||
onCallConfigChecks: onCallConfigChecks ?? { is_chatops_connected: false, is_integration_chatops_connected: false },
|
||||
};
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@ import { firstValueFrom, take } from 'rxjs';
|
||||
|
||||
import { PluginLoadingStrategy } from '@grafana/data';
|
||||
import { setAppPluginMetas } from '@grafana/runtime/internal';
|
||||
import { getAppPluginMeta, getAppPluginMetas } from '@grafana/runtime/unstable';
|
||||
import { getAppPluginMeta } from '@grafana/runtime/unstable';
|
||||
|
||||
import { log } from '../logs/log';
|
||||
import { resetLogMock } from '../logs/testUtils';
|
||||
@@ -31,7 +31,6 @@ jest.mock('../logs/log', () => {
|
||||
});
|
||||
|
||||
describe('AddedComponentsRegistry', () => {
|
||||
const originalApps = getAppPluginMetas();
|
||||
const pluginId = 'grafana-basic-app';
|
||||
const appPluginConfig = {
|
||||
id: pluginId,
|
||||
@@ -66,7 +65,7 @@ describe('AddedComponentsRegistry', () => {
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
setAppPluginMetas(originalApps);
|
||||
setAppPluginMetas({});
|
||||
});
|
||||
|
||||
it('should return empty registry when no extensions registered', async () => {
|
||||
|
||||
@@ -2,7 +2,7 @@ import { firstValueFrom, take } from 'rxjs';
|
||||
|
||||
import { PluginLoadingStrategy } from '@grafana/data';
|
||||
import { setAppPluginMetas } from '@grafana/runtime/internal';
|
||||
import { getAppPluginMeta, getAppPluginMetas } from '@grafana/runtime/unstable';
|
||||
import { getAppPluginMeta } from '@grafana/runtime/unstable';
|
||||
|
||||
import { log } from '../logs/log';
|
||||
import { resetLogMock } from '../logs/testUtils';
|
||||
@@ -30,7 +30,6 @@ jest.mock('../logs/log', () => {
|
||||
});
|
||||
|
||||
describe('addedFunctionsRegistry', () => {
|
||||
const originalApps = getAppPluginMetas();
|
||||
const pluginId = 'grafana-basic-app';
|
||||
const appPluginConfig = {
|
||||
id: pluginId,
|
||||
@@ -65,7 +64,7 @@ describe('addedFunctionsRegistry', () => {
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
setAppPluginMetas(originalApps);
|
||||
setAppPluginMetas({});
|
||||
});
|
||||
|
||||
it('should return empty registry when no extensions registered', async () => {
|
||||
|
||||
@@ -2,7 +2,7 @@ import { firstValueFrom, take } from 'rxjs';
|
||||
|
||||
import { PluginLoadingStrategy } from '@grafana/data';
|
||||
import { setAppPluginMetas } from '@grafana/runtime/internal';
|
||||
import { getAppPluginMeta, getAppPluginMetas } from '@grafana/runtime/unstable';
|
||||
import { getAppPluginMeta } from '@grafana/runtime/unstable';
|
||||
|
||||
import { log } from '../logs/log';
|
||||
import { resetLogMock } from '../logs/testUtils';
|
||||
@@ -30,7 +30,6 @@ jest.mock('../logs/log', () => {
|
||||
});
|
||||
|
||||
describe('AddedLinksRegistry', () => {
|
||||
const originalApps = getAppPluginMetas();
|
||||
const pluginId = 'grafana-basic-app';
|
||||
const appPluginConfig = {
|
||||
id: pluginId,
|
||||
@@ -65,7 +64,7 @@ describe('AddedLinksRegistry', () => {
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
setAppPluginMetas(originalApps);
|
||||
setAppPluginMetas({});
|
||||
});
|
||||
|
||||
it('should return empty registry when no extensions registered', async () => {
|
||||
|
||||
@@ -3,7 +3,7 @@ import { firstValueFrom, take } from 'rxjs';
|
||||
|
||||
import { PluginLoadingStrategy } from '@grafana/data';
|
||||
import { setAppPluginMetas } from '@grafana/runtime/internal';
|
||||
import { getAppPluginMeta, getAppPluginMetas } from '@grafana/runtime/unstable';
|
||||
import { getAppPluginMeta } from '@grafana/runtime/unstable';
|
||||
|
||||
import { log } from '../logs/log';
|
||||
import { resetLogMock } from '../logs/testUtils';
|
||||
@@ -31,7 +31,6 @@ jest.mock('../logs/log', () => {
|
||||
});
|
||||
|
||||
describe('ExposedComponentsRegistry', () => {
|
||||
const originalApps = getAppPluginMetas();
|
||||
const pluginId = 'grafana-basic-app';
|
||||
const appPluginConfig = {
|
||||
id: pluginId,
|
||||
@@ -66,7 +65,7 @@ describe('ExposedComponentsRegistry', () => {
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
setAppPluginMetas(originalApps);
|
||||
setAppPluginMetas({});
|
||||
});
|
||||
|
||||
it('should return empty registry when no exposed components have been registered', async () => {
|
||||
|
||||
@@ -1,19 +1,27 @@
|
||||
import { useAsync } from 'react-use';
|
||||
|
||||
import { AppPluginConfig } from '@grafana/data';
|
||||
import { useAppPluginMetas } from '@grafana/runtime/unstable';
|
||||
|
||||
import { preloadPlugins } from '../pluginPreloader';
|
||||
|
||||
import { getAppPluginConfigs } from './utils';
|
||||
export type UseLoadAppPluginsPredicate = (apps: AppPluginConfig[], filterById: string) => string[];
|
||||
|
||||
export function useLoadAppPlugins(pluginIds: string[] = []): { isLoading: boolean } {
|
||||
const noop: UseLoadAppPluginsPredicate = () => [];
|
||||
|
||||
export function useLoadAppPlugins(
|
||||
filterById: string,
|
||||
predicate: UseLoadAppPluginsPredicate = noop
|
||||
): { isLoading: boolean } {
|
||||
const { isAppPluginMetasLoading, apps } = useAppPluginMetas();
|
||||
const { isAppPluginMetasLoading: isFilteredLoading, apps: filtered } = useAppPluginMetas(predicate(apps, filterById));
|
||||
const { loading: isLoading } = useAsync(async () => {
|
||||
const appConfigs = getAppPluginConfigs(pluginIds);
|
||||
|
||||
if (!appConfigs.length) {
|
||||
if (!filtered.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
await preloadPlugins(appConfigs);
|
||||
});
|
||||
await preloadPlugins(filtered);
|
||||
}, [filtered]);
|
||||
|
||||
return { isLoading };
|
||||
return { isLoading: isLoading || isAppPluginMetasLoading || isFilteredLoading };
|
||||
}
|
||||
|
||||
@@ -4,7 +4,6 @@ import type { JSX } from 'react';
|
||||
import { PluginContextProvider, PluginLoadingStrategy, PluginMeta, PluginType } from '@grafana/data';
|
||||
import { config } from '@grafana/runtime';
|
||||
import { setAppPluginMetas } from '@grafana/runtime/internal';
|
||||
import { getAppPluginMetas } from '@grafana/runtime/unstable';
|
||||
|
||||
import { ExtensionRegistriesProvider } from './ExtensionRegistriesContext';
|
||||
import { log } from './logs/log';
|
||||
@@ -53,7 +52,6 @@ describe('usePluginComponent()', () => {
|
||||
let registries: PluginExtensionRegistries;
|
||||
let wrapper: ({ children }: { children: React.ReactNode }) => JSX.Element;
|
||||
let pluginMeta: PluginMeta;
|
||||
const originalApps = getAppPluginMetas();
|
||||
const pluginId = 'myorg-extensions-app';
|
||||
const exposedComponentId = `${pluginId}/exposed-component/v1`;
|
||||
const exposedComponentConfig = {
|
||||
@@ -145,7 +143,7 @@ describe('usePluginComponent()', () => {
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
setAppPluginMetas(originalApps);
|
||||
setAppPluginMetas({});
|
||||
});
|
||||
|
||||
it('should return null if there are no component exposed for the id', () => {
|
||||
|
||||
@@ -15,7 +15,7 @@ import { isExposedComponentDependencyMissing } from './validators';
|
||||
export function usePluginComponent<Props extends object = {}>(id: string): UsePluginComponentResult<Props> {
|
||||
const registryItem = useExposedComponentRegistrySlice<Props>(id);
|
||||
const pluginContext = usePluginContext();
|
||||
const { isLoading: isLoadingAppPlugins } = useLoadAppPlugins(getExposedComponentPluginDependencies(id));
|
||||
const { isLoading: isLoadingAppPlugins } = useLoadAppPlugins(id, getExposedComponentPluginDependencies);
|
||||
|
||||
return useMemo(() => {
|
||||
// For backwards compatibility we don't enable restrictions in production or when the hook is used in core Grafana.
|
||||
|
||||
@@ -21,7 +21,7 @@ export function usePluginComponents<Props extends object = {}>({
|
||||
}: UsePluginComponentsOptions): UsePluginComponentsResult<Props> {
|
||||
const registryItems = useAddedComponentsRegistrySlice<Props>(extensionPointId);
|
||||
const pluginContext = usePluginContext();
|
||||
const { isLoading: isLoadingAppPlugins } = useLoadAppPlugins(getExtensionPointPluginDependencies(extensionPointId));
|
||||
const { isLoading: isLoadingAppPlugins } = useLoadAppPlugins(extensionPointId, getExtensionPointPluginDependencies);
|
||||
|
||||
return useMemo(() => {
|
||||
const { result } = validateExtensionPoint({ extensionPointId, pluginContext, isLoadingAppPlugins });
|
||||
|
||||
@@ -15,8 +15,7 @@ export function usePluginFunctions<Signature>({
|
||||
}: UsePluginFunctionsOptions): UsePluginFunctionsResult<Signature> {
|
||||
const registryItems = useAddedFunctionsRegistrySlice<Signature>(extensionPointId);
|
||||
const pluginContext = usePluginContext();
|
||||
const deps = getExtensionPointPluginDependencies(extensionPointId);
|
||||
const { isLoading: isLoadingAppPlugins } = useLoadAppPlugins(deps);
|
||||
const { isLoading: isLoadingAppPlugins } = useLoadAppPlugins(extensionPointId, getExtensionPointPluginDependencies);
|
||||
|
||||
return useMemo(() => {
|
||||
const { result } = validateExtensionPoint({ extensionPointId, pluginContext, isLoadingAppPlugins });
|
||||
|
||||
@@ -24,7 +24,7 @@ export function usePluginLinks({
|
||||
}: UsePluginLinksOptions): UsePluginLinksResult {
|
||||
const registryItems = useAddedLinksRegistrySlice(extensionPointId);
|
||||
const pluginContext = usePluginContext();
|
||||
const { isLoading: isLoadingAppPlugins } = useLoadAppPlugins(getExtensionPointPluginDependencies(extensionPointId));
|
||||
const { isLoading: isLoadingAppPlugins } = useLoadAppPlugins(extensionPointId, getExtensionPointPluginDependencies);
|
||||
|
||||
return useMemo(() => {
|
||||
const { result, pointLog } = validateExtensionPoint({
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
import { render, screen, waitFor } from '@testing-library/react';
|
||||
import { type Unsubscribable } from 'rxjs';
|
||||
|
||||
import { dateTime, usePluginContext, PluginLoadingStrategy } from '@grafana/data';
|
||||
import { config, AppPluginConfig } from '@grafana/runtime';
|
||||
import { type AppPluginConfig, dateTime, usePluginContext, PluginLoadingStrategy } from '@grafana/data';
|
||||
import { config } from '@grafana/runtime';
|
||||
import { setAppPluginMetas } from '@grafana/runtime/internal';
|
||||
import { getAppPluginMeta, getAppPluginMetas } from '@grafana/runtime/unstable';
|
||||
import { appEvents } from 'app/core/app_events';
|
||||
import { ShowModalReactEvent } from 'app/types/events';
|
||||
|
||||
@@ -18,7 +17,6 @@ import {
|
||||
wrapWithPluginContext,
|
||||
getExtensionPointPluginDependencies,
|
||||
getExposedComponentPluginDependencies,
|
||||
getAppPluginConfigs,
|
||||
getAppPluginIdFromExposedComponentId,
|
||||
getAppPluginDependencies,
|
||||
getExtensionPointPluginMeta,
|
||||
@@ -998,79 +996,6 @@ describe('Plugin Extensions / Utils', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('getAppPluginConfigs()', () => {
|
||||
const originalApps = getAppPluginMetas();
|
||||
const genereicAppPluginConfig = {
|
||||
path: '',
|
||||
version: '',
|
||||
preload: false,
|
||||
angular: {
|
||||
detected: false,
|
||||
hideDeprecation: false,
|
||||
},
|
||||
loadingStrategy: PluginLoadingStrategy.fetch,
|
||||
dependencies: {
|
||||
grafanaVersion: '8.0.0',
|
||||
plugins: [],
|
||||
extensions: {
|
||||
exposedComponents: [],
|
||||
},
|
||||
},
|
||||
extensions: {
|
||||
addedLinks: [],
|
||||
addedComponents: [],
|
||||
addedFunctions: [],
|
||||
exposedComponents: [],
|
||||
extensionPoints: [],
|
||||
},
|
||||
};
|
||||
|
||||
afterEach(() => {
|
||||
setAppPluginMetas(originalApps);
|
||||
});
|
||||
|
||||
test('should return the app plugin configs based on the provided plugin ids', () => {
|
||||
setAppPluginMetas({
|
||||
'myorg-first-app': {
|
||||
...genereicAppPluginConfig,
|
||||
id: 'myorg-first-app',
|
||||
},
|
||||
'myorg-second-app': {
|
||||
...genereicAppPluginConfig,
|
||||
id: 'myorg-second-app',
|
||||
},
|
||||
'myorg-third-app': {
|
||||
...genereicAppPluginConfig,
|
||||
id: 'myorg-third-app',
|
||||
},
|
||||
});
|
||||
|
||||
expect(getAppPluginConfigs(['myorg-first-app', 'myorg-third-app'])).toEqual([
|
||||
getAppPluginMeta('myorg-first-app'),
|
||||
getAppPluginMeta('myorg-third-app'),
|
||||
]);
|
||||
});
|
||||
|
||||
test('should simply ignore the app plugin ids that do not belong to a config', () => {
|
||||
setAppPluginMetas({
|
||||
'myorg-first-app': {
|
||||
...genereicAppPluginConfig,
|
||||
id: 'myorg-first-app',
|
||||
},
|
||||
'myorg-second-app': {
|
||||
...genereicAppPluginConfig,
|
||||
id: 'myorg-second-app',
|
||||
},
|
||||
'myorg-third-app': {
|
||||
...genereicAppPluginConfig,
|
||||
id: 'myorg-third-app',
|
||||
},
|
||||
});
|
||||
|
||||
expect(getAppPluginConfigs(['myorg-first-app', 'unknown-app-id'])).toEqual([getAppPluginMeta('myorg-first-app')]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getAppPluginIdFromExposedComponentId()', () => {
|
||||
test('should return the app plugin id from an extension point id', () => {
|
||||
expect(getAppPluginIdFromExposedComponentId('myorg-extensions-app/component/v1')).toBe('myorg-extensions-app');
|
||||
@@ -1078,7 +1003,6 @@ describe('Plugin Extensions / Utils', () => {
|
||||
});
|
||||
|
||||
describe('getExtensionPointPluginDependencies()', () => {
|
||||
const originalApps = getAppPluginMetas();
|
||||
const genereicAppPluginConfig = {
|
||||
path: '',
|
||||
version: '',
|
||||
@@ -1104,10 +1028,6 @@ describe('Plugin Extensions / Utils', () => {
|
||||
},
|
||||
};
|
||||
|
||||
afterEach(() => {
|
||||
setAppPluginMetas(originalApps);
|
||||
});
|
||||
|
||||
test('should return the app plugin ids that register extensions to a link extension point', () => {
|
||||
const extensionPointId = 'myorg-first-app/link/v1';
|
||||
|
||||
@@ -1292,7 +1212,6 @@ describe('Plugin Extensions / Utils', () => {
|
||||
});
|
||||
|
||||
describe('getExposedComponentPluginDependencies()', () => {
|
||||
const originalApps = getAppPluginMetas();
|
||||
const genereicAppPluginConfig = {
|
||||
path: '',
|
||||
version: '',
|
||||
@@ -1318,10 +1237,6 @@ describe('Plugin Extensions / Utils', () => {
|
||||
},
|
||||
};
|
||||
|
||||
afterEach(() => {
|
||||
setAppPluginMetas(originalApps);
|
||||
});
|
||||
|
||||
test('should only return the app plugin id that exposes the component, if that component does not depend on anything', () => {
|
||||
const exposedComponentId = 'myorg-second-app/component/v1';
|
||||
|
||||
@@ -1438,7 +1353,6 @@ describe('Plugin Extensions / Utils', () => {
|
||||
});
|
||||
|
||||
describe('getAppPluginDependencies()', () => {
|
||||
const originalApps = getAppPluginMetas();
|
||||
const genereicAppPluginConfig = {
|
||||
path: '',
|
||||
version: '',
|
||||
@@ -1464,10 +1378,6 @@ describe('Plugin Extensions / Utils', () => {
|
||||
},
|
||||
};
|
||||
|
||||
afterEach(() => {
|
||||
setAppPluginMetas(originalApps);
|
||||
});
|
||||
|
||||
test('should not end up in an infinite loop if there are circular dependencies', () => {
|
||||
setAppPluginMetas({
|
||||
'myorg-first-app': {
|
||||
@@ -1528,7 +1438,6 @@ describe('Plugin Extensions / Utils', () => {
|
||||
});
|
||||
|
||||
describe('getExtensionPointPluginMeta()', () => {
|
||||
const originalApps = getAppPluginMetas();
|
||||
const mockExtensionPointId = 'test-extension-point';
|
||||
const mockApp1: AppPluginConfig = {
|
||||
id: 'app1',
|
||||
@@ -1586,10 +1495,6 @@ describe('Plugin Extensions / Utils', () => {
|
||||
setAppPluginMetas({});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
setAppPluginMetas(originalApps);
|
||||
});
|
||||
|
||||
it('should return empty map when no plugins have extensions for the point', () => {
|
||||
setAppPluginMetas({
|
||||
app1: { ...mockApp1, extensions: { ...mockApp1.extensions, addedComponents: [], addedLinks: [] } },
|
||||
|
||||
@@ -4,6 +4,7 @@ import * as React from 'react';
|
||||
import { useAsync } from 'react-use';
|
||||
|
||||
import {
|
||||
type AppPluginConfig,
|
||||
type PluginExtensionEventHelpers,
|
||||
type PluginExtensionOpenModalOptions,
|
||||
isDateTime,
|
||||
@@ -16,8 +17,8 @@ import {
|
||||
PluginExtensionPoints,
|
||||
ExtensionInfo,
|
||||
} from '@grafana/data';
|
||||
import { reportInteraction, config, AppPluginConfig } from '@grafana/runtime';
|
||||
import { getAppPluginMeta, getAppPluginMetas } from '@grafana/runtime/unstable';
|
||||
import { reportInteraction, config } from '@grafana/runtime';
|
||||
import { getAppPluginMeta } from '@grafana/runtime/unstable';
|
||||
import { Modal } from '@grafana/ui';
|
||||
import { appEvents } from 'app/core/app_events';
|
||||
import { getPluginSettings } from 'app/features/plugins/pluginSettings';
|
||||
@@ -33,6 +34,7 @@ import { RestrictedGrafanaApisProvider } from '../components/restrictedGrafanaAp
|
||||
import { ExtensionErrorBoundary } from './ExtensionErrorBoundary';
|
||||
import { ExtensionsLog, log as baseLog } from './logs/log';
|
||||
import { AddedLinkRegistryItem } from './registry/AddedLinksRegistry';
|
||||
import { UseLoadAppPluginsPredicate } from './useLoadAppPlugins';
|
||||
import { assertIsNotPromise, assertStringProps, isPromise } from './validators';
|
||||
|
||||
export function handleErrorsInFn(fn: Function, errorMessagePrefix = '') {
|
||||
@@ -607,9 +609,6 @@ export function getLinkExtensionPathWithTracking(pluginId: string, path: string,
|
||||
// Can be set with the `GF_DEFAULT_APP_MODE` environment variable
|
||||
export const isGrafanaDevMode = () => config.buildInfo.env === 'development';
|
||||
|
||||
export const getAppPluginConfigs = (pluginIds: string[] = []) =>
|
||||
Object.values(getAppPluginMetas()).filter((app) => pluginIds.includes(app.id));
|
||||
|
||||
export const getAppPluginIdFromExposedComponentId = (exposedComponentId: string) => {
|
||||
return exposedComponentId.split('/')[0];
|
||||
};
|
||||
@@ -617,8 +616,11 @@ export const getAppPluginIdFromExposedComponentId = (exposedComponentId: string)
|
||||
// Returns a list of app plugin ids that are registering extensions to this extension point.
|
||||
// (These plugins are necessary to be loaded to use the extension point.)
|
||||
// (The function also returns the plugin ids that the plugins - that extend the extension point - depend on.)
|
||||
export const getExtensionPointPluginDependencies = (extensionPointId: string): string[] => {
|
||||
return Object.values(getAppPluginMetas())
|
||||
export const getExtensionPointPluginDependencies: UseLoadAppPluginsPredicate = (
|
||||
apps: AppPluginConfig[],
|
||||
extensionPointId: string
|
||||
): string[] => {
|
||||
return apps
|
||||
.filter(
|
||||
(app) =>
|
||||
app.extensions.addedLinks.some((link) => link.targets.includes(extensionPointId)) ||
|
||||
@@ -626,7 +628,7 @@ export const getExtensionPointPluginDependencies = (extensionPointId: string): s
|
||||
)
|
||||
.map((app) => app.id)
|
||||
.reduce((acc: string[], id: string) => {
|
||||
return [...acc, id, ...getAppPluginDependencies(id)];
|
||||
return [...acc, id, ...getAppPluginDependencies(apps, id)];
|
||||
}, []);
|
||||
};
|
||||
|
||||
@@ -643,9 +645,12 @@ export type ExtensionPointPluginMeta = Map<
|
||||
* @param extensionPointId - The id of the extension point.
|
||||
* @returns A map of plugin ids and their addedComponents and addedLinks to the extension point.
|
||||
*/
|
||||
export const getExtensionPointPluginMeta = (extensionPointId: string): ExtensionPointPluginMeta => {
|
||||
export const getExtensionPointPluginMeta = (
|
||||
apps: AppPluginConfig[],
|
||||
extensionPointId: string
|
||||
): ExtensionPointPluginMeta => {
|
||||
return new Map(
|
||||
getExtensionPointPluginDependencies(extensionPointId)
|
||||
getExtensionPointPluginDependencies(apps, extensionPointId)
|
||||
.map((pluginId) => {
|
||||
const app = getAppPluginMeta(pluginId);
|
||||
// if the plugin does not exist or does not expose any components or links to the extension point, return undefined
|
||||
@@ -672,19 +677,26 @@ export const getExtensionPointPluginMeta = (extensionPointId: string): Extension
|
||||
|
||||
// Returns a list of app plugin ids that are necessary to be loaded to use the exposed component.
|
||||
// (It is first the plugin that exposes the component, and then the ones that it depends on.)
|
||||
export const getExposedComponentPluginDependencies = (exposedComponentId: string) => {
|
||||
export const getExposedComponentPluginDependencies: UseLoadAppPluginsPredicate = (
|
||||
apps: AppPluginConfig[],
|
||||
exposedComponentId: string
|
||||
) => {
|
||||
const pluginId = getAppPluginIdFromExposedComponentId(exposedComponentId);
|
||||
|
||||
return [pluginId].reduce((acc: string[], pluginId: string) => {
|
||||
return [...acc, pluginId, ...getAppPluginDependencies(pluginId)];
|
||||
return [...acc, pluginId, ...getAppPluginDependencies(apps, pluginId)];
|
||||
}, []);
|
||||
};
|
||||
|
||||
// Returns a list of app plugin ids that are necessary to be loaded, based on the `dependencies.extensions`
|
||||
// metadata field. (For example the plugins that expose components that the app depends on.)
|
||||
// Heads up! This is a recursive function.
|
||||
export const getAppPluginDependencies = (pluginId: string, visited: string[] = []): string[] => {
|
||||
const app = getAppPluginMeta(pluginId);
|
||||
export const getAppPluginDependencies = (
|
||||
apps: AppPluginConfig[],
|
||||
pluginId: string,
|
||||
visited: string[] = []
|
||||
): string[] => {
|
||||
const app = apps.find((a) => a.id === pluginId);
|
||||
if (!app) {
|
||||
return [];
|
||||
}
|
||||
@@ -699,7 +711,7 @@ export const getAppPluginDependencies = (pluginId: string, visited: string[] = [
|
||||
return (
|
||||
pluginIdDependencies
|
||||
.reduce((acc, _pluginId) => {
|
||||
return [...acc, ...getAppPluginDependencies(_pluginId, [...visited, pluginId])];
|
||||
return [...acc, ...getAppPluginDependencies(apps, _pluginId, [...visited, pluginId])];
|
||||
}, pluginIdDependencies)
|
||||
// We don't want the plugin to "depend on itself"
|
||||
.filter((id) => id !== pluginId)
|
||||
@@ -707,23 +719,26 @@ export const getAppPluginDependencies = (pluginId: string, visited: string[] = [
|
||||
};
|
||||
|
||||
// Returns a list of app plugins that has to be loaded before core Grafana could finish the initialization.
|
||||
export const getAppPluginsToAwait = () => {
|
||||
export const getAppPluginsToAwait = (apps: AppPluginConfig[]) => {
|
||||
const pluginIds = [
|
||||
// The "cloud-home-app" is registering banners once it's loaded, and this can cause a rerender in the AppChrome if it's loaded after the Grafana app init.
|
||||
'cloud-home-app',
|
||||
];
|
||||
|
||||
return Object.values(getAppPluginMetas()).filter((app) => pluginIds.includes(app.id));
|
||||
return apps.filter((app) => pluginIds.includes(app.id));
|
||||
};
|
||||
|
||||
// Returns a list of app plugins that has to be preloaded in parallel with the core Grafana initialization.
|
||||
export const getAppPluginsToPreload = () => {
|
||||
export const getAppPluginsToPreload = (apps: AppPluginConfig[]) => {
|
||||
// The DashboardPanelMenu extension point is using the `getPluginExtensions()` API in scenes at the moment, which means that it cannot yet benefit from dynamic plugin loading.
|
||||
const dashboardPanelMenuPluginIds = getExtensionPointPluginDependencies(PluginExtensionPoints.DashboardPanelMenu);
|
||||
const awaitedPluginIds = getAppPluginsToAwait().map((app) => app.id);
|
||||
const dashboardPanelMenuPluginIds = getExtensionPointPluginDependencies(
|
||||
apps,
|
||||
PluginExtensionPoints.DashboardPanelMenu
|
||||
);
|
||||
const awaitedPluginIds = getAppPluginsToAwait(apps).map((app) => app.id);
|
||||
const isNotAwaited = (app: AppPluginConfig) => !awaitedPluginIds.includes(app.id);
|
||||
|
||||
return Object.values(getAppPluginMetas()).filter((app) => {
|
||||
return apps.filter((app) => {
|
||||
return isNotAwaited(app) && (app.preload || dashboardPanelMenuPluginIds.includes(app.id));
|
||||
});
|
||||
};
|
||||
|
||||
@@ -8,7 +8,6 @@ import {
|
||||
PluginType,
|
||||
} from '@grafana/data';
|
||||
import { setAppPluginMetas } from '@grafana/runtime/internal';
|
||||
import { getAppPluginMetas } from '@grafana/runtime/unstable';
|
||||
|
||||
import { createLogMock } from './logs/testUtils';
|
||||
import {
|
||||
@@ -224,7 +223,6 @@ describe('Plugin Extension Validators', () => {
|
||||
});
|
||||
|
||||
describe('isAddedLinkMetaInfoMissing()', () => {
|
||||
const originalApps = getAppPluginMetas();
|
||||
const pluginId = 'myorg-extensions-app';
|
||||
const appPluginConfig = {
|
||||
id: pluginId,
|
||||
@@ -262,7 +260,7 @@ describe('Plugin Extension Validators', () => {
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
setAppPluginMetas(originalApps);
|
||||
setAppPluginMetas({});
|
||||
});
|
||||
|
||||
it('should return FALSE if the meta-info in the plugin.json is correct', () => {
|
||||
@@ -373,7 +371,6 @@ describe('Plugin Extension Validators', () => {
|
||||
});
|
||||
|
||||
describe('isAddedComponentMetaInfoMissing()', () => {
|
||||
const originalApps = getAppPluginMetas();
|
||||
const pluginId = 'myorg-extensions-app';
|
||||
const appPluginConfig = {
|
||||
id: pluginId,
|
||||
@@ -412,7 +409,7 @@ describe('Plugin Extension Validators', () => {
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
setAppPluginMetas(originalApps);
|
||||
setAppPluginMetas({});
|
||||
});
|
||||
|
||||
it('should return FALSE if the meta-info in the plugin.json is correct', () => {
|
||||
@@ -522,7 +519,6 @@ describe('Plugin Extension Validators', () => {
|
||||
});
|
||||
|
||||
describe('isExposedComponentMetaInfoMissing()', () => {
|
||||
const originalApps = getAppPluginMetas();
|
||||
const pluginId = 'myorg-extensions-app';
|
||||
const appPluginConfig = {
|
||||
id: pluginId,
|
||||
@@ -561,7 +557,7 @@ describe('Plugin Extension Validators', () => {
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
setAppPluginMetas(originalApps);
|
||||
setAppPluginMetas({});
|
||||
});
|
||||
|
||||
it('should return FALSE if the meta-info in the plugin.json is correct', () => {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { PluginType, patchArrayVectorProrotypeMethods } from '@grafana/data';
|
||||
import { config } from '@grafana/runtime';
|
||||
import { getAppPluginMetas } from '@grafana/runtime/unstable';
|
||||
import { getAppPluginConfig } from '@grafana/runtime/unstable';
|
||||
|
||||
import { transformPluginSourceForCDN } from '../cdn/utils';
|
||||
import { resolvePluginUrlWithCache } from '../loader/pluginInfoCache';
|
||||
@@ -122,7 +122,7 @@ export function patchSandboxEnvironmentPrototype(sandboxEnvironment: SandboxEnvi
|
||||
);
|
||||
}
|
||||
|
||||
export function getPluginLoadData(pluginId: string): SandboxPluginMeta {
|
||||
export async function getPluginLoadData(pluginId: string): Promise<SandboxPluginMeta> {
|
||||
// find it in datasources
|
||||
for (const datasource of Object.values(config.datasources)) {
|
||||
if (datasource.type === pluginId) {
|
||||
@@ -139,16 +139,15 @@ export function getPluginLoadData(pluginId: string): SandboxPluginMeta {
|
||||
|
||||
//find it in apps
|
||||
//the information inside the apps object is more limited
|
||||
for (const app of Object.values(getAppPluginMetas())) {
|
||||
if (app.id === pluginId) {
|
||||
return {
|
||||
id: pluginId,
|
||||
type: PluginType.app,
|
||||
module: app.path,
|
||||
moduleHash: app.moduleHash,
|
||||
};
|
||||
}
|
||||
const app = await getAppPluginConfig(pluginId);
|
||||
if (!app) {
|
||||
throw new Error(`Could not find plugin ${pluginId}`);
|
||||
}
|
||||
|
||||
throw new Error(`Could not find plugin ${pluginId}`);
|
||||
return {
|
||||
id: pluginId,
|
||||
type: PluginType.app,
|
||||
module: app.path,
|
||||
moduleHash: app.moduleHash,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -31,7 +31,7 @@ const pluginLogCache: Record<string, boolean> = {};
|
||||
export async function importPluginModuleInSandbox({ pluginId }: { pluginId: string }): Promise<System.Module> {
|
||||
patchWebAPIs();
|
||||
try {
|
||||
const pluginMeta = getPluginLoadData(pluginId);
|
||||
const pluginMeta = await getPluginLoadData(pluginId);
|
||||
if (!pluginImportCache.has(pluginId)) {
|
||||
pluginImportCache.set(pluginId, doImportPluginModuleInSandbox(pluginMeta));
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user