Compare commits
10 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| e2d459afdd | |||
| 27c464c833 | |||
| e1efcae8f3 | |||
| 1f32a31c2b | |||
| 65cee8e6b4 | |||
| 6da8c5d1bb | |||
| 87f09ffa2d | |||
| e0b76f7916 | |||
| 87b32fe05c | |||
| 649fabb2e6 |
@@ -1,5 +1,7 @@
|
||||
import { HttpResponse, http } from 'msw';
|
||||
|
||||
import { mockTeamsMap } from '../../../../fixtures/teams';
|
||||
|
||||
const getDisplayMapping = () =>
|
||||
http.get<{ namespace: string }>('/apis/iam.grafana.app/v0alpha1/namespaces/:namespace/display', ({ request }) => {
|
||||
const url = new URL(request.url);
|
||||
@@ -26,4 +28,76 @@ const getDisplayMapping = () =>
|
||||
});
|
||||
});
|
||||
|
||||
export default [getDisplayMapping()];
|
||||
const listExternalGroupMappings = () =>
|
||||
http.get<{ namespace: string }>('/apis/iam.grafana.app/v0alpha1/namespaces/:namespace/externalgroupmappings', () => {
|
||||
const items = [];
|
||||
for (const [teamName, data] of mockTeamsMap.entries()) {
|
||||
for (const group of data.groups) {
|
||||
items.push({
|
||||
apiVersion: 'iam.grafana.app/v0alpha1',
|
||||
kind: 'ExternalGroupMapping',
|
||||
metadata: {
|
||||
name: `mapping-${teamName}-${group.groupId}`,
|
||||
creationTimestamp: new Date().toISOString(),
|
||||
},
|
||||
spec: {
|
||||
externalGroupId: group.groupId,
|
||||
teamRef: {
|
||||
name: teamName,
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
return HttpResponse.json({ items });
|
||||
});
|
||||
|
||||
const createExternalGroupMapping = () =>
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
http.post<{ namespace: string }, any>(
|
||||
'/apis/iam.grafana.app/v0alpha1/namespaces/:namespace/externalgroupmappings',
|
||||
async ({ request }) => {
|
||||
const body = await request.json();
|
||||
const teamName = body.spec.teamRef.name;
|
||||
const groupId = body.spec.externalGroupId;
|
||||
|
||||
const teamData = mockTeamsMap.get(teamName);
|
||||
if (teamData) {
|
||||
teamData.groups.push({ groupId });
|
||||
}
|
||||
|
||||
return HttpResponse.json({
|
||||
...body,
|
||||
metadata: {
|
||||
name: `mapping-${teamName}-${groupId}`,
|
||||
creationTimestamp: new Date().toISOString(),
|
||||
...body.metadata,
|
||||
},
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
const deleteExternalGroupMapping = () =>
|
||||
http.delete<{ namespace: string; name: string }>(
|
||||
'/apis/iam.grafana.app/v0alpha1/namespaces/:namespace/externalgroupmappings/:name',
|
||||
({ params }) => {
|
||||
const { name } = params;
|
||||
|
||||
for (const [teamName, data] of mockTeamsMap.entries()) {
|
||||
const groupIndex = data.groups.findIndex((g) => `mapping-${teamName}-${g.groupId}` === name);
|
||||
if (groupIndex !== -1) {
|
||||
data.groups.splice(groupIndex, 1);
|
||||
return HttpResponse.json({ status: 'Success' });
|
||||
}
|
||||
}
|
||||
|
||||
return HttpResponse.json({ status: 'Failure', message: 'Not found' }, { status: 404 });
|
||||
}
|
||||
);
|
||||
|
||||
export default [
|
||||
getDisplayMapping(),
|
||||
listExternalGroupMappings(),
|
||||
createExternalGroupMapping(),
|
||||
deleteExternalGroupMapping(),
|
||||
];
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { render, screen, waitFor } from 'test/test-utils';
|
||||
|
||||
import { setBackendSrv } from '@grafana/runtime';
|
||||
import { setBackendSrv, config } from '@grafana/runtime';
|
||||
import { setupMockServer } from '@grafana/test-utils/server';
|
||||
import { MOCK_TEAMS, MOCK_TEAM_GROUPS } from '@grafana/test-utils/unstable';
|
||||
import { backendSrv } from 'app/core/services/backend_srv';
|
||||
@@ -25,9 +25,8 @@ describe('TeamGroupSync', () => {
|
||||
expect(await screen.findAllByRole('row')).toHaveLength(MOCK_TEAM_GROUPS.length + 1); // items plus table header
|
||||
});
|
||||
|
||||
it('should call add group', async () => {
|
||||
it('should add group', async () => {
|
||||
const { user } = setup();
|
||||
// Wait for the groups to load so the "Add group" button appears
|
||||
await screen.findAllByRole('row');
|
||||
|
||||
await user.click(screen.getAllByRole('button', { name: /add group/i })[0]);
|
||||
@@ -43,10 +42,54 @@ describe('TeamGroupSync', () => {
|
||||
const { user } = setup();
|
||||
const groupToRemove = MOCK_TEAM_GROUPS[0].groupId;
|
||||
|
||||
// Wait for group to be rendered
|
||||
await screen.findByRole('row', { name: new RegExp(groupToRemove, 'i') });
|
||||
|
||||
// Remove group
|
||||
await user.click(screen.getByRole('button', { name: `Remove group ${groupToRemove}` }));
|
||||
|
||||
await waitFor(() =>
|
||||
expect(screen.queryByRole('row', { name: new RegExp(groupToRemove, 'i') })).not.toBeInTheDocument()
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('TeamGroupSync with kubernetesExternalGroupMapping enabled', () => {
|
||||
const originalFeatureToggles = { ...config.featureToggles };
|
||||
const originalNamespace = config.namespace;
|
||||
|
||||
beforeAll(() => {
|
||||
config.featureToggles.kubernetesExternalGroupMapping = true;
|
||||
config.namespace = 'default';
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
config.featureToggles = originalFeatureToggles;
|
||||
config.namespace = originalNamespace;
|
||||
});
|
||||
|
||||
it('should render groups table', async () => {
|
||||
setup();
|
||||
expect(await screen.findAllByRole('row')).toHaveLength(MOCK_TEAM_GROUPS.length + 1); // items plus table header
|
||||
});
|
||||
|
||||
it('should add group', async () => {
|
||||
const { user } = setup();
|
||||
await screen.findAllByRole('row');
|
||||
|
||||
await user.click(screen.getAllByRole('button', { name: /add group/i })[0]);
|
||||
expect(screen.getByRole('textbox', { name: /add external group/i })).toBeVisible();
|
||||
|
||||
await user.type(screen.getByRole('textbox', { name: /add external group/i }), 'new-group');
|
||||
await user.click(screen.getAllByRole('button', { name: /add group/i })[1]);
|
||||
|
||||
expect(await screen.findByRole('row', { name: /new-group/i })).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should remove group', async () => {
|
||||
const { user } = setup();
|
||||
const groupToRemove = MOCK_TEAM_GROUPS[0].groupId;
|
||||
|
||||
await screen.findByRole('row', { name: new RegExp(groupToRemove, 'i') });
|
||||
|
||||
await user.click(screen.getByRole('button', { name: `Remove group ${groupToRemove}` }));
|
||||
|
||||
await waitFor(() =>
|
||||
|
||||
@@ -1,12 +1,7 @@
|
||||
import { css, cx } from '@emotion/css';
|
||||
import { FormEventHandler, useState } from 'react';
|
||||
|
||||
import {
|
||||
TeamGroupDto,
|
||||
useAddTeamGroupApiMutation,
|
||||
useGetTeamGroupsApiQuery,
|
||||
useRemoveTeamGroupApiQueryMutation,
|
||||
} from '@grafana/api-clients/rtkq/legacy';
|
||||
import { TeamGroupDto } from '@grafana/api-clients/rtkq/legacy';
|
||||
import { Trans, t } from '@grafana/i18n';
|
||||
import { Input, Tooltip, Icon, Button, useTheme2, InlineField, InlineFieldRow, useStyles2 } from '@grafana/ui';
|
||||
import { SlideDown } from 'app/core/components/Animations/SlideDown';
|
||||
@@ -15,6 +10,8 @@ import EmptyListCTA from 'app/core/components/EmptyListCTA/EmptyListCTA';
|
||||
import { UpgradeBox, UpgradeContent, UpgradeContentProps } from 'app/core/components/Upgrade/UpgradeBox';
|
||||
import { highlightTrial } from 'app/features/admin/utils';
|
||||
|
||||
import { useAddExternalGroupMapping, useGetExternalGroupMappings, useRemoveExternalGroupMapping } from './hooks';
|
||||
|
||||
interface Props {
|
||||
isReadOnly: boolean;
|
||||
teamUid: string;
|
||||
@@ -27,9 +24,9 @@ export const TeamGroupSync = ({ isReadOnly, teamUid }: Props) => {
|
||||
const [newGroupId, setNewGroupId] = useState('');
|
||||
const styles = useStyles2(getStyles);
|
||||
|
||||
const { data: groups = [] } = useGetTeamGroupsApiQuery({ teamId: teamUid });
|
||||
const [addTeamGroup] = useAddTeamGroupApiMutation();
|
||||
const [removeTeamGroup] = useRemoveTeamGroupApiQueryMutation();
|
||||
const { data: groups = [] } = useGetExternalGroupMappings({ teamId: teamUid });
|
||||
const [addTeamGroup] = useAddExternalGroupMapping();
|
||||
const [removeTeamGroup] = useRemoveExternalGroupMapping();
|
||||
|
||||
const onToggleAdding = () => {
|
||||
setIsAddBoxVisible(!isAddBoxVisible);
|
||||
@@ -46,11 +43,12 @@ export const TeamGroupSync = ({ isReadOnly, teamUid }: Props) => {
|
||||
setNewGroupId('');
|
||||
};
|
||||
|
||||
const onRemoveGroup = async (groupId: string | undefined) => {
|
||||
if (!groupId) {
|
||||
const onRemoveGroup = async (group: TeamGroupDto) => {
|
||||
if (!group.groupId) {
|
||||
return;
|
||||
}
|
||||
await removeTeamGroup({ teamId: teamUid, groupId });
|
||||
// group.uid is always defined here because it comes from the API
|
||||
await removeTeamGroup({ teamId: teamUid, groupId: group.groupId, uid: group.uid! });
|
||||
};
|
||||
|
||||
const isNewGroupValid = () => {
|
||||
@@ -65,7 +63,7 @@ export const TeamGroupSync = ({ isReadOnly, teamUid }: Props) => {
|
||||
<Button
|
||||
size="sm"
|
||||
variant="destructive"
|
||||
onClick={() => onRemoveGroup(group.groupId)}
|
||||
onClick={() => onRemoveGroup(group)}
|
||||
disabled={isReadOnly}
|
||||
aria-label={t('teams.team-group-sync.aria-label-remove', 'Remove group {{groupName}}', {
|
||||
groupName: group.groupId,
|
||||
|
||||
@@ -1,6 +1,18 @@
|
||||
import { skipToken } from '@reduxjs/toolkit/query';
|
||||
import { useEffect, useMemo } from 'react';
|
||||
|
||||
import {
|
||||
useCreateExternalGroupMappingMutation,
|
||||
useListExternalGroupMappingQuery,
|
||||
useDeleteExternalGroupMappingMutation,
|
||||
} from '@grafana/api-clients/rtkq/iam/v0alpha1';
|
||||
import {
|
||||
useAddTeamGroupApiMutation,
|
||||
useGetTeamGroupsApiQuery,
|
||||
useRemoveTeamGroupApiQueryMutation,
|
||||
TeamGroupDto,
|
||||
} from '@grafana/api-clients/rtkq/legacy';
|
||||
import { config } from '@grafana/runtime';
|
||||
import {
|
||||
useSearchTeamsQuery as useLegacySearchTeamsQuery,
|
||||
useCreateTeamMutation,
|
||||
@@ -152,3 +164,71 @@ export const useCreateTeam = () => {
|
||||
|
||||
return [trigger, response] as const;
|
||||
};
|
||||
|
||||
export const useGetExternalGroupMappings = (args: { teamId: string }) => {
|
||||
const shouldUseAppPlatform = Boolean(config.featureToggles.kubernetesExternalGroupMapping);
|
||||
|
||||
const legacyResult = useGetTeamGroupsApiQuery(args, { skip: shouldUseAppPlatform });
|
||||
|
||||
const { data: newApiData, ...newApiRest } = useListExternalGroupMappingQuery({}, { skip: !shouldUseAppPlatform });
|
||||
|
||||
const groups: TeamGroupDto[] = useMemo(() => {
|
||||
// FIXME: Consider using the search API which has sorting support
|
||||
return (newApiData?.items || [])
|
||||
.filter((item) => item.spec.teamRef.name === args.teamId)
|
||||
.map((item) => ({
|
||||
groupId: item.spec.externalGroupId,
|
||||
uid: item.metadata.name,
|
||||
}));
|
||||
}, [newApiData, args.teamId]);
|
||||
|
||||
if (shouldUseAppPlatform) {
|
||||
return {
|
||||
...newApiRest,
|
||||
data: groups,
|
||||
};
|
||||
}
|
||||
return legacyResult;
|
||||
};
|
||||
|
||||
export const useAddExternalGroupMapping = () => {
|
||||
const legacyMutation = useAddTeamGroupApiMutation();
|
||||
|
||||
const [addNew, newResult] = useCreateExternalGroupMappingMutation();
|
||||
|
||||
const add = async (args: { teamId: string; teamGroupMapping: { groupId: string } }) => {
|
||||
return addNew({
|
||||
externalGroupMapping: {
|
||||
metadata: {
|
||||
generateName: 'external-group-mapping-',
|
||||
},
|
||||
spec: {
|
||||
externalGroupId: args.teamGroupMapping.groupId,
|
||||
teamRef: {
|
||||
name: args.teamId,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
if (!config.featureToggles.kubernetesExternalGroupMapping) {
|
||||
return legacyMutation;
|
||||
}
|
||||
return [add, newResult] as const;
|
||||
};
|
||||
|
||||
export const useRemoveExternalGroupMapping = () => {
|
||||
const legacyMutation = useRemoveTeamGroupApiQueryMutation();
|
||||
|
||||
const [deleteMapping, deleteResult] = useDeleteExternalGroupMappingMutation();
|
||||
|
||||
const remove = async (args: { teamId: string; groupId: string; uid: string }) => {
|
||||
return deleteMapping({ name: args.uid });
|
||||
};
|
||||
|
||||
if (!config.featureToggles.kubernetesExternalGroupMapping) {
|
||||
return legacyMutation;
|
||||
}
|
||||
return [remove, deleteResult] as const;
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user