mirror of
https://github.com/grafana/grafana.git
synced 2024-11-21 16:38:03 -06:00
Alerting: Remove disable logic for name field on time intervals when using k8s API (#91885)
Co-authored-by: Gilles De Mey <gilles.de.mey@gmail.com>
This commit is contained in:
parent
43dba8c3f9
commit
e2a1d59a96
@ -13,8 +13,8 @@ import {
|
||||
import { captureRequests } from 'app/features/alerting/unified/mocks/server/events';
|
||||
import { MOCK_DATASOURCE_EXTERNAL_VANILLA_ALERTMANAGER_UID } from 'app/features/alerting/unified/mocks/server/handlers/datasources';
|
||||
import {
|
||||
TIME_INTERVAL_UID_HAPPY_PATH,
|
||||
TIME_INTERVAL_UID_FILE_PROVISIONED,
|
||||
TIME_INTERVAL_NAME_HAPPY_PATH,
|
||||
TIME_INTERVAL_NAME_FILE_PROVISIONED,
|
||||
} from 'app/features/alerting/unified/mocks/server/handlers/k8s/timeIntervals.k8s';
|
||||
import { setupDataSources } from 'app/features/alerting/unified/testSetup/datasources';
|
||||
import { AlertManagerCortexConfig, MuteTimeInterval } from 'app/plugins/datasource/alertmanager/types';
|
||||
@ -185,7 +185,7 @@ const fillOutForm = async ({
|
||||
|
||||
const saveMuteTiming = async () => {
|
||||
const user = userEvent.setup();
|
||||
await user.click(screen.getByText(/save mute timing/i));
|
||||
await user.click(await screen.findByText(/save mute timing/i));
|
||||
};
|
||||
|
||||
setupMswServer();
|
||||
@ -365,7 +365,7 @@ describe('Mute timings', () => {
|
||||
});
|
||||
|
||||
it('allows creation of new mute timings', async () => {
|
||||
await renderMuteTimings({
|
||||
renderMuteTimings({
|
||||
pathname: '/alerting/routes/mute-timing/new',
|
||||
});
|
||||
|
||||
@ -378,7 +378,7 @@ describe('Mute timings', () => {
|
||||
it('shows error when mute timing does not exist', async () => {
|
||||
renderMuteTimings({
|
||||
pathname: '/alerting/routes/mute-timing/edit',
|
||||
search: `?alertmanager=${GRAFANA_RULES_SOURCE_NAME}&muteName=${TIME_INTERVAL_UID_HAPPY_PATH + '_force_breakage'}`,
|
||||
search: `?alertmanager=${GRAFANA_RULES_SOURCE_NAME}&muteName=${TIME_INTERVAL_NAME_HAPPY_PATH + '_force_breakage'}`,
|
||||
});
|
||||
|
||||
expect(await screen.findByText(/No matching mute timing found/i)).toBeInTheDocument();
|
||||
@ -387,12 +387,9 @@ describe('Mute timings', () => {
|
||||
it('loads edit form correctly and allows saving', async () => {
|
||||
renderMuteTimings({
|
||||
pathname: '/alerting/routes/mute-timing/edit',
|
||||
search: `?alertmanager=${GRAFANA_RULES_SOURCE_NAME}&muteName=${TIME_INTERVAL_UID_HAPPY_PATH}`,
|
||||
search: `?alertmanager=${GRAFANA_RULES_SOURCE_NAME}&muteName=${TIME_INTERVAL_NAME_HAPPY_PATH}`,
|
||||
});
|
||||
|
||||
// For now, we expect the name field to be disabled editing via the k8s API
|
||||
expect(await ui.nameField.find()).toBeDisabled();
|
||||
|
||||
await saveMuteTiming();
|
||||
await expectedToHaveRedirectedToRoutesRoute();
|
||||
});
|
||||
@ -400,7 +397,7 @@ describe('Mute timings', () => {
|
||||
it('loads view form for provisioned interval', async () => {
|
||||
renderMuteTimings({
|
||||
pathname: '/alerting/routes/mute-timing/edit',
|
||||
search: `?muteName=${TIME_INTERVAL_UID_FILE_PROVISIONED}`,
|
||||
search: `?muteName=${TIME_INTERVAL_NAME_FILE_PROVISIONED}`,
|
||||
});
|
||||
|
||||
expect(await screen.findByText(/This mute timing cannot be edited through the UI/i)).toBeInTheDocument();
|
||||
|
@ -30,7 +30,7 @@ export const MuteTimingActionsButtons = ({ muteTiming, alertManagerSourceName }:
|
||||
|
||||
const isGrafanaDataSource = alertManagerSourceName === GRAFANA_RULES_SOURCE_NAME;
|
||||
const viewOrEditHref = makeAMLink(`/alerting/routes/mute-timing/edit`, alertManagerSourceName, {
|
||||
muteName: muteTiming?.metadata?.name || muteTiming.name,
|
||||
muteName: muteTiming.id,
|
||||
});
|
||||
|
||||
const viewOrEditButton = (
|
||||
|
@ -11,7 +11,6 @@ import {
|
||||
useUpdateMuteTiming,
|
||||
useValidateMuteTiming,
|
||||
} from 'app/features/alerting/unified/components/mute-timings/useMuteTimings';
|
||||
import { shouldUseK8sApi } from 'app/features/alerting/unified/utils/k8s/utils';
|
||||
|
||||
import { useAlertmanager } from '../../state/AlertmanagerContext';
|
||||
import { MuteTimingFields } from '../../types/mute-timing-form';
|
||||
@ -65,13 +64,6 @@ const MuteTimingForm = ({ muteTiming, showError, loading, provisioned, editMode
|
||||
const [updateTimeInterval] = useUpdateMuteTiming(hookArgs);
|
||||
const validateMuteTiming = useValidateMuteTiming(hookArgs);
|
||||
|
||||
/**
|
||||
* The k8s API approach does not support renaming an entity at this time,
|
||||
* as it requires renaming all other references of this entity.
|
||||
*
|
||||
* For now, the cleanest solution is to disabled renaming the field in this scenario
|
||||
*/
|
||||
const disableNameField = editMode && shouldUseK8sApi(selectedAlertmanager!);
|
||||
const styles = useStyles2(getStyles);
|
||||
const defaultValues = useDefaultValues(muteTiming);
|
||||
|
||||
@ -115,7 +107,6 @@ const MuteTimingForm = ({ muteTiming, showError, loading, provisioned, editMode
|
||||
description="A unique name for the mute timing"
|
||||
invalid={!!formApi.formState.errors?.name}
|
||||
error={formApi.formState.errors.name?.message}
|
||||
disabled={disableNameField}
|
||||
>
|
||||
<Input
|
||||
{...formApi.register('name', {
|
||||
|
@ -5,7 +5,7 @@ import { timeIntervalsApi } from 'app/features/alerting/unified/api/timeInterval
|
||||
import { mergeTimeIntervals } from 'app/features/alerting/unified/components/mute-timings/util';
|
||||
import {
|
||||
ComGithubGrafanaGrafanaPkgApisAlertingNotificationsV0Alpha1TimeInterval,
|
||||
ReadNamespacedTimeIntervalApiResponse,
|
||||
IoK8SApimachineryPkgApisMetaV1ObjectMeta,
|
||||
} from 'app/features/alerting/unified/openapi/timeIntervalsApi.gen';
|
||||
import { BaseAlertmanagerArgs } from 'app/features/alerting/unified/types/hooks';
|
||||
import { PROVENANCE_ANNOTATION, PROVENANCE_NONE } from 'app/features/alerting/unified/utils/k8s/constants';
|
||||
@ -24,7 +24,6 @@ const { useLazyGetAlertmanagerConfigurationQuery } = alertmanagerApi;
|
||||
const {
|
||||
useLazyListNamespacedTimeIntervalQuery,
|
||||
useCreateNamespacedTimeIntervalMutation,
|
||||
useLazyReadNamespacedTimeIntervalQuery,
|
||||
useReplaceNamespacedTimeIntervalMutation,
|
||||
useDeleteNamespacedTimeIntervalMutation,
|
||||
} = timeIntervalsApi;
|
||||
@ -35,7 +34,7 @@ const {
|
||||
* */
|
||||
export type MuteTiming = MuteTimeInterval & {
|
||||
id: string;
|
||||
metadata?: ReadNamespacedTimeIntervalApiResponse['metadata'];
|
||||
metadata?: IoK8SApimachineryPkgApisMetaV1ObjectMeta;
|
||||
};
|
||||
|
||||
/** Alias for generated kuberenetes Alerting API Server type */
|
||||
@ -156,14 +155,18 @@ export const useCreateMuteTiming = ({ alertmanager }: BaseAlertmanagerArgs) => {
|
||||
export const useGetMuteTiming = ({ alertmanager, name: nameToFind }: BaseAlertmanagerArgs & { name: string }) => {
|
||||
const useK8sApi = shouldUseK8sApi(alertmanager);
|
||||
|
||||
const [getGrafanaTimeInterval, k8sResponse] = useLazyReadNamespacedTimeIntervalQuery({
|
||||
const [getGrafanaTimeInterval, k8sResponse] = useLazyListNamespacedTimeIntervalQuery({
|
||||
selectFromResult: ({ data, ...rest }) => {
|
||||
if (!data) {
|
||||
return { data, ...rest };
|
||||
}
|
||||
|
||||
if (data.items.length === 0) {
|
||||
return { ...rest, data: undefined, isError: true };
|
||||
}
|
||||
|
||||
return {
|
||||
data: parseK8sTimeInterval(data),
|
||||
data: parseK8sTimeInterval(data.items[0]),
|
||||
...rest,
|
||||
};
|
||||
},
|
||||
@ -192,7 +195,7 @@ export const useGetMuteTiming = ({ alertmanager, name: nameToFind }: BaseAlertma
|
||||
useEffect(() => {
|
||||
if (useK8sApi) {
|
||||
const namespace = getK8sNamespace();
|
||||
getGrafanaTimeInterval({ namespace, name: nameToFind }, true);
|
||||
getGrafanaTimeInterval({ namespace, fieldSelector: `spec.name=${nameToFind}` }, true);
|
||||
} else {
|
||||
getAlertmanagerTimeInterval(alertmanager, true);
|
||||
}
|
||||
|
@ -1,13 +1,18 @@
|
||||
import { HttpResponse, http } from 'msw';
|
||||
|
||||
import { filterBySelector } from 'app/features/alerting/unified/mocks/server/handlers/k8s/utils';
|
||||
import { ALERTING_API_SERVER_BASE_URL, getK8sResponse } from 'app/features/alerting/unified/mocks/server/utils';
|
||||
import { ComGithubGrafanaGrafanaPkgApisAlertingNotificationsV0Alpha1TimeInterval } from 'app/features/alerting/unified/openapi/timeIntervalsApi.gen';
|
||||
import { PROVENANCE_ANNOTATION, PROVENANCE_NONE } from 'app/features/alerting/unified/utils/k8s/constants';
|
||||
|
||||
/** UID of a time interval that we expect to follow all happy paths within tests/mocks */
|
||||
export const TIME_INTERVAL_UID_HAPPY_PATH = 'f4eae7a4895fa786';
|
||||
/** Display name of a time interval that we expect to follow all happy paths within tests/mocks */
|
||||
export const TIME_INTERVAL_NAME_HAPPY_PATH = 'Some interval';
|
||||
|
||||
/** UID of a (file) provisioned time interval */
|
||||
export const TIME_INTERVAL_UID_FILE_PROVISIONED = 'd7b8515fc39e90f7';
|
||||
export const TIME_INTERVAL_NAME_FILE_PROVISIONED = 'A provisioned interval';
|
||||
|
||||
const allTimeIntervals = getK8sResponse<ComGithubGrafanaGrafanaPkgApisAlertingNotificationsV0Alpha1TimeInterval>(
|
||||
'TimeIntervalList',
|
||||
@ -22,7 +27,7 @@ const allTimeIntervals = getK8sResponse<ComGithubGrafanaGrafanaPkgApisAlertingNo
|
||||
namespace: 'default',
|
||||
resourceVersion: 'e0270bfced786660',
|
||||
},
|
||||
spec: { name: 'Some interval', time_intervals: [] },
|
||||
spec: { name: TIME_INTERVAL_NAME_HAPPY_PATH, time_intervals: [] },
|
||||
},
|
||||
{
|
||||
metadata: {
|
||||
@ -34,7 +39,7 @@ const allTimeIntervals = getK8sResponse<ComGithubGrafanaGrafanaPkgApisAlertingNo
|
||||
namespace: 'default',
|
||||
resourceVersion: 'a76d2fcc6731aa0c',
|
||||
},
|
||||
spec: { name: 'A provisioned interval', time_intervals: [] },
|
||||
spec: { name: TIME_INTERVAL_NAME_FILE_PROVISIONED, time_intervals: [] },
|
||||
},
|
||||
]
|
||||
);
|
||||
@ -44,9 +49,9 @@ const getIntervalByName = (name: string) => {
|
||||
};
|
||||
|
||||
export const listNamespacedTimeIntervalHandler = () =>
|
||||
http.get<{ namespace: string }>(
|
||||
http.get<{ namespace: string }, { fieldSelector: string }>(
|
||||
`${ALERTING_API_SERVER_BASE_URL}/namespaces/:namespace/timeintervals`,
|
||||
({ params }) => {
|
||||
({ params, request }) => {
|
||||
const { namespace } = params;
|
||||
|
||||
// k8s APIs expect `default` rather than `org-1` - this is one particular example
|
||||
@ -59,6 +64,17 @@ export const listNamespacedTimeIntervalHandler = () =>
|
||||
{ status: 403 }
|
||||
);
|
||||
}
|
||||
|
||||
// Rudimentary filter support for `spec.name`
|
||||
const url = new URL(request.url);
|
||||
const fieldSelector = url.searchParams.get('fieldSelector');
|
||||
|
||||
if (fieldSelector && fieldSelector.includes('spec.name')) {
|
||||
const filteredItems = filterBySelector(allTimeIntervals.items, fieldSelector);
|
||||
|
||||
return HttpResponse.json({ items: filteredItems });
|
||||
}
|
||||
|
||||
return HttpResponse.json(allTimeIntervals);
|
||||
}
|
||||
);
|
||||
|
@ -0,0 +1,20 @@
|
||||
import { chain, filter, matchesProperty, trim } from 'lodash';
|
||||
|
||||
/**
|
||||
* Filters a list of k8s items by a selector string
|
||||
*/
|
||||
export function filterBySelector<T>(items: T[], selector: string) {
|
||||
// e.g. [['path.to.key', 'value'], ['other.path', 'value']]
|
||||
const filters: string[][] = chain(selector)
|
||||
.split(',')
|
||||
.map(trim)
|
||||
.map((s) => s.split('='))
|
||||
.value();
|
||||
|
||||
return filter(items, (item) =>
|
||||
filters.every(([key, value]) => {
|
||||
const matcher = matchesProperty(key, value);
|
||||
return matcher(item);
|
||||
})
|
||||
);
|
||||
}
|
@ -42,13 +42,6 @@ const injectedRtkApi = api
|
||||
}),
|
||||
invalidatesTags: ['TimeInterval'],
|
||||
}),
|
||||
readNamespacedTimeInterval: build.query<ReadNamespacedTimeIntervalApiResponse, ReadNamespacedTimeIntervalApiArg>({
|
||||
query: (queryArg) => ({
|
||||
url: `/apis/notifications.alerting.grafana.app/v0alpha1/namespaces/${queryArg['namespace']}/timeintervals/${queryArg.name}`,
|
||||
params: { pretty: queryArg.pretty },
|
||||
}),
|
||||
providesTags: ['TimeInterval'],
|
||||
}),
|
||||
replaceNamespacedTimeInterval: build.mutation<
|
||||
ReplaceNamespacedTimeIntervalApiResponse,
|
||||
ReplaceNamespacedTimeIntervalApiArg
|
||||
@ -171,16 +164,6 @@ export type CreateNamespacedTimeIntervalApiArg = {
|
||||
fieldValidation?: string;
|
||||
comGithubGrafanaGrafanaPkgApisAlertingNotificationsV0Alpha1TimeInterval: ComGithubGrafanaGrafanaPkgApisAlertingNotificationsV0Alpha1TimeInterval;
|
||||
};
|
||||
export type ReadNamespacedTimeIntervalApiResponse =
|
||||
/** status 200 OK */ ComGithubGrafanaGrafanaPkgApisAlertingNotificationsV0Alpha1TimeInterval;
|
||||
export type ReadNamespacedTimeIntervalApiArg = {
|
||||
/** name of the TimeInterval */
|
||||
name: string;
|
||||
/** object name and auth scope, such as for teams and projects */
|
||||
namespace: string;
|
||||
/** If 'true', then the output is pretty printed. Defaults to 'false' unless the user-agent indicates a browser or command-line HTTP tool (curl and wget). */
|
||||
pretty?: string;
|
||||
};
|
||||
export type ReplaceNamespacedTimeIntervalApiResponse = /** status 200 OK */
|
||||
| ComGithubGrafanaGrafanaPkgApisAlertingNotificationsV0Alpha1TimeInterval
|
||||
| /** status 201 Created */ ComGithubGrafanaGrafanaPkgApisAlertingNotificationsV0Alpha1TimeInterval;
|
||||
|
@ -31,7 +31,6 @@ const config: ConfigFile = {
|
||||
filterEndpoints: [
|
||||
'listNamespacedTimeInterval',
|
||||
'createNamespacedTimeInterval',
|
||||
'readNamespacedTimeInterval',
|
||||
'deleteNamespacedTimeInterval',
|
||||
'patchNamespacedTimeInterval',
|
||||
'replaceNamespacedTimeInterval',
|
||||
|
Loading…
Reference in New Issue
Block a user