Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix-missed-ad-group-message #1012

Merged
merged 13 commits into from
Apr 24, 2024
22 changes: 22 additions & 0 deletions src/components/component/unknown-ad-groups-alert.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { Alert } from '../alert';
import { List } from '@equinor/eds-core-react';

interface Props {
unknownADGroups?: Array<string>;
}
export function UnknownADGroupsAlert({ unknownADGroups }: Props) {
return (
<>
{unknownADGroups?.length > 0 && (
<Alert type="danger">
Unknown or deleted AD group(s)
<List className="o-indent-list">
{unknownADGroups.map((adGroup) => (
<List.Item key={adGroup}>{adGroup}</List.Item>
))}
</List>
</Alert>
)}
</>
);
}
93 changes: 61 additions & 32 deletions src/components/graph/adGroups.tsx
Original file line number Diff line number Diff line change
@@ -1,23 +1,26 @@
import { Typography } from '@equinor/eds-core-react';
import { debounce } from 'lodash';
import * as PropTypes from 'prop-types';
import { ActionMeta, OnChangeValue } from 'react-select';
import { ActionMeta, CSSObjectWithLabel, OnChangeValue } from 'react-select';
import AsyncSelect from 'react-select/async';

import {
AdGroup,
msGraphApi,
useGetAdGroupsQuery,
} from '../../store/ms-graph-api';
import { UnknownADGroupsAlert } from '../component/unknown-ad-groups-alert';
import AsyncResource from '../async-resource/async-resource';

type DisplayAdGroups = AdGroup & { deleted?: boolean };

type SearchGroupFunctionType = ReturnType<
typeof msGraphApi.endpoints.searchAdGroups.useLazyQuery
>[0];

