diff --git a/public/app/features/alerting/unified/RuleList.test.tsx b/public/app/features/alerting/unified/RuleList.test.tsx index 26e1538146b..325c718be5f 100644 --- a/public/app/features/alerting/unified/RuleList.test.tsx +++ b/public/app/features/alerting/unified/RuleList.test.tsx @@ -54,6 +54,7 @@ import { somePromRules, someRulerRules, } from './mocks'; +import { setupPluginsExtensionsHook } from './testSetup/plugins'; import * as config from './utils/config'; import { DataSourceType, GRAFANA_RULES_SOURCE_NAME } from './utils/datasource'; @@ -83,6 +84,8 @@ jest.spyOn(config, 'getAllDataSources'); jest.spyOn(actions, 'rulesInSameGroupHaveInvalidFor').mockReturnValue([]); jest.spyOn(apiRuler, 'rulerUrlBuilder'); +setupPluginsExtensionsHook(); + const mocks = { getAllDataSourcesMock: jest.mocked(config.getAllDataSources), getPluginLinkExtensionsMock: jest.mocked(getPluginLinkExtensions), diff --git a/public/app/features/alerting/unified/components/rules/RulesFilter.test.tsx b/public/app/features/alerting/unified/components/rules/RulesFilter.test.tsx index 9d610940269..058e0a61f25 100644 --- a/public/app/features/alerting/unified/components/rules/RulesFilter.test.tsx +++ b/public/app/features/alerting/unified/components/rules/RulesFilter.test.tsx @@ -9,6 +9,7 @@ import { mockSearchApi, setupMswServer } from 'app/features/alerting/unified/moc import * as analytics from '../../Analytics'; import { MockDataSourceSrv } from '../../mocks'; +import { setupPluginsExtensionsHook } from '../../testSetup/plugins'; import RulesFilter from './RulesFilter'; @@ -25,6 +26,8 @@ jest.mock('./MultipleDataSourcePicker', () => { setDataSourceSrv(new MockDataSourceSrv({})); +setupPluginsExtensionsHook(); + const ui = { stateFilter: { firing: byRole('radio', { name: 'Firing' }), diff --git a/public/app/features/alerting/unified/components/rules/RulesFilter.tsx b/public/app/features/alerting/unified/components/rules/RulesFilter.tsx index a8ce422fd02..713dcb3d56e 100644 --- a/public/app/features/alerting/unified/components/rules/RulesFilter.tsx +++ b/public/app/features/alerting/unified/components/rules/RulesFilter.tsx @@ -5,7 +5,6 @@ import { useForm } from 'react-hook-form'; import { DataSourceInstanceSettings, GrafanaTheme2, SelectableValue } from '@grafana/data'; import { Button, Field, Icon, Input, Label, RadioButtonGroup, Stack, Tooltip, useStyles2 } from '@grafana/ui'; import { DashboardPicker } from 'app/core/components/Select/DashboardPicker'; -import { useQueryParams } from 'app/core/hooks/useQueryParams'; import { PromAlertingRuleState, PromRuleType } from 'app/types/unified-alerting-dto'; import { @@ -16,6 +15,8 @@ import { trackRulesSearchInputInteraction, } from '../../Analytics'; import { useRulesFilter } from '../../hooks/useFilteredRules'; +import { useURLSearchParams } from '../../hooks/useURLSearchParams'; +import { useAlertingHomePageExtensions } from '../../plugins/useAlertingHomePageExtensions'; import { RuleHealth } from '../../search/rulesSearchParser'; import { alertStateToReadable } from '../../utils/rules'; import { HoverCard } from '../HoverCard'; @@ -68,7 +69,8 @@ const RuleStateOptions = Object.entries(PromAlertingRuleState).map(([key, value] const RulesFilter = ({ onFilterCleared = () => undefined }: RulesFilerProps) => { const styles = useStyles2(getStyles); - const [queryParams, setQueryParams] = useQueryParams(); + const [queryParams, updateQueryParams] = useURLSearchParams(); + const { pluginsFilterEnabled } = usePluginsFilterStatus(); const { filterState, hasActiveFilters, searchQuery, setSearchQuery, updateFilters } = useRulesFilter(); // This key is used to force a rerender on the inputs when the filters are cleared @@ -133,7 +135,7 @@ const RulesFilter = ({ onFilterCleared = () => undefined }: RulesFilerProps) => }; const handleViewChange = (view: string) => { - setQueryParams({ view }); + updateQueryParams({ view }); trackRulesListViewChange({ view }); }; @@ -220,6 +222,19 @@ const RulesFilter = ({ onFilterCleared = () => undefined }: RulesFilerProps) => onChange={handleRuleHealthChange} /> + {pluginsFilterEnabled && ( +
+ + + options={[ + { label: 'Show', value: undefined }, + { label: 'Hide', value: 'hide' }, + ]} + value={filterState.plugins} + onChange={(value) => updateFilters({ ...filterState, plugins: value })} + /> +
+ )} @@ -262,7 +277,7 @@ const RulesFilter = ({ onFilterCleared = () => undefined }: RulesFilerProps) => @@ -348,4 +363,9 @@ const helpStyles = (theme: GrafanaTheme2) => ({ }), }); +function usePluginsFilterStatus() { + const { extensions } = useAlertingHomePageExtensions(); + return { pluginsFilterEnabled: extensions.length > 0 }; +} + export default RulesFilter; diff --git a/public/app/features/alerting/unified/home/PluginIntegrations.tsx b/public/app/features/alerting/unified/home/PluginIntegrations.tsx index 1e9baf298b6..dda9dd0f3b2 100644 --- a/public/app/features/alerting/unified/home/PluginIntegrations.tsx +++ b/public/app/features/alerting/unified/home/PluginIntegrations.tsx @@ -1,19 +1,16 @@ import { css } from '@emotion/css'; import React from 'react'; -import { PluginExtensionPoints } from '@grafana/data'; import { GrafanaTheme2 } from '@grafana/data/'; -import { usePluginComponentExtensions } from '@grafana/runtime'; import { Stack, Text } from '@grafana/ui'; import { useStyles2 } from '@grafana/ui/'; +import { useAlertingHomePageExtensions } from '../plugins/useAlertingHomePageExtensions'; + export function PluginIntegrations() { const styles = useStyles2(getStyles); - const { extensions } = usePluginComponentExtensions({ - extensionPointId: PluginExtensionPoints.AlertingHomePage, - limitPerPlugin: 1, - }); + const { extensions } = useAlertingHomePageExtensions(); if (extensions.length === 0) { return null; diff --git a/public/app/features/alerting/unified/hooks/useFilteredRules.ts b/public/app/features/alerting/unified/hooks/useFilteredRules.ts index a71c8790f3a..3ceacb11b07 100644 --- a/public/app/features/alerting/unified/hooks/useFilteredRules.ts +++ b/public/app/features/alerting/unified/hooks/useFilteredRules.ts @@ -13,7 +13,13 @@ import { labelsMatchMatchers, matcherToMatcherField, parseMatchers } from '../ut import { Annotation } from '../utils/constants'; import { isCloudRulesSource } from '../utils/datasource'; import { parseMatcher } from '../utils/matchers'; -import { getRuleHealth, isAlertingRule, isGrafanaRulerRule, isPromRuleType } from '../utils/rules'; +import { + getRuleHealth, + isAlertingRule, + isGrafanaRulerRule, + isPluginProvidedRule, + isPromRuleType, +} from '../utils/rules'; import { calculateGroupTotals, calculateRuleFilteredTotals, calculateRuleTotals } from './useCombinedRuleNamespaces'; import { useURLSearchParams } from './useURLSearchParams'; @@ -26,7 +32,9 @@ export function useRulesFilter() { const [queryParams, updateQueryParams] = useURLSearchParams(); const searchQuery = queryParams.get('search') ?? ''; - const filterState = useMemo(() => getSearchFilterFromQuery(searchQuery), [searchQuery]); + const filterState = useMemo(() => { + return getSearchFilterFromQuery(searchQuery); + }, [searchQuery]); const hasActiveFilters = useMemo(() => Object.values(filterState).some((filter) => !isEmpty(filter)), [filterState]); const updateFilters = useCallback( @@ -208,7 +216,7 @@ const reduceGroups = (filterState: RulesFilter) => { const matchesFilterFor = chain(filterState) // ⚠️ keep this list of properties we filter for here up-to-date ⚠️ // We are ignoring predicates we've matched before we get here (like "freeFormWords") - .pick(['ruleType', 'dataSourceNames', 'ruleHealth', 'labels', 'ruleState', 'dashboardUid']) + .pick(['ruleType', 'dataSourceNames', 'ruleHealth', 'labels', 'ruleState', 'dashboardUid', 'plugins']) .omitBy(isEmpty) .mapValues(() => false) .value(); @@ -217,6 +225,10 @@ const reduceGroups = (filterState: RulesFilter) => { matchesFilterFor.ruleType = true; } + if ('plugins' in matchesFilterFor && filterState.plugins === 'hide') { + matchesFilterFor.plugins = !isPluginProvidedRule(rule); + } + if ('dataSourceNames' in matchesFilterFor) { if (isGrafanaRulerRule(rule.rulerRule)) { const doesNotQueryDs = isQueryingDataSource(rule.rulerRule, filterState); diff --git a/public/app/features/alerting/unified/plugins/useAlertingHomePageExtensions.ts b/public/app/features/alerting/unified/plugins/useAlertingHomePageExtensions.ts new file mode 100644 index 00000000000..5afd7cef697 --- /dev/null +++ b/public/app/features/alerting/unified/plugins/useAlertingHomePageExtensions.ts @@ -0,0 +1,9 @@ +import { PluginExtensionPoints } from '@grafana/data'; +import { usePluginComponentExtensions } from '@grafana/runtime'; + +export function useAlertingHomePageExtensions() { + return usePluginComponentExtensions({ + extensionPointId: PluginExtensionPoints.AlertingHomePage, + limitPerPlugin: 1, + }); +} diff --git a/public/app/features/alerting/unified/search/rulesSearchParser.ts b/public/app/features/alerting/unified/search/rulesSearchParser.ts index d1d47f707e6..6088f518e72 100644 --- a/public/app/features/alerting/unified/search/rulesSearchParser.ts +++ b/public/app/features/alerting/unified/search/rulesSearchParser.ts @@ -21,6 +21,7 @@ export interface RulesFilter { labels: string[]; ruleHealth?: RuleHealth; dashboardUid?: string; + plugins?: 'hide'; } const filterSupportedTerms: FilterSupportedTerm[] = [ @@ -33,6 +34,7 @@ const filterSupportedTerms: FilterSupportedTerm[] = [ FilterSupportedTerm.type, FilterSupportedTerm.health, FilterSupportedTerm.dashboard, + FilterSupportedTerm.plugins, ]; export enum RuleHealth { @@ -56,6 +58,7 @@ export function getSearchFilterFromQuery(query: string): RulesFilter { [terms.TypeToken]: (value) => (isPromRuleType(value) ? (filter.ruleType = value) : undefined), [terms.HealthToken]: (value) => (filter.ruleHealth = getRuleHealth(value)), [terms.DashboardToken]: (value) => (filter.dashboardUid = value), + [terms.PluginsToken]: (value) => (filter.plugins = value === 'hide' ? value : undefined), [terms.FreeFormExpression]: (value) => filter.freeFormWords.push(value), }; @@ -98,6 +101,9 @@ export function applySearchFilterToQuery(query: string, filter: RulesFilter): st if (filter.dashboardUid) { filterStateArray.push({ type: terms.DashboardToken, value: filter.dashboardUid }); } + if (filter.plugins) { + filterStateArray.push({ type: terms.PluginsToken, value: filter.plugins }); + } if (filter.freeFormWords) { filterStateArray.push(...filter.freeFormWords.map((word) => ({ type: terms.FreeFormExpression, value: word }))); } diff --git a/public/app/features/alerting/unified/search/search.grammar b/public/app/features/alerting/unified/search/search.grammar index f1be5445220..cbbab17dc18 100644 --- a/public/app/features/alerting/unified/search/search.grammar +++ b/public/app/features/alerting/unified/search/search.grammar @@ -1,6 +1,6 @@ @top AlertRuleSearch { expression+ } -@dialects { dataSourceFilter, nameSpaceFilter, labelFilter, groupFilter, ruleFilter, stateFilter, typeFilter, healthFilter, dashboardFilter } +@dialects { dataSourceFilter, nameSpaceFilter, labelFilter, groupFilter, ruleFilter, stateFilter, typeFilter, healthFilter, dashboardFilter, pluginsFilter } expression { (FilterExpression | FreeFormExpression) expression } @@ -15,7 +15,8 @@ FilterExpression { filter | filter | filter | - filter + filter | + filter } filter { token FilterValue } @@ -43,6 +44,7 @@ filter { token FilterValue } TypeToken[@dialect=typeFilter] { filterToken<"type"> } HealthToken[@dialect=healthFilter] { filterToken<"health"> } DashboardToken[@dialect=dashboardFilter] { filterToken<"dashboard"> } + PluginsToken[@dialect=pluginsFilter] { filterToken<"plugins"> } @precedence { DataSourceToken, word } @precedence { NameSpaceToken, word } @@ -53,5 +55,6 @@ filter { token FilterValue } @precedence { TypeToken, word } @precedence { HealthToken, word } @precedence { DashboardToken, word } + @precedence { PluginsToken, word } } diff --git a/public/app/features/alerting/unified/search/search.js b/public/app/features/alerting/unified/search/search.js index 8dd0e3c309e..97dbb48acf6 100644 --- a/public/app/features/alerting/unified/search/search.js +++ b/public/app/features/alerting/unified/search/search.js @@ -3,29 +3,30 @@ import { LRParser } from '@lezer/lr'; export const parser = LRParser.deserialize({ version: 14, states: - "!vOQOPOOOuOPO'#CiOOOO'#Ci'#CiOQOPO'#CmOOOO'#Cj'#CjQQOPOOO!mOQO'#C^O!rOPO'#CkO!wOPO,59TOOOO,59X,59XOOOO-E6h-E6hOOOO,58x,58xOOOO,59V,59VOOOO-E6i-E6i", + "!vOQOPOOOxOPO'#CjOOOO'#Cj'#CjOQOPO'#CnOOOO'#Ck'#CkQQOPOOO!sOQO'#C^O!xOPO'#ClO!}OPO,59UOOOO,59Y,59YOOOO-E6i-E6iOOOO,58x,58xOOOO,59W,59WOOOO-E6j-E6j", stateData: - '$[~ORUOTUOUUOVUOWUOXUOYUOZUO[UObPOdQO~OcVOR]XT]XU]XV]XW]XX]XY]XZ]X[]Xb]Xd]X~OSZO~Ob[O~OcVOR]aT]aU]aV]aW]aX]aY]aZ]a[]ab]ad]a~OR~T~U~V~W~Y~Z~[~R[ZYXWVUTb~', - goto: '{bPPcPPPPPPPPPPchnPtVRORTQTORYTQWPR]WSSOTRXR', + '$h~ORUOTUOUUOVUOWUOXUOYUOZUO[UO]UOcPOeQO~OdVOR^XT^XU^XV^XW^XX^XY^XZ^X[^X]^Xc^Xe^X~OSZO~Oc[O~OdVOR^aT^aU^aV^aW^aX^aY^aZ^a[^a]^ac^ae^a~OR~T~U~V~W~Y~Z~[~]~R][ZYXWVUTc~', + goto: '|cPPdPPPPPPPPPPPdioPuVRORTQTORYTQWPR]WSSOTRXR', nodeNames: - '⚠ AlertRuleSearch FilterExpression DataSourceToken FilterValue NameSpaceToken LabelToken GroupToken RuleToken StateToken TypeToken HealthToken DashboardToken FreeFormExpression', - maxTerm: 20, + '⚠ AlertRuleSearch FilterExpression DataSourceToken FilterValue NameSpaceToken LabelToken GroupToken RuleToken StateToken TypeToken HealthToken DashboardToken PluginsToken FreeFormExpression', + maxTerm: 21, skippedNodes: [0], repeatNodeCount: 2, tokenData: - "#0PRRqqr#Yrs&fst#Ytu#Yuv#Yvw#Ywx#Yxy#Yyz#Yz{#Y{|#Y|!P#Y!P!Q#Y!Q![#Y![!]+h!]#W#Y#W#X,z#X#Z#Y#Z#[Ia#[#]!$Z#]#`#Y#`#a!.m#a#b#Y#b#c!7g#c#f#Y#f#g!Fe#g#h!Mu#h#i#(o#i$Ch#Y$JU;'S#Y;'S;(d&Y;(d;(e&`<%lO#YR#acSQbPqr#Yst#Ytu#Yuv#Yvw#Ywx#Yxy#Yyz#Yz{#Y{|#Y|!P#Y!P!Q#Y!Q![#Y![!]$l!]$Ch#Y$JU;'S#Y;'S;(d&Y;(d;(e&`<%lO#YQ$qcSQqr$lst$ltu$luv$lvw$lwx$lxy$lyz$lz{$l{|$l|!P$l!P!Q$l!Q![$l![!]$l!]$Ch$l$JU;'S$l;'S;(d%|;(d;(e&S<%lO$lQ&PP;=`<%l$lQ&VP;=`;NQ$lR&]P;=`<%l#YR&cP;=`;NQ#YR&irX^(spq(sqr(sst(stu(suv(svw(swx(sxy(syz(sz{(s{|(s|!P(s!P!Q(s!Q![(s![!](s!]#y(s#y#z(s#z$f(s$f$g(s$g#BY(s#BY#BZ(s#BZ$Ch(s$IS$I_(s$I|$JO(s$JT$JU(s$JU$KV(s$KV$KW(s$KW&FU(s&FU&FV(s&FV;'S(s;'S;(d+[;(d;(e+b<%lO(sR(vsX^(spq(sqr(srs+Tst(stu(suv(svw(swx(sxy(syz(sz{(s{|(s|!P(s!P!Q(s!Q![(s![!](s!]#y(s#y#z(s#z$f(s$f$g(s$g#BY(s#BY#BZ(s#BZ$Ch(s$IS$I_(s$I|$JO(s$JT$JU(s$JU$KV(s$KV$KW(s$KW&FU(s&FU&FV(s&FV;'S(s;'S;(d+[;(d;(e+b<%lO(sR+[OSQdPR+_P;=`<%l(sR+eP;=`;NQ(sR+ocSQcPqr$lst$ltu$luv$lvw$lwx$lxy$lyz$lz{$l{|$l|!P$l!P!Q$l!Q![$l![!]$l!]$Ch$l$JU;'S$l;'S;(d%|;(d;(e&S<%lO$lR-ReSQbPqr#Yst#Ytu#Yuv#Yvw#Ywx#Yxy#Yyz#Yz{#Y{|#Y|!P#Y!P!Q#Y!Q![#Y![!]$l!]#T#Y#T#U.d#U$Ch#Y$JU;'S#Y;'S;(d&Y;(d;(e&`<%lO#YR.kfSQbPqr#Yst#Ytu#Yuv#Yvw#Ywx#Yxy#Yyz#Yz{#Y{|#Y|!P#Y!P!Q#Y!Q![#Y![!]$l!]#g#Y#g#h0P#h#i;{#i$Ch#Y$JU;'S#Y;'S;(d&Y;(d;(e&`<%lO#YR0WeSQbPqr#Yst#Ytu#Yuv#Yvw#Ywx#Yxy#Yyz#Yz{#Y{|#Y|!P#Y!P!Q#Y!Q![#Y![!]$l!]#[#Y#[#]1i#]$Ch#Y$JU;'S#Y;'S;(d&Y;(d;(e&`<%lO#YR1peSQbPqr#Yst#Ytu#Yuv#Yvw#Ywx#Yxy#Yyz#Yz{#Y{|#Y|!P#Y!P!Q#Y!Q![#Y![!]$l!]#U#Y#U#V3R#V$Ch#Y$JU;'S#Y;'S;(d&Y;(d;(e&`<%lO#YR3YeSQbPqr#Yst#Ytu#Yuv#Yvw#Ywx#Yxy#Yyz#Yz{#Y{|#Y|!P#Y!P!Q#Y!Q![#Y![!]$l!]#c#Y#c#d4k#d$Ch#Y$JU;'S#Y;'S;(d&Y;(d;(e&`<%lO#YR4reSQbPqr#Yst#Ytu#Yuv#Yvw#Ywx#Yxy#Yyz#Yz{#Y{|#Y|!P#Y!P!Q#Y!Q![#Y![!]$l!]#T#Y#T#U6T#U$Ch#Y$JU;'S#Y;'S;(d&Y;(d;(e&`<%lO#YR6[eSQbPqr#Yst#Ytu#Yuv#Yvw#Ywx#Yxy#Yyz#Yz{#Y{|#Y|!P#Y!P!Q#Y!Q![#Y![!]$l!]#f#Y#f#g7m#g$Ch#Y$JU;'S#Y;'S;(d&Y;(d;(e&`<%lO#YR7teSQbPqr#Yst#Ytu#Yuv#Yvw#Ywx#Yxy#Yyz#Yz{#Y{|#Y|!P#Y!P!Q#Y!Q![#Y![!]$l!]#W#Y#W#X9V#X$Ch#Y$JU;'S#Y;'S;(d&Y;(d;(e&`<%lO#YR9^cSQbPqr#Yst#Ytu#Yuv#Yvw#Ywx#Yxy#Yyz#Yz{#Y{|#Y|!P#Y!P!Q#Y!Q![#Y![!]:i!]$Ch#Y$JU;'S#Y;'S;(d&Y;(d;(e&`<%lO#YR:pcSQ[Pqr$lst$ltu$luv$lvw$lwx$lxy$lyz$lz{$l{|$l|!P$l!P!Q$l!Q![$l![!]$l!]$Ch$l$JU;'S$l;'S;(d%|;(d;(e&S<%lO$lR}#h$Ch#Y$JU;'S#Y;'S;(d&Y;(d;(e&`<%lO#YR?UeSQbPqr#Yst#Ytu#Yuv#Yvw#Ywx#Yxy#Yyz#Yz{#Y{|#Y|!P#Y!P!Q#Y!Q![#Y![!]$l!]#c#Y#c#d@g#d$Ch#Y$JU;'S#Y;'S;(d&Y;(d;(e&`<%lO#YR@neSQbPqr#Yst#Ytu#Yuv#Yvw#Ywx#Yxy#Yyz#Yz{#Y{|#Y|!P#Y!P!Q#Y!Q![#Y![!]$l!]#i#Y#i#jBP#j$Ch#Y$JU;'S#Y;'S;(d&Y;(d;(e&`<%lO#YRBWeSQbPqr#Yst#Ytu#Yuv#Yvw#Ywx#Yxy#Yyz#Yz{#Y{|#Y|!P#Y!P!Q#Y!Q![#Y![!]$l!]#f#Y#f#gCi#g$Ch#Y$JU;'S#Y;'S;(d&Y;(d;(e&`<%lO#YRCpeSQbPqr#Yst#Ytu#Yuv#Yvw#Ywx#Yxy#Yyz#Yz{#Y{|#Y|!P#Y!P!Q#Y!Q![#Y![!]$l!]#V#Y#V#WER#W$Ch#Y$JU;'S#Y;'S;(d&Y;(d;(e&`<%lO#YREYeSQbPqr#Yst#Ytu#Yuv#Yvw#Ywx#Yxy#Yyz#Yz{#Y{|#Y|!P#Y!P!Q#Y!Q![#Y![!]$l!]#X#Y#X#YFk#Y$Ch#Y$JU;'S#Y;'S;(d&Y;(d;(e&`<%lO#YRFrcSQbPqr#Yst#Ytu#Yuv#Yvw#Ywx#Yxy#Yyz#Yz{#Y{|#Y|!P#Y!P!Q#Y!Q![#Y![!]G}!]$Ch#Y$JU;'S#Y;'S;(d&Y;(d;(e&`<%lO#YRHUcSQRPqr$lst$ltu$luv$lvw$lwx$lxy$lyz$lz{$l{|$l|!P$l!P!Q$l!Q![$l![!]$l!]$Ch$l$JU;'S$l;'S;(d%|;(d;(e&S<%lO$lRIheSQbPqr#Yst#Ytu#Yuv#Yvw#Ywx#Yxy#Yyz#Yz{#Y{|#Y|!P#Y!P!Q#Y!Q![#Y![!]$l!]#f#Y#f#gJy#g$Ch#Y$JU;'S#Y;'S;(d&Y;(d;(e&`<%lO#YRKQeSQbPqr#Yst#Ytu#Yuv#Yvw#Ywx#Yxy#Yyz#Yz{#Y{|#Y|!P#Y!P!Q#Y!Q![#Y![!]$l!]#c#Y#c#dLc#d$Ch#Y$JU;'S#Y;'S;(d&Y;(d;(e&`<%lO#YRLjeSQbPqr#Yst#Ytu#Yuv#Yvw#Ywx#Yxy#Yyz#Yz{#Y{|#Y|!P#Y!P!Q#Y!Q![#Y![!]$l!]#i#Y#i#jM{#j$Ch#Y$JU;'S#Y;'S;(d&Y;(d;(e&`<%lO#YRNSeSQbPqr#Yst#Ytu#Yuv#Yvw#Ywx#Yxy#Yyz#Yz{#Y{|#Y|!P#Y!P!Q#Y!Q![#Y![!]$l!]#d#Y#d#e! e#e$Ch#Y$JU;'S#Y;'S;(d&Y;(d;(e&`<%lO#YR! lcSQbPqr#Yst#Ytu#Yuv#Yvw#Ywx#Yxy#Yyz#Yz{#Y{|#Y|!P#Y!P!Q#Y!Q![#Y![!]!!w!]$Ch#Y$JU;'S#Y;'S;(d&Y;(d;(e&`<%lO#YR!#OcSQVPqr$lst$ltu$luv$lvw$lwx$lxy$lyz$lz{$l{|$l|!P$l!P!Q$l!Q![$l![!]$l!]$Ch$l$JU;'S$l;'S;(d%|;(d;(e&S<%lO$lR!$beSQbPqr#Yst#Ytu#Yuv#Yvw#Ywx#Yxy#Yyz#Yz{#Y{|#Y|!P#Y!P!Q#Y!Q![#Y![!]$l!]#X#Y#X#Y!%s#Y$Ch#Y$JU;'S#Y;'S;(d&Y;(d;(e&`<%lO#YR!%zeSQbPqr#Yst#Ytu#Yuv#Yvw#Ywx#Yxy#Yyz#Yz{#Y{|#Y|!P#Y!P!Q#Y!Q![#Y![!]$l!]#T#Y#T#U!']#U$Ch#Y$JU;'S#Y;'S;(d&Y;(d;(e&`<%lO#YR!'deSQbPqr#Yst#Ytu#Yuv#Yvw#Ywx#Yxy#Yyz#Yz{#Y{|#Y|!P#Y!P!Q#Y!Q![#Y![!]$l!]#`#Y#`#a!(u#a$Ch#Y$JU;'S#Y;'S;(d&Y;(d;(e&`<%lO#YR!(|eSQbPqr#Yst#Ytu#Yuv#Yvw#Ywx#Yxy#Yyz#Yz{#Y{|#Y|!P#Y!P!Q#Y!Q![#Y![!]$l!]#h#Y#h#i!*_#i$Ch#Y$JU;'S#Y;'S;(d&Y;(d;(e&`<%lO#YR!*feSQbPqr#Yst#Ytu#Yuv#Yvw#Ywx#Yxy#Yyz#Yz{#Y{|#Y|!P#Y!P!Q#Y!Q![#Y![!]$l!]#[#Y#[#]!+w#]$Ch#Y$JU;'S#Y;'S;(d&Y;(d;(e&`<%lO#YR!,OcSQbPqr#Yst#Ytu#Yuv#Yvw#Ywx#Yxy#Yyz#Yz{#Y{|#Y|!P#Y!P!Q#Y!Q![#Y![!]!-Z!]$Ch#Y$JU;'S#Y;'S;(d&Y;(d;(e&`<%lO#YR!-bcSQZPqr$lst$ltu$luv$lvw$lwx$lxy$lyz$lz{$l{|$l|!P$l!P!Q$l!Q![$l![!]$l!]$Ch$l$JU;'S$l;'S;(d%|;(d;(e&S<%lO$lR!.teSQbPqr#Yst#Ytu#Yuv#Yvw#Ywx#Yxy#Yyz#Yz{#Y{|#Y|!P#Y!P!Q#Y!Q![#Y![!]$l!]#T#Y#T#U!0V#U$Ch#Y$JU;'S#Y;'S;(d&Y;(d;(e&`<%lO#YR!0^eSQbPqr#Yst#Ytu#Yuv#Yvw#Ywx#Yxy#Yyz#Yz{#Y{|#Y|!P#Y!P!Q#Y!Q![#Y![!]$l!]#U#Y#U#V!1o#V$Ch#Y$JU;'S#Y;'S;(d&Y;(d;(e&`<%lO#YR!1veSQbPqr#Yst#Ytu#Yuv#Yvw#Ywx#Yxy#Yyz#Yz{#Y{|#Y|!P#Y!P!Q#Y!Q![#Y![!]$l!]#X#Y#X#Y!3X#Y$Ch#Y$JU;'S#Y;'S;(d&Y;(d;(e&`<%lO#YR!3`eSQbPqr#Yst#Ytu#Yuv#Yvw#Ywx#Yxy#Yyz#Yz{#Y{|#Y|!P#Y!P!Q#Y!Q![#Y![!]$l!]#`#Y#`#a!4q#a$Ch#Y$JU;'S#Y;'S;(d&Y;(d;(e&`<%lO#YR!4xcSQbPqr#Yst#Ytu#Yuv#Yvw#Ywx#Yxy#Yyz#Yz{#Y{|#Y|!P#Y!P!Q#Y!Q![#Y![!]!6T!]$Ch#Y$JU;'S#Y;'S;(d&Y;(d;(e&`<%lO#YR!6[cSQUPqr$lst$ltu$luv$lvw$lwx$lxy$lyz$lz{$l{|$l|!P$l!P!Q$l!Q![$l![!]$l!]$Ch$l$JU;'S$l;'S;(d%|;(d;(e&S<%lO$lR!7neSQbPqr#Yst#Ytu#Yuv#Yvw#Ywx#Yxy#Yyz#Yz{#Y{|#Y|!P#Y!P!Q#Y!Q![#Y![!]$l!]#T#Y#T#U!9P#U$Ch#Y$JU;'S#Y;'S;(d&Y;(d;(e&`<%lO#YR!9WeSQbPqr#Yst#Ytu#Yuv#Yvw#Ywx#Yxy#Yyz#Yz{#Y{|#Y|!P#Y!P!Q#Y!Q![#Y![!]$l!]#a#Y#a#b!:i#b$Ch#Y$JU;'S#Y;'S;(d&Y;(d;(e&`<%lO#YR!:peSQbPqr#Yst#Ytu#Yuv#Yvw#Ywx#Yxy#Yyz#Yz{#Y{|#Y|!P#Y!P!Q#Y!Q![#Y![!]$l!]#X#Y#X#Y! = { [terms.TypeToken]: 'type', [terms.HealthToken]: 'health', [terms.DashboardToken]: 'dashboard', + [terms.PluginsToken]: 'plugins', }; // This enum allows to configure parser behavior @@ -29,6 +30,7 @@ export enum FilterSupportedTerm { type = 'typeFilter', health = 'healthFilter', dashboard = 'dashboardFilter', + plugins = 'pluginsFilter', } export type QueryFilterMapper = Record void>; diff --git a/public/app/features/alerting/unified/testSetup/plugins.ts b/public/app/features/alerting/unified/testSetup/plugins.ts index 9cd8041afdf..4efa4d9c3e1 100644 --- a/public/app/features/alerting/unified/testSetup/plugins.ts +++ b/public/app/features/alerting/unified/testSetup/plugins.ts @@ -1,8 +1,9 @@ import { RequestHandler } from 'msw'; import { PluginMeta, PluginType } from '@grafana/data'; -import { config } from '@grafana/runtime'; +import { config, setPluginExtensionsHook } from '@grafana/runtime'; +import { mockPluginLinkExtension } from '../mocks'; import { pluginsHandler } from '../mocks/plugins'; export function setupPlugins(plugins: PluginMeta[]): { apiHandlers: RequestHandler[] } { @@ -21,6 +22,19 @@ export function setupPlugins(plugins: PluginMeta[]): { apiHandlers: RequestHandl }; } +export function setupPluginsExtensionsHook() { + setPluginExtensionsHook(() => ({ + extensions: plugins.map((plugin) => + mockPluginLinkExtension({ + pluginId: plugin.id, + title: plugin.name, + path: `/a/${plugin.id}`, + }) + ), + isLoading: false, + })); +} + export const plugins: PluginMeta[] = [ { id: 'grafana-slo-app',