chore: more refactor
This commit is contained in:
@@ -29,4 +29,4 @@ export {
|
||||
export { UserStorage } from '../utils/userStorage';
|
||||
|
||||
export { initOpenFeature, evaluateBooleanFlag } from './openFeature';
|
||||
export { initPluginMetas, setAppPluginMetas } from '../services/plugins';
|
||||
export { setAppPluginMetas } from '../services/plugins';
|
||||
|
||||
@@ -14,7 +14,7 @@ function areAppsInitialized(): boolean {
|
||||
return Boolean(Object.keys(apps).length);
|
||||
}
|
||||
|
||||
export async function initPluginMetas(): Promise<void> {
|
||||
async function initPluginMetas(): Promise<void> {
|
||||
if (appsPromise) {
|
||||
return appsPromise;
|
||||
}
|
||||
@@ -44,15 +44,7 @@ export async function getAppPluginMetas(): Promise<AppPluginConfig[]> {
|
||||
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> {
|
||||
export async function getAppPluginMeta(id: string): Promise<AppPluginConfig | undefined> {
|
||||
if (!areAppsInitialized()) {
|
||||
await initPluginMetas();
|
||||
}
|
||||
@@ -85,3 +77,14 @@ export function useAppPluginMetas(filterByIds: string[] = []): UseAppPluginMetas
|
||||
|
||||
return { isAppPluginMetasLoading: loading, error, apps: filtered };
|
||||
}
|
||||
|
||||
export interface UseAppPluginMetaResult {
|
||||
isAppPluginMetaLoading: boolean;
|
||||
error: Error | undefined;
|
||||
app: AppPluginConfig | undefined;
|
||||
}
|
||||
|
||||
export function useAppPluginMeta(filterById: string): UseAppPluginMetaResult {
|
||||
const { loading, error, value: app } = useAsync(() => getAppPluginMeta(filterById));
|
||||
return { isAppPluginMetaLoading: loading, error, app };
|
||||
}
|
||||
|
||||
@@ -14,9 +14,10 @@ export const unstable = {};
|
||||
|
||||
export {
|
||||
type AppPluginMetas,
|
||||
type UseAppPluginMetasResult as UseAppPluginMetasCollectionResult,
|
||||
type UseAppPluginMetaResult,
|
||||
type UseAppPluginMetasResult,
|
||||
getAppPluginMeta,
|
||||
getAppPluginConfig,
|
||||
getAppPluginMetas,
|
||||
useAppPluginMeta,
|
||||
useAppPluginMetas,
|
||||
} from './services/plugins';
|
||||
|
||||
+3
-17
@@ -44,14 +44,12 @@ import {
|
||||
} from '@grafana/runtime';
|
||||
import {
|
||||
initOpenFeature,
|
||||
initPluginMetas,
|
||||
setGetObservablePluginComponents,
|
||||
setGetObservablePluginLinks,
|
||||
setPanelDataErrorView,
|
||||
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';
|
||||
@@ -101,10 +99,9 @@ import { usePluginComponent } from './features/plugins/extensions/usePluginCompo
|
||||
import { usePluginComponents } from './features/plugins/extensions/usePluginComponents';
|
||||
import { usePluginFunctions } from './features/plugins/extensions/usePluginFunctions';
|
||||
import { usePluginLinks } from './features/plugins/extensions/usePluginLinks';
|
||||
import { getAppPluginsToAwait, getAppPluginsToPreload } from './features/plugins/extensions/utils';
|
||||
import { importPanelPlugin, syncGetPanelPlugin } from './features/plugins/importPanelPlugin';
|
||||
import { initSystemJSHooks } from './features/plugins/loader/systemjsHooks';
|
||||
import { preloadPlugins } from './features/plugins/pluginPreloader';
|
||||
import { preloadPluginsToBeAwaited, preloadPluginsToBePreloaded } from './features/plugins/pluginPreloader';
|
||||
import { QueryRunner } from './features/query/state/QueryRunner';
|
||||
import { runRequest } from './features/query/state/runRequest';
|
||||
import { initWindowRuntime } from './features/runtime/init';
|
||||
@@ -178,13 +175,6 @@ export class GrafanaApp {
|
||||
// This needs to be done after the `initEchoSrv` since it is being used under the hood.
|
||||
startMeasure('frontend_app_init');
|
||||
|
||||
try {
|
||||
startMeasure('frontend_app_init_plugins');
|
||||
await initPluginMetas();
|
||||
} finally {
|
||||
stopMeasure('frontend_app_init_plugins');
|
||||
}
|
||||
|
||||
setLocale(config.regionalFormat);
|
||||
setWeekStart(contextSrv.user.weekStart);
|
||||
setPanelRenderer(PanelRenderer);
|
||||
@@ -266,12 +256,8 @@ export class GrafanaApp {
|
||||
const skipAppPluginsPreload =
|
||||
config.featureToggles.rendererDisableAppPluginsPreload && contextSrv.user.authenticatedBy === 'render';
|
||||
if (contextSrv.user.orgRole !== '' && !skipAppPluginsPreload) {
|
||||
const apps = await getAppPluginMetas();
|
||||
const appPluginsToAwait = getAppPluginsToAwait(apps);
|
||||
const appPluginsToPreload = getAppPluginsToPreload(apps);
|
||||
|
||||
preloadPlugins(appPluginsToPreload);
|
||||
await preloadPlugins(appPluginsToAwait);
|
||||
preloadPluginsToBePreloaded();
|
||||
await preloadPluginsToBeAwaited();
|
||||
}
|
||||
|
||||
setHelpNavItemHook(useHelpNode);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { SupportedPlugin } from '../types/pluginBridges';
|
||||
import { getIrmIfPresentOrIncidentPluginId } from '../utils/config';
|
||||
|
||||
import { alertingApi } from './alertingApi';
|
||||
|
||||
@@ -7,18 +7,17 @@ interface IncidentsPluginConfigDto {
|
||||
isIncidentCreated: boolean;
|
||||
}
|
||||
|
||||
const getProxyApiUrl = (path: string, pluginId: SupportedPlugin) => `/api/plugins/${pluginId}/resources${path}`;
|
||||
const getProxyApiUrl = (path: string) => `/api/plugins/${getIrmIfPresentOrIncidentPluginId()}/resources${path}`;
|
||||
|
||||
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,
|
||||
}),
|
||||
export const incidentsApi = alertingApi.injectEndpoints({
|
||||
endpoints: (build) => ({
|
||||
getIncidentsPluginConfig: build.query<IncidentsPluginConfigDto, void>({
|
||||
query: () => ({
|
||||
url: getProxyApiUrl('/api/ConfigurationTrackerService.GetConfigurationTracker'),
|
||||
data: {},
|
||||
method: 'POST',
|
||||
showErrorAlert: false,
|
||||
}),
|
||||
}),
|
||||
});
|
||||
}),
|
||||
});
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { setAppPluginMetas } from '@grafana/runtime/internal';
|
||||
import { config } from '@grafana/runtime';
|
||||
|
||||
import { pluginMeta, pluginMetaToPluginConfig } from '../testSetup/plugins';
|
||||
import { SupportedPlugin } from '../types/pluginBridges';
|
||||
@@ -7,7 +7,8 @@ import { getProxyApiUrl } from './onCallApi';
|
||||
|
||||
describe('getProxyApiUrl', () => {
|
||||
it('should return URL with IRM plugin ID when IRM plugin is present', () => {
|
||||
setAppPluginMetas({ [SupportedPlugin.Irm]: pluginMetaToPluginConfig(pluginMeta[SupportedPlugin.Irm]) });
|
||||
// eslint-disable-next-line no-restricted-syntax
|
||||
config.apps = { [SupportedPlugin.Irm]: pluginMetaToPluginConfig(pluginMeta[SupportedPlugin.Irm]) };
|
||||
|
||||
expect(getProxyApiUrl('/alert_receive_channels/')).toBe(
|
||||
'/api/plugins/grafana-irm-app/resources/alert_receive_channels/'
|
||||
@@ -15,10 +16,11 @@ describe('getProxyApiUrl', () => {
|
||||
});
|
||||
|
||||
it('should return URL with OnCall plugin ID when IRM plugin is not present', () => {
|
||||
setAppPluginMetas({
|
||||
// eslint-disable-next-line no-restricted-syntax
|
||||
config.apps = {
|
||||
[SupportedPlugin.OnCall]: pluginMetaToPluginConfig(pluginMeta[SupportedPlugin.OnCall]),
|
||||
[SupportedPlugin.Incident]: pluginMetaToPluginConfig(pluginMeta[SupportedPlugin.Incident]),
|
||||
});
|
||||
};
|
||||
|
||||
expect(getProxyApiUrl('/alert_receive_channels/')).toBe(
|
||||
'/api/plugins/grafana-oncall-app/resources/alert_receive_channels/'
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { FetchError, isFetchError } from '@grafana/runtime';
|
||||
|
||||
import { GRAFANA_ONCALL_INTEGRATION_TYPE } from '../components/receivers/grafanaAppReceivers/onCall/onCall';
|
||||
import { SupportedPlugin } from '../types/pluginBridges';
|
||||
import { getIrmIfPresentOrOnCallPluginId } from '../utils/config';
|
||||
|
||||
import { alertingApi } from './alertingApi';
|
||||
|
||||
@@ -38,63 +38,62 @@ export interface OnCallConfigChecks {
|
||||
is_integration_chatops_connected: boolean;
|
||||
}
|
||||
|
||||
export function getProxyApiUrl(path: string, pluginId: SupportedPlugin) {
|
||||
return `/api/plugins/${pluginId}/resources${path}`;
|
||||
export function getProxyApiUrl(path: string) {
|
||||
return `/api/plugins/${getIrmIfPresentOrOnCallPluginId()}/resources${path}`;
|
||||
}
|
||||
|
||||
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;
|
||||
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,
|
||||
},
|
||||
providesTags: ['OnCallIntegrations'],
|
||||
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,
|
||||
}),
|
||||
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,
|
||||
}),
|
||||
}),
|
||||
});
|
||||
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
|
||||
@@ -102,6 +101,8 @@ 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,19 +11,16 @@ interface Props {
|
||||
url?: string;
|
||||
}
|
||||
|
||||
const pluginId = getIrmIfPresentOrIncidentPluginId();
|
||||
|
||||
export const DeclareIncidentButton = ({ title = '', severity = '', url = '' }: Props) => {
|
||||
const {
|
||||
irmConfig: { incidentPluginId },
|
||||
isIrmConfigLoading,
|
||||
} = useIrmConfig();
|
||||
const bridgeURL = createBridgeURL(incidentPluginId, '/incidents/declare', {
|
||||
const bridgeURL = createBridgeURL(pluginId, '/incidents/declare', {
|
||||
title,
|
||||
severity,
|
||||
url,
|
||||
});
|
||||
|
||||
const { loading: isPluginBridgeLoading, installed, settings } = usePluginBridge(incidentPluginId);
|
||||
const loading = isIrmConfigLoading || isPluginBridgeLoading;
|
||||
const { loading, installed, settings } = usePluginBridge(pluginId);
|
||||
|
||||
return (
|
||||
<>
|
||||
@@ -54,18 +51,13 @@ export const DeclareIncidentButton = ({ title = '', severity = '', url = '' }: P
|
||||
};
|
||||
|
||||
export const DeclareIncidentMenuItem = ({ title = '', severity = '', url = '' }: Props) => {
|
||||
const {
|
||||
irmConfig: { incidentPluginId },
|
||||
isIrmConfigLoading,
|
||||
} = useIrmConfig();
|
||||
const bridgeURL = createBridgeURL(incidentPluginId, '/incidents/declare', {
|
||||
const bridgeURL = createBridgeURL(pluginId, '/incidents/declare', {
|
||||
title,
|
||||
severity,
|
||||
url,
|
||||
});
|
||||
|
||||
const { loading: isPluginBridgeLoading, installed, settings } = usePluginBridge(incidentPluginId);
|
||||
const loading = isIrmConfigLoading || isPluginBridgeLoading;
|
||||
const { loading, installed, settings } = usePluginBridge(pluginId);
|
||||
|
||||
return (
|
||||
<>
|
||||
|
||||
@@ -12,7 +12,6 @@ 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';
|
||||
@@ -22,7 +21,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 { SupportedPlugin } from '../../types/pluginBridges';
|
||||
import { getIrmIfPresentOrOnCallPluginId } from '../../utils/config';
|
||||
|
||||
import { enhanceContactPointsWithMetadata } from './utils';
|
||||
|
||||
@@ -42,7 +41,7 @@ const {
|
||||
useGrafanaNotifiersQuery,
|
||||
useLazyGetAlertmanagerConfigurationQuery,
|
||||
} = alertmanagerApi;
|
||||
|
||||
const { useGrafanaOnCallIntegrationsQuery } = onCallApi;
|
||||
const {
|
||||
useListNamespacedReceiverQuery,
|
||||
useReadNamespacedReceiverQuery,
|
||||
@@ -62,14 +61,8 @@ const defaultOptions = {
|
||||
* Otherwise, returns no data
|
||||
*/
|
||||
const useOnCallIntegrations = ({ skip }: Skippable = {}) => {
|
||||
const {
|
||||
irmConfig: { onCallPluginId },
|
||||
isIrmConfigLoading,
|
||||
} = useIrmConfig();
|
||||
const { installed, loading: isPluginBridgeLoading } = usePluginBridge(onCallPluginId);
|
||||
const { useGrafanaOnCallIntegrationsQuery } = onCallApi(onCallPluginId);
|
||||
const { installed, loading } = usePluginBridge(getIrmIfPresentOrOnCallPluginId());
|
||||
const oncallIntegrationsResponse = useGrafanaOnCallIntegrationsQuery(undefined, { skip: skip || !installed });
|
||||
const loading = isIrmConfigLoading || isPluginBridgeLoading;
|
||||
|
||||
return useMemo(() => {
|
||||
if (installed) {
|
||||
@@ -145,11 +138,9 @@ 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 || isIrmConfigLoading;
|
||||
const isLoading = onCallResponse.isLoading || alertNotifiers.isLoading || contactPointsListResponse.isLoading;
|
||||
|
||||
if (isLoading) {
|
||||
return {
|
||||
@@ -169,7 +160,6 @@ export const useGrafanaContactPoints = ({
|
||||
onCallIntegrations: onCallResponse?.data,
|
||||
contactPoints: contactPointsListResponse.data || [],
|
||||
alertmanagerConfiguration: alertmanagerConfigResponse.data,
|
||||
irmConfig,
|
||||
});
|
||||
|
||||
return {
|
||||
@@ -182,8 +172,6 @@ export const useGrafanaContactPoints = ({
|
||||
contactPointsListResponse,
|
||||
contactPointsStatusResponse,
|
||||
onCallResponse,
|
||||
isIrmConfigLoading,
|
||||
irmConfig,
|
||||
]);
|
||||
};
|
||||
|
||||
@@ -250,10 +238,9 @@ 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 || isIrmConfigLoading,
|
||||
skip: skip || !isGrafanaAlertmanager,
|
||||
fetchStatuses,
|
||||
fetchPolicies,
|
||||
});
|
||||
@@ -267,7 +254,6 @@ export function useContactPointsWithStatus({
|
||||
notifiers: cloudNotifierTypes,
|
||||
contactPoints: result.data.alertmanager_config.receivers ?? [],
|
||||
alertmanagerConfiguration: result.data,
|
||||
irmConfig,
|
||||
})
|
||||
: [],
|
||||
}),
|
||||
|
||||
@@ -5,7 +5,6 @@ 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,
|
||||
@@ -117,7 +116,6 @@ type EnhanceContactPointsArgs = {
|
||||
onCallIntegrations?: OnCallIntegrationDTO[] | undefined | null;
|
||||
contactPoints: Receiver[];
|
||||
alertmanagerConfiguration?: AlertManagerCortexConfig;
|
||||
irmConfig: UseIsIrmConfig;
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -134,7 +132,6 @@ 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(
|
||||
@@ -165,7 +162,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), irmConfig)
|
||||
? getOnCallMetadata(onCallIntegrations, receiver, Boolean(alertmanagerConfiguration))
|
||||
: undefined,
|
||||
};
|
||||
}),
|
||||
|
||||
+3
-2
@@ -1,4 +1,4 @@
|
||||
import { setAppPluginMetas } from '@grafana/runtime/internal';
|
||||
import { config } from '@grafana/runtime';
|
||||
import { RulerRulesConfigDTO } from 'app/types/unified-alerting-dto';
|
||||
|
||||
import { pluginMeta, pluginMetaToPluginConfig } from '../../testSetup/plugins';
|
||||
@@ -67,7 +67,8 @@ describe('filterRulerRulesConfig', () => {
|
||||
};
|
||||
|
||||
it('should filter by namespace', () => {
|
||||
setAppPluginMetas({ [SupportedPlugin.Slo]: pluginMetaToPluginConfig(pluginMeta[SupportedPlugin.Slo]) });
|
||||
// eslint-disable-next-line no-restricted-syntax
|
||||
config.apps = { [SupportedPlugin.Slo]: pluginMetaToPluginConfig(pluginMeta[SupportedPlugin.Slo]) };
|
||||
const { filteredConfig, someRulesAreSkipped } = filterRulerRulesConfig(mockRulesConfig, 'namespace1');
|
||||
|
||||
expect(filteredConfig).toEqual({
|
||||
|
||||
+7
-14
@@ -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 { useIrmConfig } from 'app/features/gops/configuration-tracker/irmHooks';
|
||||
import { getIrmIfPresentOrOnCallPluginId } from 'app/features/alerting/unified/utils/config';
|
||||
|
||||
import { useAppNotification } from '../../../../../../../core/copy/appNotification';
|
||||
import { Receiver } from '../../../../../../../plugins/datasource/alertmanager/types';
|
||||
@@ -38,21 +38,17 @@ enum OnCallIntegrationStatus {
|
||||
}
|
||||
|
||||
function useOnCallPluginStatus() {
|
||||
const {
|
||||
irmConfig: { onCallPluginId },
|
||||
isIrmConfigLoading,
|
||||
} = useIrmConfig();
|
||||
const {
|
||||
installed: isOnCallEnabled,
|
||||
loading: isPluginBridgeLoading,
|
||||
error: pluginError,
|
||||
} = usePluginBridge(onCallPluginId);
|
||||
} = usePluginBridge(getIrmIfPresentOrOnCallPluginId());
|
||||
|
||||
const {
|
||||
data: onCallFeatures = [],
|
||||
error: onCallFeaturesError,
|
||||
isLoading: isOnCallFeaturesLoading,
|
||||
} = onCallApi(onCallPluginId).endpoints.features.useQuery(undefined, { skip: !isOnCallEnabled });
|
||||
} = onCallApi.endpoints.features.useQuery(undefined, { skip: !isOnCallEnabled });
|
||||
|
||||
const integrationStatus = useMemo((): OnCallIntegrationStatus => {
|
||||
if (!isOnCallEnabled) {
|
||||
@@ -74,22 +70,19 @@ function useOnCallPluginStatus() {
|
||||
isOnCallEnabled,
|
||||
integrationStatus,
|
||||
isAlertingV2IntegrationEnabled,
|
||||
isOnCallStatusLoading: isPluginBridgeLoading || isOnCallFeaturesLoading || isIrmConfigLoading,
|
||||
isOnCallStatusLoading: isPluginBridgeLoading || isOnCallFeaturesLoading,
|
||||
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(onCallPluginId);
|
||||
onCallApi;
|
||||
|
||||
const [validateIntegrationNameQuery, { isFetching: isValidating }] = useLazyValidateIntegrationNameQuery();
|
||||
const [createIntegrationMutation] = useCreateIntegrationMutation();
|
||||
@@ -278,7 +271,7 @@ export function useOnCallIntegration() {
|
||||
extendOnCallReceivers,
|
||||
createOnCallIntegrations,
|
||||
onCallFormValidators,
|
||||
isLoadingOnCallIntegration: isLoadingOnCallIntegrations || isOnCallStatusLoading || isIrmConfigLoading,
|
||||
isLoadingOnCallIntegration: isLoadingOnCallIntegrations || isOnCallStatusLoading,
|
||||
isValidating,
|
||||
hasOnCallError: Boolean(onCallError) || isIntegrationsQueryError,
|
||||
};
|
||||
|
||||
+16
-18
@@ -1,9 +1,6 @@
|
||||
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 { SupportedPlugin } from '../../../types/pluginBridges';
|
||||
import { getIrmIfPresentOrOnCallPluginId, getIsIrmPluginPresent } from '../../../utils/config';
|
||||
import { createBridgeURL } from '../../PluginBridge';
|
||||
|
||||
import { GRAFANA_APP_RECEIVERS_SOURCE_IMAGE } from './types';
|
||||
@@ -16,37 +13,38 @@ export interface ReceiverPluginMetadata {
|
||||
warning?: string;
|
||||
}
|
||||
|
||||
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],
|
||||
});
|
||||
const onCallReceiverICon = GRAFANA_APP_RECEIVERS_SOURCE_IMAGE[getIrmIfPresentOrOnCallPluginId()];
|
||||
const onCallReceiverTitle = 'Grafana OnCall';
|
||||
|
||||
export const onCallReceiverMeta: ReceiverPluginMetadata = {
|
||||
title: onCallReceiverTitle,
|
||||
icon: onCallReceiverICon,
|
||||
};
|
||||
|
||||
export function getOnCallMetadata(
|
||||
onCallIntegrations: OnCallIntegrationDTO[] | undefined | null,
|
||||
receiver: GrafanaManagedReceiverConfig,
|
||||
hasAlertManagerConfigData = true,
|
||||
irmConfig: UseIsIrmConfig
|
||||
hasAlertManagerConfigData = true
|
||||
): ReceiverPluginMetadata {
|
||||
const pluginName = irmConfig.isIrmPluginPresent ? 'IRM' : 'OnCall';
|
||||
const pluginId = irmConfig.onCallPluginId;
|
||||
const pluginName = getIsIrmPluginPresent() ? 'IRM' : 'OnCall';
|
||||
|
||||
if (!hasAlertManagerConfigData) {
|
||||
return onCallReceiverMeta(pluginId);
|
||||
return onCallReceiverMeta;
|
||||
}
|
||||
|
||||
if (!receiver.settings?.url) {
|
||||
return onCallReceiverMeta(pluginId);
|
||||
return onCallReceiverMeta;
|
||||
}
|
||||
|
||||
// oncall status is still loading
|
||||
if (onCallIntegrations === undefined) {
|
||||
return onCallReceiverMeta(pluginId);
|
||||
return onCallReceiverMeta;
|
||||
}
|
||||
|
||||
// indication that onCall is not enabled
|
||||
if (onCallIntegrations == null) {
|
||||
return {
|
||||
...onCallReceiverMeta(pluginId),
|
||||
...onCallReceiverMeta,
|
||||
warning: `Grafana ${pluginName} is not installed or is disabled`,
|
||||
};
|
||||
}
|
||||
@@ -56,10 +54,10 @@ export function getOnCallMetadata(
|
||||
);
|
||||
|
||||
return {
|
||||
...onCallReceiverMeta(pluginId),
|
||||
...onCallReceiverMeta,
|
||||
description: matchingOnCallIntegration?.display_name,
|
||||
externalUrl: matchingOnCallIntegration
|
||||
? createBridgeURL(pluginId, `/integrations/${matchingOnCallIntegration.value}`)
|
||||
? createBridgeURL(getIrmIfPresentOrOnCallPluginId(), `/integrations/${matchingOnCallIntegration.value}`)
|
||||
: undefined,
|
||||
warning: matchingOnCallIntegration ? undefined : `${pluginName} Integration no longer exists`,
|
||||
};
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { type DefaultBodyType, HttpResponse, HttpResponseResolver, PathParams, http } from 'msw';
|
||||
|
||||
import { config } from '@grafana/runtime';
|
||||
import server from '@grafana/test-utils/server';
|
||||
import { mockDataSource, mockFolder } from 'app/features/alerting/unified/mocks';
|
||||
import {
|
||||
@@ -9,7 +10,10 @@ 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 } from 'app/features/alerting/unified/mocks/server/handlers/plugins';
|
||||
import {
|
||||
getDisabledPluginHandler,
|
||||
getPluginMissingHandler,
|
||||
} from 'app/features/alerting/unified/mocks/server/handlers/plugins';
|
||||
import {
|
||||
ALERTING_API_SERVER_BASE_URL,
|
||||
getK8sResponse,
|
||||
@@ -208,6 +212,13 @@ 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) => {
|
||||
// eslint-disable-next-line no-restricted-syntax
|
||||
delete config.apps[pluginId];
|
||||
server.use(getPluginMissingHandler(pluginId));
|
||||
};
|
||||
|
||||
/** Make a plugin respond with `enabled: false`, as if its installed but disabled */
|
||||
export const disablePlugin = (pluginId: SupportedPlugin) => {
|
||||
clearPluginSettingsCache(pluginId);
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
import { HttpResponse, http } from 'msw';
|
||||
|
||||
import { PluginLoadingStrategy, PluginMeta } from '@grafana/data';
|
||||
import { setAppPluginMetas } from '@grafana/runtime/internal';
|
||||
import { type AppPluginMetas } from '@grafana/runtime/unstable';
|
||||
import { config } from '@grafana/runtime';
|
||||
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,10 +11,9 @@ 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) => {
|
||||
const allPlugins: AppPluginMetas = {};
|
||||
plugins.reduce((acc, curr) => {
|
||||
const { id, baseUrl, info, angular } = curr;
|
||||
acc[id] = {
|
||||
plugins.forEach(({ id, baseUrl, info, angular }) => {
|
||||
// eslint-disable-next-line no-restricted-syntax
|
||||
config.apps[id] = {
|
||||
id,
|
||||
path: baseUrl,
|
||||
preload: true,
|
||||
@@ -37,9 +35,7 @@ export const getPluginsHandler = (pluginsArray: PluginMeta[] = plugins) => {
|
||||
},
|
||||
},
|
||||
};
|
||||
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,4 +1,4 @@
|
||||
import { setAppPluginMetas } from '@grafana/runtime/internal';
|
||||
import { config } from '@grafana/runtime';
|
||||
import { RuleWithLocation } from 'app/types/unified-alerting';
|
||||
import {
|
||||
RulerAlertingRuleDTO,
|
||||
@@ -137,7 +137,10 @@ describe('cloneRuleDefinition', () => {
|
||||
|
||||
it('Should remove the origin label when cloning data source plugin-provided rules', () => {
|
||||
// Mock the plugin as installed
|
||||
setAppPluginMetas({ [SupportedPlugin.Slo]: pluginMetaToPluginConfig(pluginMeta[SupportedPlugin.Slo]) });
|
||||
// eslint-disable-next-line no-restricted-syntax
|
||||
config.apps = {
|
||||
[SupportedPlugin.Slo]: pluginMetaToPluginConfig(pluginMeta[SupportedPlugin.Slo]),
|
||||
};
|
||||
|
||||
const rule: RulerAlertingRuleDTO = mockRulerAlertingRule({
|
||||
alert: 'slo-provider-alert',
|
||||
@@ -172,7 +175,10 @@ describe('cloneRuleDefinition', () => {
|
||||
});
|
||||
|
||||
it('Should remove the origin label when cloning Grafana-managed plugin-provided rules', () => {
|
||||
setAppPluginMetas({ [SupportedPlugin.Slo]: pluginMetaToPluginConfig(pluginMeta[SupportedPlugin.Slo]) });
|
||||
// eslint-disable-next-line no-restricted-syntax
|
||||
config.apps = {
|
||||
[SupportedPlugin.Slo]: pluginMetaToPluginConfig(pluginMeta[SupportedPlugin.Slo]),
|
||||
};
|
||||
|
||||
const rule: RulerGrafanaRuleDTO = mockRulerGrafanaRule(
|
||||
{
|
||||
|
||||
@@ -1,6 +1,14 @@
|
||||
import { config } from '@grafana/runtime';
|
||||
|
||||
import { checkEvaluationIntervalGlobalLimit } from './config';
|
||||
import { pluginMeta, pluginMetaToPluginConfig } from '../testSetup/plugins';
|
||||
import { SupportedPlugin } from '../types/pluginBridges';
|
||||
|
||||
import {
|
||||
checkEvaluationIntervalGlobalLimit,
|
||||
getIrmIfPresentOrIncidentPluginId,
|
||||
getIrmIfPresentOrOnCallPluginId,
|
||||
getIsIrmPluginPresent,
|
||||
} from './config';
|
||||
|
||||
describe('checkEvaluationIntervalGlobalLimit', () => {
|
||||
it('should NOT exceed limit if evaluate every is not valid duration', () => {
|
||||
@@ -51,3 +59,54 @@ describe('checkEvaluationIntervalGlobalLimit', () => {
|
||||
expect(exceedsLimit).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getIsIrmPluginPresent', () => {
|
||||
it('should return true when IRM plugin is present in config.apps', () => {
|
||||
// eslint-disable-next-line no-restricted-syntax
|
||||
config.apps = { [SupportedPlugin.Irm]: pluginMetaToPluginConfig(pluginMeta[SupportedPlugin.Irm]) };
|
||||
expect(getIsIrmPluginPresent()).toBe(true);
|
||||
});
|
||||
|
||||
it('should return false when IRM plugin is not present in config.apps', () => {
|
||||
// eslint-disable-next-line no-restricted-syntax
|
||||
config.apps = {
|
||||
[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', () => {
|
||||
// eslint-disable-next-line no-restricted-syntax
|
||||
config.apps = { [SupportedPlugin.Irm]: pluginMetaToPluginConfig(pluginMeta[SupportedPlugin.Irm]) };
|
||||
expect(getIrmIfPresentOrIncidentPluginId()).toBe(SupportedPlugin.Irm);
|
||||
});
|
||||
|
||||
it('should return Incident plugin ID when IRM plugin is not present', () => {
|
||||
// eslint-disable-next-line no-restricted-syntax
|
||||
config.apps = {
|
||||
[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', () => {
|
||||
// eslint-disable-next-line no-restricted-syntax
|
||||
config.apps = { [SupportedPlugin.Irm]: pluginMetaToPluginConfig(pluginMeta[SupportedPlugin.Irm]) };
|
||||
expect(getIrmIfPresentOrOnCallPluginId()).toBe(SupportedPlugin.Irm);
|
||||
});
|
||||
|
||||
it('should return OnCall plugin ID when IRM plugin is not present', () => {
|
||||
// eslint-disable-next-line no-restricted-syntax
|
||||
config.apps = {
|
||||
[SupportedPlugin.OnCall]: pluginMetaToPluginConfig(pluginMeta[SupportedPlugin.OnCall]),
|
||||
[SupportedPlugin.Incident]: pluginMetaToPluginConfig(pluginMeta[SupportedPlugin.Incident]),
|
||||
};
|
||||
expect(getIrmIfPresentOrOnCallPluginId()).toBe(SupportedPlugin.OnCall);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import { DataSourceInstanceSettings, DataSourceJsonData } from '@grafana/data';
|
||||
import { config } from '@grafana/runtime';
|
||||
|
||||
import { SupportedPlugin } from '../types/pluginBridges';
|
||||
|
||||
import { isValidPrometheusDuration, safeParsePrometheusDuration } from './time';
|
||||
|
||||
export function getAllDataSources(): Array<DataSourceInstanceSettings<DataSourceJsonData>> {
|
||||
@@ -26,3 +28,16 @@ export function checkEvaluationIntervalGlobalLimit(alertGroupEvaluateEvery?: str
|
||||
|
||||
return { globalLimit: evaluateEveryGlobalLimitMs, exceedsLimit };
|
||||
}
|
||||
|
||||
export function getIsIrmPluginPresent() {
|
||||
// eslint-disable-next-line no-restricted-syntax
|
||||
return SupportedPlugin.Irm in config.apps;
|
||||
}
|
||||
|
||||
export function getIrmIfPresentOrIncidentPluginId() {
|
||||
return getIsIrmPluginPresent() ? SupportedPlugin.Irm : SupportedPlugin.Incident;
|
||||
}
|
||||
|
||||
export function getIrmIfPresentOrOnCallPluginId() {
|
||||
return getIsIrmPluginPresent() ? SupportedPlugin.Irm : SupportedPlugin.OnCall;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { PluginLoadingStrategy } from '@grafana/data';
|
||||
import { setAppPluginMetas } from '@grafana/runtime/internal';
|
||||
import { config } from '@grafana/runtime';
|
||||
import { RuleGroupIdentifier } from 'app/types/unified-alerting';
|
||||
|
||||
import {
|
||||
@@ -42,7 +42,8 @@ describe('getRuleOrigin', () => {
|
||||
});
|
||||
|
||||
it('returns pluginId when origin label matches expected format and plugin is installed', () => {
|
||||
setAppPluginMetas({
|
||||
// eslint-disable-next-line no-restricted-syntax
|
||||
config.apps = {
|
||||
installed_plugin: {
|
||||
id: 'installed_plugin',
|
||||
version: '',
|
||||
@@ -65,7 +66,7 @@ describe('getRuleOrigin', () => {
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
};
|
||||
const rule = mockPromAlertingRule({
|
||||
labels: { [GRAFANA_ORIGIN_LABEL]: 'plugin/installed_plugin' },
|
||||
});
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { capitalize } from 'lodash';
|
||||
|
||||
import { AlertState } from '@grafana/data';
|
||||
import { getAppPluginMeta } from '@grafana/runtime/unstable';
|
||||
import { config } from '@grafana/runtime';
|
||||
import {
|
||||
Alert,
|
||||
AlertingRule,
|
||||
@@ -273,7 +273,8 @@ export function getRulePluginOrigin(rule?: Rule | PromRuleDTO | RulerRuleDTO): R
|
||||
}
|
||||
|
||||
function isPluginInstalled(pluginId: string) {
|
||||
return Boolean(getAppPluginMeta(pluginId));
|
||||
// eslint-disable-next-line no-restricted-syntax
|
||||
return Boolean(config.apps[pluginId]);
|
||||
}
|
||||
|
||||
export function isPluginProvidedGroup(group: RulerRuleGroupDTO): boolean {
|
||||
|
||||
+3
-3
@@ -5,7 +5,7 @@ import { GrafanaTheme2 } from '@grafana/data';
|
||||
import { Trans, t } from '@grafana/i18n';
|
||||
import { config } from '@grafana/runtime';
|
||||
import { UserStorage } from '@grafana/runtime/internal';
|
||||
import { getAppPluginMeta } from '@grafana/runtime/unstable';
|
||||
import { useAppPluginMeta } from '@grafana/runtime/unstable';
|
||||
import { Alert, LinkButton, useStyles2 } from '@grafana/ui';
|
||||
import { contextSrv } from 'app/core/services/context_srv';
|
||||
|
||||
@@ -28,9 +28,9 @@ export function AdvisorRedirectNotice() {
|
||||
const styles = useStyles2(getStyles);
|
||||
const hasAdminRights = contextSrv.hasRole('Admin') || contextSrv.isGrafanaAdmin;
|
||||
const [showNotice, setShowNotice] = useState(false);
|
||||
const { app } = useAppPluginMeta('grafana-advisor-app');
|
||||
|
||||
const canUseAdvisor =
|
||||
hasAdminRights && config.featureToggles.grafanaAdvisor && !!getAppPluginMeta('grafana-advisor-app');
|
||||
const canUseAdvisor = hasAdminRights && config.featureToggles.grafanaAdvisor && !!app;
|
||||
|
||||
useEffect(() => {
|
||||
if (canUseAdvisor) {
|
||||
|
||||
@@ -17,9 +17,9 @@ jest.mock('@grafana/llm', () => ({
|
||||
},
|
||||
}));
|
||||
|
||||
jest.mock('@grafana/runtime', () => ({
|
||||
...jest.requireActual('@grafana/runtime'),
|
||||
getAppPluginMeta: () => ({}),
|
||||
jest.mock('@grafana/runtime/unstable', () => ({
|
||||
...jest.requireActual('@grafana/runtime/unstable'),
|
||||
getAppPluginMeta: () => Promise.resolve({}),
|
||||
}));
|
||||
|
||||
describe('getDashboardChanges', () => {
|
||||
|
||||
@@ -70,7 +70,8 @@ let llmHealthCheck: Promise<boolean> | undefined;
|
||||
* @returns true if the LLM plugin is enabled.
|
||||
*/
|
||||
export async function isLLMPluginEnabled(): Promise<boolean> {
|
||||
if (!getAppPluginMeta('grafana-llm-app')) {
|
||||
const app = await getAppPluginMeta('grafana-llm-app');
|
||||
if (!app) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { incidentsApi } from 'app/features/alerting/unified/api/incidentsApi';
|
||||
import { usePluginBridge } from 'app/features/alerting/unified/hooks/usePluginBridge';
|
||||
|
||||
import { useIrmConfig } from '../irmHooks';
|
||||
import { getIrmIfPresentOrIncidentPluginId } from 'app/features/alerting/unified/utils/config';
|
||||
|
||||
interface IncidentsPluginConfig {
|
||||
isInstalled: boolean;
|
||||
@@ -11,18 +10,16 @@ interface IncidentsPluginConfig {
|
||||
}
|
||||
|
||||
export function useGetIncidentPluginConfig(): IncidentsPluginConfig {
|
||||
const {
|
||||
irmConfig: { incidentPluginId },
|
||||
isIrmConfigLoading,
|
||||
} = useIrmConfig();
|
||||
const { installed: incidentPluginInstalled, loading: loadingPluginSettings } = usePluginBridge(incidentPluginId);
|
||||
const { installed: incidentPluginInstalled, loading: loadingPluginSettings } = usePluginBridge(
|
||||
getIrmIfPresentOrIncidentPluginId()
|
||||
);
|
||||
const { data: incidentsConfig, isLoading: loadingPluginConfig } =
|
||||
incidentsApi(incidentPluginId).endpoints.getIncidentsPluginConfig.useQuery();
|
||||
incidentsApi.endpoints.getIncidentsPluginConfig.useQuery();
|
||||
|
||||
return {
|
||||
isInstalled: incidentPluginInstalled ?? false,
|
||||
isChatOpsInstalled: incidentsConfig?.isChatOpsInstalled ?? false,
|
||||
isIncidentCreated: incidentsConfig?.isIncidentCreated ?? false,
|
||||
isLoading: loadingPluginSettings || loadingPluginConfig || isIrmConfigLoading,
|
||||
isLoading: loadingPluginSettings || loadingPluginConfig,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,75 +0,0 @@
|
||||
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);
|
||||
});
|
||||
});
|
||||
});
|
||||
+25
-59
@@ -1,12 +1,14 @@
|
||||
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 { SupportedPlugin } from 'app/features/alerting/unified/types/pluginBridges';
|
||||
import {
|
||||
getIrmIfPresentOrIncidentPluginId,
|
||||
getIrmIfPresentOrOnCallPluginId,
|
||||
getIsIrmPluginPresent,
|
||||
} from 'app/features/alerting/unified/utils/config';
|
||||
import { GRAFANA_RULES_SOURCE_NAME } from 'app/features/alerting/unified/utils/datasource';
|
||||
import { RelativeUrl, createRelativeUrl } from 'app/features/alerting/unified/utils/url';
|
||||
|
||||
@@ -109,39 +111,6 @@ 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 },
|
||||
@@ -150,10 +119,6 @@ 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}`, {
|
||||
@@ -186,7 +151,7 @@ export function useGetEssentialsConfiguration(): EssentialsConfigurationData {
|
||||
},
|
||||
];
|
||||
|
||||
if (isIrmPluginPresent) {
|
||||
if (!getIsIrmPluginPresent()) {
|
||||
steps = [
|
||||
...steps,
|
||||
{
|
||||
@@ -301,8 +266,8 @@ export function useGetEssentialsConfiguration(): EssentialsConfigurationData {
|
||||
},
|
||||
{
|
||||
title: t('gops.use-get-essentials-configuration.essential-content.title.respond', 'Respond'),
|
||||
description: isIrmPluginPresent ? 'Configure IRM' : 'Configure OnCall and Incident',
|
||||
steps: isIrmPluginPresent
|
||||
description: getIsIrmPluginPresent() ? 'Configure IRM' : 'Configure OnCall and Incident',
|
||||
steps: getIsIrmPluginPresent()
|
||||
? [
|
||||
{
|
||||
title: t(
|
||||
@@ -336,11 +301,11 @@ export function useGetEssentialsConfiguration(): EssentialsConfigurationData {
|
||||
button: {
|
||||
type: 'openLink',
|
||||
urlLink: {
|
||||
url: `/a/${incidentPluginId}/integrations/apps/grate.irm.slack`,
|
||||
url: `/a/${getIrmIfPresentOrIncidentPluginId()}/integrations/apps/grate.irm.slack`,
|
||||
},
|
||||
label: t('gops.use-get-essentials-configuration.essential-content.label.connect', 'Connect'),
|
||||
urlLinkOnDone: {
|
||||
url: `/a/${incidentPluginId}/integrations/apps/grate.irm.slack`,
|
||||
url: `/a/${getIrmIfPresentOrIncidentPluginId()}/integrations/apps/grate.irm.slack`,
|
||||
},
|
||||
labelOnDone: 'View',
|
||||
},
|
||||
@@ -358,11 +323,11 @@ export function useGetEssentialsConfiguration(): EssentialsConfigurationData {
|
||||
button: {
|
||||
type: 'openLink',
|
||||
urlLink: {
|
||||
url: `/a/${onCallPluginId}/integrations/`,
|
||||
url: `/a/${getIrmIfPresentOrOnCallPluginId()}/integrations/`,
|
||||
},
|
||||
label: t('gops.use-get-essentials-configuration.essential-content.label.add', 'Add'),
|
||||
urlLinkOnDone: {
|
||||
url: `/a/${onCallPluginId}/integrations/`,
|
||||
url: `/a/${getIrmIfPresentOrOnCallPluginId()}/integrations/`,
|
||||
},
|
||||
labelOnDone: 'View',
|
||||
},
|
||||
@@ -382,11 +347,11 @@ export function useGetEssentialsConfiguration(): EssentialsConfigurationData {
|
||||
button: {
|
||||
type: 'openLink',
|
||||
urlLink: {
|
||||
url: `/a/${incidentPluginId}/walkthrough/generate-key`,
|
||||
url: `/a/${getIrmIfPresentOrIncidentPluginId()}/walkthrough/generate-key`,
|
||||
},
|
||||
label: t('gops.use-get-essentials-configuration.essential-content.label.initialize', 'Initialize'),
|
||||
urlLinkOnDone: {
|
||||
url: `/a/${incidentPluginId}`,
|
||||
url: `/a/${getIrmIfPresentOrIncidentPluginId()}`,
|
||||
},
|
||||
labelOnDone: 'View',
|
||||
},
|
||||
@@ -404,12 +369,12 @@ export function useGetEssentialsConfiguration(): EssentialsConfigurationData {
|
||||
button: {
|
||||
type: 'openLink',
|
||||
urlLink: {
|
||||
url: `/a/${onCallPluginId}/settings`,
|
||||
url: `/a/${getIrmIfPresentOrOnCallPluginId()}/settings`,
|
||||
queryParams: { tab: 'ChatOps', chatOpsTab: 'Slack' },
|
||||
},
|
||||
label: t('gops.use-get-essentials-configuration.essential-content.label.connect', 'Connect'),
|
||||
urlLinkOnDone: {
|
||||
url: `/a/${onCallPluginId}/settings`,
|
||||
url: `/a/${getIrmIfPresentOrOnCallPluginId()}/settings`,
|
||||
queryParams: { tab: 'ChatOps' },
|
||||
},
|
||||
labelOnDone: 'View',
|
||||
@@ -426,11 +391,11 @@ export function useGetEssentialsConfiguration(): EssentialsConfigurationData {
|
||||
button: {
|
||||
type: 'openLink',
|
||||
urlLink: {
|
||||
url: `/a/${incidentPluginId}/integrations/grate.slack`,
|
||||
url: `/a/${getIrmIfPresentOrIncidentPluginId()}/integrations/grate.slack`,
|
||||
},
|
||||
label: t('gops.use-get-essentials-configuration.essential-content.label.connect', 'Connect'),
|
||||
urlLinkOnDone: {
|
||||
url: `/a/${incidentPluginId}/integrations`,
|
||||
url: `/a/${getIrmIfPresentOrIncidentPluginId()}/integrations`,
|
||||
},
|
||||
},
|
||||
done: isChatOpsInstalled,
|
||||
@@ -447,11 +412,11 @@ export function useGetEssentialsConfiguration(): EssentialsConfigurationData {
|
||||
button: {
|
||||
type: 'openLink',
|
||||
urlLink: {
|
||||
url: `/a/${onCallPluginId}/integrations/`,
|
||||
url: `/a/${getIrmIfPresentOrOnCallPluginId()}/integrations/`,
|
||||
},
|
||||
label: t('gops.use-get-essentials-configuration.essential-content.label.add', 'Add'),
|
||||
urlLinkOnDone: {
|
||||
url: `/a/${onCallPluginId}/integrations/`,
|
||||
url: `/a/${getIrmIfPresentOrOnCallPluginId()}/integrations/`,
|
||||
},
|
||||
labelOnDone: 'View',
|
||||
},
|
||||
@@ -467,7 +432,7 @@ export function useGetEssentialsConfiguration(): EssentialsConfigurationData {
|
||||
description: '',
|
||||
steps: [
|
||||
{
|
||||
title: isIrmPluginPresent ? 'Send test alert' : 'Send OnCall demo alert via Alerting integration',
|
||||
title: getIsIrmPluginPresent() ? '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',
|
||||
@@ -476,7 +441,8 @@ export function useGetEssentialsConfiguration(): EssentialsConfigurationData {
|
||||
'Select integration'
|
||||
),
|
||||
options: onCallOptions,
|
||||
onClickOption: (value) => onIntegrationClick(value, `/a/${onCallPluginId}/integrations/`),
|
||||
onClickOption: (value) =>
|
||||
onIntegrationClick(value, `/a/${getIrmIfPresentOrOnCallPluginId()}/integrations/`),
|
||||
stepNotAvailableText: 'No integrations available',
|
||||
},
|
||||
},
|
||||
@@ -492,7 +458,7 @@ export function useGetEssentialsConfiguration(): EssentialsConfigurationData {
|
||||
button: {
|
||||
type: 'openLink',
|
||||
urlLink: {
|
||||
url: `/a/${incidentPluginId}`,
|
||||
url: `/a/${getIrmIfPresentOrIncidentPluginId()}`,
|
||||
queryParams: { declare: 'new', drill: '1' },
|
||||
},
|
||||
label: t('gops.use-get-essentials-configuration.essential-content.label.start-drill', 'Start drill'),
|
||||
@@ -513,7 +479,7 @@ export function useGetEssentialsConfiguration(): EssentialsConfigurationData {
|
||||
},
|
||||
{ stepsDone: 0, totalStepsToDo: 0 }
|
||||
);
|
||||
return { essentialContent, stepsDone, totalStepsToDo, isLoading: isLoading || isIrmConfigLoading };
|
||||
return { essentialContent, stepsDone, totalStepsToDo, isLoading };
|
||||
}
|
||||
interface UseConfigurationProps {
|
||||
dataSourceConfigurationData: DataSourceConfigurationData;
|
||||
@@ -1,44 +1,29 @@
|
||||
import { onCallApi } from 'app/features/alerting/unified/api/onCallApi';
|
||||
import { usePluginBridge } from 'app/features/alerting/unified/hooks/usePluginBridge';
|
||||
|
||||
import { useIrmConfig } from '../irmHooks';
|
||||
import { getIrmIfPresentOrOnCallPluginId } from 'app/features/alerting/unified/utils/config';
|
||||
|
||||
export function useGetOnCallIntegrations() {
|
||||
const {
|
||||
irmConfig: { onCallPluginId },
|
||||
isIrmConfigLoading,
|
||||
} = useIrmConfig();
|
||||
const { installed: onCallPluginInstalled } = usePluginBridge(onCallPluginId);
|
||||
const { installed: onCallPluginInstalled } = usePluginBridge(getIrmIfPresentOrOnCallPluginId());
|
||||
|
||||
const { data: onCallIntegrations } = onCallApi(onCallPluginId).endpoints.grafanaOnCallIntegrations.useQuery(
|
||||
undefined,
|
||||
{
|
||||
skip: !onCallPluginInstalled || isIrmConfigLoading,
|
||||
refetchOnFocus: true,
|
||||
refetchOnReconnect: true,
|
||||
refetchOnMountOrArgChange: true,
|
||||
}
|
||||
);
|
||||
const { data: onCallIntegrations } = onCallApi.endpoints.grafanaOnCallIntegrations.useQuery(undefined, {
|
||||
skip: !onCallPluginInstalled,
|
||||
refetchOnFocus: true,
|
||||
refetchOnReconnect: true,
|
||||
refetchOnMountOrArgChange: true,
|
||||
});
|
||||
|
||||
return onCallIntegrations ?? [];
|
||||
}
|
||||
|
||||
function useGetOnCallConfigurationChecks() {
|
||||
const {
|
||||
irmConfig: { onCallPluginId },
|
||||
isIrmConfigLoading,
|
||||
} = useIrmConfig();
|
||||
const { data: onCallConfigChecks, isLoading } = onCallApi(onCallPluginId).endpoints.onCallConfigChecks.useQuery(
|
||||
undefined,
|
||||
{
|
||||
refetchOnFocus: true,
|
||||
refetchOnReconnect: true,
|
||||
refetchOnMountOrArgChange: true,
|
||||
}
|
||||
);
|
||||
const { data: onCallConfigChecks, isLoading } = onCallApi.endpoints.onCallConfigChecks.useQuery(undefined, {
|
||||
refetchOnFocus: true,
|
||||
refetchOnReconnect: true,
|
||||
refetchOnMountOrArgChange: true,
|
||||
});
|
||||
|
||||
return {
|
||||
isLoading: isLoading || isIrmConfigLoading,
|
||||
isLoading,
|
||||
onCallConfigChecks: onCallConfigChecks ?? { is_chatops_connected: false, is_integration_chatops_connected: false },
|
||||
};
|
||||
}
|
||||
|
||||
@@ -448,7 +448,7 @@ describe('AddedComponentsRegistry', () => {
|
||||
};
|
||||
|
||||
// Make sure that the meta-info is empty
|
||||
const meta = getAppPluginMeta(pluginId);
|
||||
const meta = await getAppPluginMeta(pluginId);
|
||||
expect(meta).toBeDefined();
|
||||
|
||||
const app = { ...meta!, extensions: { ...meta!.extensions, addedComponents: [] } };
|
||||
@@ -501,7 +501,7 @@ describe('AddedComponentsRegistry', () => {
|
||||
};
|
||||
|
||||
// Make sure that the meta-info is empty
|
||||
const meta = getAppPluginMeta(pluginId);
|
||||
const meta = await getAppPluginMeta(pluginId);
|
||||
expect(meta).toBeDefined();
|
||||
|
||||
const app = { ...meta!, extensions: { ...meta!.extensions, addedComponents: [] } };
|
||||
@@ -531,7 +531,7 @@ describe('AddedComponentsRegistry', () => {
|
||||
};
|
||||
|
||||
// Make sure that the meta-info is empty
|
||||
const meta = getAppPluginMeta(pluginId);
|
||||
const meta = await getAppPluginMeta(pluginId);
|
||||
expect(meta).toBeDefined();
|
||||
|
||||
const app = { ...meta!, extensions: { ...meta!.extensions, addedComponents: [componentConfig] } };
|
||||
|
||||
@@ -30,10 +30,10 @@ export class AddedComponentsRegistry extends Registry<
|
||||
super(options);
|
||||
}
|
||||
|
||||
mapToRegistry(
|
||||
async mapToRegistry(
|
||||
registry: RegistryType<AddedComponentRegistryItem[]>,
|
||||
item: PluginExtensionConfigs<PluginExtensionAddedComponentConfig>
|
||||
): RegistryType<AddedComponentRegistryItem[]> {
|
||||
): Promise<RegistryType<AddedComponentRegistryItem[]>> {
|
||||
const { pluginId, configs } = item;
|
||||
|
||||
for (const config of configs) {
|
||||
@@ -51,7 +51,7 @@ export class AddedComponentsRegistry extends Registry<
|
||||
if (
|
||||
pluginId !== 'grafana' &&
|
||||
isGrafanaDevMode() &&
|
||||
isAddedComponentMetaInfoMissing(pluginId, config, configLog)
|
||||
(await isAddedComponentMetaInfoMissing(pluginId, config, configLog))
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -640,7 +640,7 @@ describe('addedFunctionsRegistry', () => {
|
||||
};
|
||||
|
||||
// Make sure that the meta-info is empty
|
||||
const meta = getAppPluginMeta(pluginId);
|
||||
const meta = await getAppPluginMeta(pluginId);
|
||||
expect(meta).toBeDefined();
|
||||
|
||||
const app = { ...meta!, extensions: { ...meta!.extensions, addedFunctions: [] } };
|
||||
@@ -693,7 +693,7 @@ describe('addedFunctionsRegistry', () => {
|
||||
};
|
||||
|
||||
// Make sure that the meta-info is empty
|
||||
const meta = getAppPluginMeta(pluginId);
|
||||
const meta = await getAppPluginMeta(pluginId);
|
||||
expect(meta).toBeDefined();
|
||||
|
||||
const app = { ...meta!, extensions: { ...meta!.extensions, addedFunctions: [] } };
|
||||
@@ -723,7 +723,7 @@ describe('addedFunctionsRegistry', () => {
|
||||
};
|
||||
|
||||
// Make sure that the meta-info is empty
|
||||
const meta = getAppPluginMeta(pluginId);
|
||||
const meta = await getAppPluginMeta(pluginId);
|
||||
expect(meta).toBeDefined();
|
||||
|
||||
const app = { ...meta!, extensions: { ...meta!.extensions, addedFunctions: [fnConfig] } };
|
||||
|
||||
@@ -28,11 +28,12 @@ export class AddedFunctionsRegistry extends Registry<AddedFunctionsRegistryItem[
|
||||
super(options);
|
||||
}
|
||||
|
||||
mapToRegistry(
|
||||
async mapToRegistry(
|
||||
registry: RegistryType<AddedFunctionsRegistryItem[]>,
|
||||
item: PluginExtensionConfigs<PluginExtensionAddedFunctionConfig>
|
||||
): RegistryType<AddedFunctionsRegistryItem[]> {
|
||||
): Promise<RegistryType<AddedFunctionsRegistryItem[]>> {
|
||||
const { pluginId, configs } = item;
|
||||
|
||||
for (const config of configs) {
|
||||
const configLog = this.logger.child({
|
||||
title: config.title,
|
||||
@@ -49,7 +50,11 @@ export class AddedFunctionsRegistry extends Registry<AddedFunctionsRegistryItem[
|
||||
continue;
|
||||
}
|
||||
|
||||
if (pluginId !== 'grafana' && isGrafanaDevMode() && isAddedFunctionMetaInfoMissing(pluginId, config, configLog)) {
|
||||
if (
|
||||
pluginId !== 'grafana' &&
|
||||
isGrafanaDevMode() &&
|
||||
(await isAddedFunctionMetaInfoMissing(pluginId, config, configLog))
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
@@ -624,7 +624,7 @@ describe('AddedLinksRegistry', () => {
|
||||
};
|
||||
|
||||
// Make sure that the meta-info is empty
|
||||
const meta = getAppPluginMeta(pluginId);
|
||||
const meta = await getAppPluginMeta(pluginId);
|
||||
expect(meta).toBeDefined();
|
||||
|
||||
const app = { ...meta!, extensions: { ...meta!.extensions, addedLinks: [] } };
|
||||
@@ -679,7 +679,7 @@ describe('AddedLinksRegistry', () => {
|
||||
};
|
||||
|
||||
// Make sure that the meta-info is empty
|
||||
const meta = getAppPluginMeta(pluginId);
|
||||
const meta = await getAppPluginMeta(pluginId);
|
||||
expect(meta).toBeDefined();
|
||||
|
||||
const app = { ...meta!, extensions: { ...meta!.extensions, addedLinks: [] } };
|
||||
@@ -710,7 +710,7 @@ describe('AddedLinksRegistry', () => {
|
||||
};
|
||||
|
||||
// Make sure that the meta-info is empty
|
||||
const meta = getAppPluginMeta(pluginId);
|
||||
const meta = await getAppPluginMeta(pluginId);
|
||||
expect(meta).toBeDefined();
|
||||
|
||||
const app = { ...meta!, extensions: { ...meta!.extensions, addedLinks: [linkConfig] } };
|
||||
|
||||
@@ -34,10 +34,10 @@ export class AddedLinksRegistry extends Registry<AddedLinkRegistryItem[], Plugin
|
||||
super(options);
|
||||
}
|
||||
|
||||
mapToRegistry(
|
||||
async mapToRegistry(
|
||||
registry: RegistryType<AddedLinkRegistryItem[]>,
|
||||
item: PluginExtensionConfigs<PluginExtensionAddedLinkConfig>
|
||||
): RegistryType<AddedLinkRegistryItem[]> {
|
||||
): Promise<RegistryType<AddedLinkRegistryItem[]>> {
|
||||
const { pluginId, configs } = item;
|
||||
|
||||
for (const config of configs) {
|
||||
@@ -66,7 +66,11 @@ export class AddedLinksRegistry extends Registry<AddedLinkRegistryItem[], Plugin
|
||||
continue;
|
||||
}
|
||||
|
||||
if (pluginId !== 'grafana' && isGrafanaDevMode() && isAddedLinkMetaInfoMissing(pluginId, config, configLog)) {
|
||||
if (
|
||||
pluginId !== 'grafana' &&
|
||||
isGrafanaDevMode() &&
|
||||
(await isAddedLinkMetaInfoMissing(pluginId, config, configLog))
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
@@ -421,7 +421,7 @@ describe('ExposedComponentsRegistry', () => {
|
||||
};
|
||||
|
||||
// Make sure that the meta-info is empty
|
||||
const meta = getAppPluginMeta(pluginId);
|
||||
const meta = await getAppPluginMeta(pluginId);
|
||||
expect(meta).toBeDefined();
|
||||
|
||||
const app = { ...meta!, extensions: { ...meta!.extensions, exposedComponents: [] } };
|
||||
@@ -474,7 +474,7 @@ describe('ExposedComponentsRegistry', () => {
|
||||
};
|
||||
|
||||
// Make sure that the meta-info is empty
|
||||
const meta = getAppPluginMeta(pluginId);
|
||||
const meta = await getAppPluginMeta(pluginId);
|
||||
expect(meta).toBeDefined();
|
||||
|
||||
const app = { ...meta!, extensions: { ...meta!.extensions, exposedComponents: [] } };
|
||||
@@ -503,7 +503,7 @@ describe('ExposedComponentsRegistry', () => {
|
||||
component: () => React.createElement('div', null, 'Hello World1'),
|
||||
};
|
||||
|
||||
const meta = getAppPluginMeta(pluginId);
|
||||
const meta = await getAppPluginMeta(pluginId);
|
||||
expect(meta).toBeDefined();
|
||||
|
||||
const app = { ...meta!, extensions: { ...meta!.extensions, exposedComponents: [componentConfig] } };
|
||||
|
||||
@@ -30,10 +30,10 @@ export class ExposedComponentsRegistry extends Registry<
|
||||
super(options);
|
||||
}
|
||||
|
||||
mapToRegistry(
|
||||
async mapToRegistry(
|
||||
registry: RegistryType<ExposedComponentRegistryItem>,
|
||||
{ pluginId, configs }: PluginExtensionConfigs<PluginExtensionExposedComponentConfig>
|
||||
): RegistryType<ExposedComponentRegistryItem> {
|
||||
): Promise<RegistryType<ExposedComponentRegistryItem>> {
|
||||
if (!configs) {
|
||||
return registry;
|
||||
}
|
||||
@@ -65,7 +65,7 @@ export class ExposedComponentsRegistry extends Registry<
|
||||
if (
|
||||
pluginId !== 'grafana' &&
|
||||
isGrafanaDevMode() &&
|
||||
isExposedComponentMetaInfoMissing(pluginId, config, pointIdLog)
|
||||
(await isExposedComponentMetaInfoMissing(pluginId, config, pointIdLog))
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -1,4 +1,13 @@
|
||||
import { Observable, ReplaySubject, Subject, distinctUntilChanged, firstValueFrom, map, scan, startWith } from 'rxjs';
|
||||
import {
|
||||
Observable,
|
||||
ReplaySubject,
|
||||
Subject,
|
||||
distinctUntilChanged,
|
||||
firstValueFrom,
|
||||
map,
|
||||
mergeScan,
|
||||
startWith,
|
||||
} from 'rxjs';
|
||||
|
||||
import { ExtensionsLog, log } from '../logs/log';
|
||||
import { deepFreeze } from '../utils';
|
||||
@@ -44,7 +53,7 @@ export abstract class Registry<TRegistryValue extends object | unknown[] | Recor
|
||||
this.registrySubject = new ReplaySubject<RegistryType<TRegistryValue>>(1);
|
||||
this.resultSubject
|
||||
.pipe(
|
||||
scan(this.mapToRegistry.bind(this), options.initialState ?? {}),
|
||||
mergeScan(this.mapToRegistry.bind(this), options.initialState ?? {}),
|
||||
// Emit an empty registry to start the stream (it is only going to do it once during construction, and then just passes down the values)
|
||||
startWith(options.initialState ?? {})
|
||||
)
|
||||
@@ -55,7 +64,7 @@ export abstract class Registry<TRegistryValue extends object | unknown[] | Recor
|
||||
abstract mapToRegistry(
|
||||
registry: RegistryType<TRegistryValue>,
|
||||
item: PluginExtensionConfigs<TMapType>
|
||||
): RegistryType<TRegistryValue>;
|
||||
): Promise<RegistryType<TRegistryValue>>;
|
||||
|
||||
register(result: PluginExtensionConfigs<TMapType>): void {
|
||||
if (this.isReadOnly) {
|
||||
|
||||
@@ -1,27 +1,12 @@
|
||||
import { useAsync } from 'react-use';
|
||||
|
||||
import { AppPluginConfig } from '@grafana/data';
|
||||
import { useAppPluginMetas } from '@grafana/runtime/unstable';
|
||||
import { PreloadAppPluginsPredicate, preloadPluginsWithPredicate } from '../pluginPreloader';
|
||||
|
||||
import { preloadPlugins } from '../pluginPreloader';
|
||||
export function useLoadAppPlugins(extensionId: string, predicate: PreloadAppPluginsPredicate): { isLoading: boolean } {
|
||||
const { loading: isLoading } = useAsync(
|
||||
() => preloadPluginsWithPredicate(extensionId, predicate),
|
||||
[extensionId, predicate]
|
||||
);
|
||||
|
||||
export type UseLoadAppPluginsPredicate = (apps: AppPluginConfig[], filterById: string) => string[];
|
||||
|
||||
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 () => {
|
||||
if (!filtered.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
await preloadPlugins(filtered);
|
||||
}, [filtered]);
|
||||
|
||||
return { isLoading: isLoading || isAppPluginMetasLoading || isFilteredLoading };
|
||||
return { isLoading: isLoading };
|
||||
}
|
||||
|
||||
@@ -500,7 +500,7 @@ describe('usePluginComponents()', () => {
|
||||
});
|
||||
|
||||
// It can happen that core Grafana plugins (e.g. traces) reuse core components which implement extension points.
|
||||
it('should not validate the extension point meta-info for core plugins', () => {
|
||||
it('should not validate the extension point meta-info for core plugins', async () => {
|
||||
jest.mocked(isGrafanaDevMode).mockReturnValue(true);
|
||||
|
||||
const componentConfig = {
|
||||
@@ -511,7 +511,7 @@ describe('usePluginComponents()', () => {
|
||||
};
|
||||
|
||||
// The `AddedComponentsRegistry` is validating if the link is registered in the plugin metadata.
|
||||
const meta = getAppPluginMeta(pluginId);
|
||||
const meta = await getAppPluginMeta(pluginId);
|
||||
expect(meta).toBeDefined();
|
||||
|
||||
const app = { ...meta!, extensions: { ...meta!.extensions, addedComponents: [componentConfig] } };
|
||||
|
||||
@@ -321,7 +321,7 @@ describe('usePluginFunctions()', () => {
|
||||
});
|
||||
|
||||
// It can happen that core Grafana plugins (e.g. traces) reuse core components which implement extension points.
|
||||
it('should not validate the extension point meta-info for core plugins', () => {
|
||||
it('should not validate the extension point meta-info for core plugins', async () => {
|
||||
jest.mocked(isGrafanaDevMode).mockReturnValue(true);
|
||||
|
||||
const functionConfig = {
|
||||
@@ -332,7 +332,7 @@ describe('usePluginFunctions()', () => {
|
||||
};
|
||||
|
||||
// The `AddedFunctionsRegistry` is validating if the function is registered in the plugin metadata.
|
||||
const meta = getAppPluginMeta(pluginId);
|
||||
const meta = await getAppPluginMeta(pluginId);
|
||||
expect(meta).toBeDefined();
|
||||
|
||||
const app = { ...meta!, extensions: { ...meta!.extensions, addedFunctions: [functionConfig] } };
|
||||
|
||||
@@ -261,7 +261,7 @@ describe('usePluginLinks()', () => {
|
||||
});
|
||||
|
||||
// It can happen that core Grafana plugins (e.g. traces) reuse core components which implement extension points.
|
||||
it('should not validate the extension point meta-info for core plugins', () => {
|
||||
it('should not validate the extension point meta-info for core plugins', async () => {
|
||||
jest.mocked(isGrafanaDevMode).mockReturnValue(true);
|
||||
|
||||
const linkConfig = {
|
||||
@@ -272,7 +272,7 @@ describe('usePluginLinks()', () => {
|
||||
};
|
||||
|
||||
// The `AddedLinksRegistry` is validating if the link is registered in the plugin metadata (config.apps).
|
||||
const meta = getAppPluginMeta(pluginId);
|
||||
const meta = await getAppPluginMeta(pluginId);
|
||||
expect(meta).toBeDefined();
|
||||
|
||||
const app = { ...meta!, extensions: { ...meta!.extensions, addedLinks: [linkConfig] } };
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -14,11 +14,9 @@ import {
|
||||
PanelMenuItem,
|
||||
PluginExtensionAddedLinkConfig,
|
||||
urlUtil,
|
||||
PluginExtensionPoints,
|
||||
ExtensionInfo,
|
||||
} from '@grafana/data';
|
||||
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';
|
||||
@@ -30,11 +28,11 @@ import {
|
||||
} from 'app/types/events';
|
||||
|
||||
import { RestrictedGrafanaApisProvider } from '../components/restrictedGrafanaApis/RestrictedGrafanaApisProvider';
|
||||
import { PreloadAppPluginsPredicate } from '../pluginPreloader';
|
||||
|
||||
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 = '') {
|
||||
@@ -619,7 +617,7 @@ 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: UseLoadAppPluginsPredicate = (
|
||||
export const getExtensionPointPluginDependencies: PreloadAppPluginsPredicate = (
|
||||
apps: AppPluginConfig[],
|
||||
extensionPointId: string
|
||||
): string[] => {
|
||||
@@ -655,7 +653,7 @@ export const getExtensionPointPluginMeta = (
|
||||
return new Map(
|
||||
getExtensionPointPluginDependencies(apps, extensionPointId)
|
||||
.map((pluginId) => {
|
||||
const app = getAppPluginMeta(pluginId);
|
||||
const app = apps.find((a) => a.id === pluginId);
|
||||
// if the plugin does not exist or does not expose any components or links to the extension point, return undefined
|
||||
if (
|
||||
!app ||
|
||||
@@ -680,7 +678,7 @@ export const getExtensionPointPluginMeta = (
|
||||
|
||||
// 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: UseLoadAppPluginsPredicate = (
|
||||
export const getExposedComponentPluginDependencies: PreloadAppPluginsPredicate = (
|
||||
apps: AppPluginConfig[],
|
||||
exposedComponentId: string
|
||||
) => {
|
||||
@@ -720,28 +718,3 @@ export const getAppPluginDependencies = (
|
||||
.filter((id) => id !== pluginId)
|
||||
);
|
||||
};
|
||||
|
||||
// Returns a list of app plugins that has to be loaded before core Grafana could finish the initialization.
|
||||
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 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 = (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(
|
||||
apps,
|
||||
PluginExtensionPoints.DashboardPanelMenu
|
||||
);
|
||||
const awaitedPluginIds = getAppPluginsToAwait(apps).map((app) => app.id);
|
||||
const isNotAwaited = (app: AppPluginConfig) => !awaitedPluginIds.includes(app.id);
|
||||
|
||||
return apps.filter((app) => {
|
||||
return isNotAwaited(app) && (app.preload || dashboardPanelMenuPluginIds.includes(app.id));
|
||||
});
|
||||
};
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -146,13 +146,13 @@ export const isExposedComponentDependencyMissing = (id: string, pluginContext: P
|
||||
return !exposedComponentsDependencies || !exposedComponentsDependencies.includes(id);
|
||||
};
|
||||
|
||||
export const isAddedLinkMetaInfoMissing = (
|
||||
export const isAddedLinkMetaInfoMissing = async (
|
||||
pluginId: string,
|
||||
metaInfo: PluginExtensionAddedLinkConfig,
|
||||
log: ExtensionsLog
|
||||
) => {
|
||||
const logPrefix = 'Could not register link extension. Reason:';
|
||||
const app = getAppPluginMeta(pluginId);
|
||||
const app = await getAppPluginMeta(pluginId);
|
||||
const pluginJsonMetaInfo = app ? app.extensions.addedLinks.filter(({ title }) => title === metaInfo.title) : null;
|
||||
|
||||
if (!app) {
|
||||
@@ -178,13 +178,13 @@ export const isAddedLinkMetaInfoMissing = (
|
||||
return false;
|
||||
};
|
||||
|
||||
export const isAddedFunctionMetaInfoMissing = (
|
||||
export const isAddedFunctionMetaInfoMissing = async (
|
||||
pluginId: string,
|
||||
metaInfo: PluginExtensionAddedFunctionConfig,
|
||||
log: ExtensionsLog
|
||||
) => {
|
||||
const logPrefix = 'Could not register function extension. Reason:';
|
||||
const app = getAppPluginMeta(pluginId);
|
||||
const app = await getAppPluginMeta(pluginId);
|
||||
const pluginJsonMetaInfo = app ? app.extensions.addedFunctions.filter(({ title }) => title === metaInfo.title) : null;
|
||||
|
||||
if (!app) {
|
||||
@@ -210,13 +210,13 @@ export const isAddedFunctionMetaInfoMissing = (
|
||||
return false;
|
||||
};
|
||||
|
||||
export const isAddedComponentMetaInfoMissing = (
|
||||
export const isAddedComponentMetaInfoMissing = async (
|
||||
pluginId: string,
|
||||
metaInfo: PluginExtensionAddedComponentConfig,
|
||||
log: ExtensionsLog
|
||||
) => {
|
||||
const logPrefix = 'Could not register component extension. Reason:';
|
||||
const app = getAppPluginMeta(pluginId);
|
||||
const app = await getAppPluginMeta(pluginId);
|
||||
const pluginJsonMetaInfo = app
|
||||
? app.extensions.addedComponents.filter(({ title }) => title === metaInfo.title)
|
||||
: null;
|
||||
@@ -244,13 +244,13 @@ export const isAddedComponentMetaInfoMissing = (
|
||||
return false;
|
||||
};
|
||||
|
||||
export const isExposedComponentMetaInfoMissing = (
|
||||
export const isExposedComponentMetaInfoMissing = async (
|
||||
pluginId: string,
|
||||
metaInfo: PluginExtensionExposedComponentConfig,
|
||||
log: ExtensionsLog
|
||||
) => {
|
||||
const logPrefix = 'Could not register exposed component extension. Reason:';
|
||||
const app = getAppPluginMeta(pluginId);
|
||||
const app = await getAppPluginMeta(pluginId);
|
||||
const pluginJsonMetaInfo = app ? app.extensions.exposedComponents.filter(({ id }) => id === metaInfo.id) : null;
|
||||
|
||||
if (!app) {
|
||||
|
||||
@@ -1,12 +1,15 @@
|
||||
import type {
|
||||
AppPluginConfig,
|
||||
PluginExtensionAddedLinkConfig,
|
||||
PluginExtensionExposedComponentConfig,
|
||||
PluginExtensionAddedComponentConfig,
|
||||
import {
|
||||
type AppPluginConfig,
|
||||
type PluginExtensionAddedLinkConfig,
|
||||
type PluginExtensionExposedComponentConfig,
|
||||
type PluginExtensionAddedComponentConfig,
|
||||
PluginExtensionPoints,
|
||||
} from '@grafana/data';
|
||||
import { getAppPluginMetas } from '@grafana/runtime/unstable';
|
||||
import { contextSrv } from 'app/core/services/context_srv';
|
||||
import { getPluginSettings } from 'app/features/plugins/pluginSettings';
|
||||
|
||||
import { getExtensionPointPluginDependencies } from './extensions/utils';
|
||||
import { pluginImporter } from './importer/pluginImporter';
|
||||
|
||||
export type PluginPreloadResult = {
|
||||
@@ -23,6 +26,59 @@ export const clearPreloadedPluginsCache = () => {
|
||||
preloadPromises.clear();
|
||||
};
|
||||
|
||||
function getAppPluginIdsToAwait() {
|
||||
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 pluginIds;
|
||||
}
|
||||
|
||||
function isNotAwaited(app: AppPluginConfig) {
|
||||
return !getAppPluginIdsToAwait().includes(app.id);
|
||||
}
|
||||
|
||||
export async function preloadPluginsToBeAwaited() {
|
||||
const apps = await getAppPluginMetas();
|
||||
const awaited = getAppPluginIdsToAwait();
|
||||
const filtered = apps.filter((app) => awaited.includes(app.id));
|
||||
|
||||
preloadPlugins(filtered);
|
||||
}
|
||||
|
||||
export async function preloadPluginsToBePreloaded() {
|
||||
const apps = await getAppPluginMetas();
|
||||
|
||||
// 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(
|
||||
apps,
|
||||
PluginExtensionPoints.DashboardPanelMenu
|
||||
);
|
||||
|
||||
const filtered = apps.filter((app) => {
|
||||
return isNotAwaited(app) && (app.preload || dashboardPanelMenuPluginIds.includes(app.id));
|
||||
});
|
||||
|
||||
preloadPlugins(filtered);
|
||||
}
|
||||
|
||||
export type PreloadAppPluginsPredicate = (apps: AppPluginConfig[], extensionId: string) => string[];
|
||||
|
||||
const noop: PreloadAppPluginsPredicate = () => [];
|
||||
|
||||
export async function preloadPluginsWithPredicate(extensionId: string, predicate: PreloadAppPluginsPredicate = noop) {
|
||||
const apps = await getAppPluginMetas();
|
||||
const filteredIds = predicate(apps, extensionId);
|
||||
const filtered = apps.filter((app) => filteredIds.includes(app.id));
|
||||
|
||||
if (!filtered.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
preloadPlugins(filtered);
|
||||
}
|
||||
|
||||
export async function preloadPlugins(apps: AppPluginConfig[] = []) {
|
||||
// Create preload promises for each app, reusing existing promises if already loading
|
||||
const promises = apps.map((app) => {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { PluginType, patchArrayVectorProrotypeMethods } from '@grafana/data';
|
||||
import { config } from '@grafana/runtime';
|
||||
import { getAppPluginConfig } from '@grafana/runtime/unstable';
|
||||
import { getAppPluginMeta } from '@grafana/runtime/unstable';
|
||||
|
||||
import { transformPluginSourceForCDN } from '../cdn/utils';
|
||||
import { resolvePluginUrlWithCache } from '../loader/pluginInfoCache';
|
||||
@@ -139,7 +139,7 @@ export async function getPluginLoadData(pluginId: string): Promise<SandboxPlugin
|
||||
|
||||
//find it in apps
|
||||
//the information inside the apps object is more limited
|
||||
const app = await getAppPluginConfig(pluginId);
|
||||
const app = await getAppPluginMeta(pluginId);
|
||||
if (!app) {
|
||||
throw new Error(`Could not find plugin ${pluginId}`);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user