const loadOptions = debounce(
(
callback: (options: Array<AdGroup>) => void,
callback: (options: Array<DisplayAdGroups>) => void,
searchGroup: SearchGroupFunctionType,
value: string
) => filterOptions(searchGroup, value).then(callback),
Expand All @@ -31,9 +34,19 @@ async function filterOptions(
return (await searchGroups({ groupName, limit: 10 }).unwrap()).value;
}

function selectValueStyle(
base: CSSObjectWithLabel,
props: { data: DisplayAdGroups }
): CSSObjectWithLabel {
if (props.data.deleted) {
base.backgroundColor = 'var(--eds_interactive_danger__highlight)';
}
return base;
}

export type HandleAdGroupsChangeCB = (
value: OnChangeValue<AdGroup, true>,
actionMeta: ActionMeta<AdGroup>
value: OnChangeValue<DisplayAdGroups, true>,
actionMeta: ActionMeta<DisplayAdGroups>
) => void;
interface Props {
handleAdGroupsChange: HandleAdGroupsChangeCB;
Expand All @@ -49,37 +62,48 @@ export function ADGroups({
ids: adGroups ?? [],
});
const [searchGroups] = msGraphApi.endpoints.searchAdGroups.useLazyQuery();
const displayGroups = adGroups
?.map((id) => ({ id, info: groupsInfo?.find((g) => g.id === id) }))
.map<DisplayAdGroups>((g) => ({
id: g.id,
displayName: g.info?.displayName ?? g.id,
deleted: !g.info,
}));

const unknownADGroups = adGroups?.filter(
(adGroupId) => !groupsInfo?.some((adGroup) => adGroup.id === adGroupId)
);

return (
<AsyncResource asyncState={state}>
{groupsInfo && (
<>
<AsyncSelect
isMulti
name="ADGroups"
menuPosition="fixed"
closeMenuOnScroll={(e: Event) => {
const target = e.target as HTMLInputElement;
return (
target?.parentElement?.className &&
!target.parentElement.className.match(/menu/)
);
}}
noOptionsMessage={() => null}
loadOptions={(inputValue, callback) => {
inputValue?.length < 3
? callback([])
: loadOptions(callback, searchGroups, inputValue);
}}
onChange={handleAdGroupsChange}
getOptionLabel={({ displayName }) => displayName}
getOptionValue={({ id }) => id}
closeMenuOnSelect={false}
defaultValue={groupsInfo}
isDisabled={isDisabled}
/>
</>
)}
<AsyncSelect
isMulti
name="ADGroups"
menuPosition="fixed"
closeMenuOnScroll={(e: Event) => {
const target = e.target as HTMLInputElement;
return (
target?.parentElement?.className &&
!target.parentElement.className.match(/menu/)
);
}}
noOptionsMessage={() => null}
loadOptions={(inputValue, callback) => {
inputValue?.length < 3
? callback([])
: loadOptions(callback, searchGroups, inputValue);
}}
onChange={handleAdGroupsChange}
getOptionLabel={({ displayName }) => displayName}
getOptionValue={({ id }) => id}
closeMenuOnSelect={false}
defaultValue={displayGroups}
isDisabled={isDisabled}
styles={{
multiValueLabel: selectValueStyle,
multiValueRemove: selectValueStyle,
}}
/>
<Typography
className="helpertext"
group="input"
Expand All @@ -88,6 +112,11 @@ export function ADGroups({
>
Azure Active Directory groups (type 3 characters to search)
</Typography>
{!state.isFetching && unknownADGroups?.length > 0 && (
<UnknownADGroupsAlert
unknownADGroups={unknownADGroups}
></UnknownADGroupsAlert>
)}
</AsyncResource>
);
}
Expand Down
2 changes: 1 addition & 1 deletion src/components/page-configuration/change-admin-form.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ export default function ChangeAdminForm({ registration, refetch }: Props) {

return (
<Accordion className="accordion" chevronPosition="right">
<Accordion.Item style={{ overflow: 'visible' }}>
<Accordion.Item style={{ overflow: 'visible' }} isExpanded={true}>
<Accordion.Header>
<Accordion.HeaderTitle>
<Typography>Access control</Typography>
Expand Down
56 changes: 38 additions & 18 deletions src/components/page-configuration/overview.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,15 @@
import { List, Tooltip, Typography } from '@equinor/eds-core-react';
import {
CircularProgress,
List,
Tooltip,
Typography,
} from '@equinor/eds-core-react';
import * as PropTypes from 'prop-types';

import { Alert } from '../alert';
import AsyncResource from '../async-resource/async-resource';
import { useGetAdGroupsQuery } from '../../store/ms-graph-api';
import { UnknownADGroupsAlert } from '../component/unknown-ad-groups-alert';
import AsyncResource from '../async-resource/async-resource';

interface Props {
adGroups?: Array<string>;
Expand All @@ -12,6 +18,9 @@ interface Props {

export function Overview({ adGroups, appName }: Props) {
const { data, ...state } = useGetAdGroupsQuery({ ids: adGroups });
const unknownADGroups = adGroups?.filter(
(adGroupId) => !data?.some((adGroup) => adGroup.id === adGroupId)
);

return (
<div className="grid grid--gap-medium">
Expand All @@ -32,22 +41,33 @@ export function Overview({ adGroups, appName }: Props) {
</Tooltip>{' '}
groups):
</Typography>
<AsyncResource asyncState={state}>
<List className="grid grid--gap-small">
{data?.map(({ id, displayName }) => (
<List.Item key={id}>
<Typography
link
href={`https://portal.azure.com/#blade/Microsoft_AAD_IAM/GroupDetailsMenuBlade/Overview/groupId/${id}`}
target="_blank"
rel="noopener noreferrer"
>
{displayName}
</Typography>
</List.Item>
))}
</List>
</AsyncResource>
{state.isLoading ? (
<>
<CircularProgress size={24} /> Updating…
</>
) : (
<AsyncResource asyncState={state}>
<List className="grid grid--gap-small">
{data?.map(({ id, displayName }) => (
<List.Item key={id}>
<Typography
link
href={`https://portal.azure.com/#blade/Microsoft_AAD_IAM/GroupDetailsMenuBlade/Overview/groupId/${id}`}
target="_blank"
rel="noopener noreferrer"
>
{displayName}
</Typography>
</List.Item>
))}
</List>
{!state.isFetching && unknownADGroups?.length > 0 && (
<UnknownADGroupsAlert
unknownADGroups={unknownADGroups}
></UnknownADGroupsAlert>
)}
</AsyncResource>
)}
</>
) : (
<Alert type="warning">
Expand Down
1 change: 0 additions & 1 deletion src/store/ms-graph-api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,6 @@ type GetAdGroupArg = {
};
type GetAdGroupResponse = AdGroup;
export type AdGroup = {
'@odaga.context': string;
displayName: string;
id: string;
};
Expand Down