Skip to content

Commit

Permalink
This will add addOrRemoveResources to support the "Add/Remove to re…
Browse files Browse the repository at this point in the history
…quest" bulk action in connect (#45964)
  • Loading branch information
avatus authored Sep 4, 2024
1 parent e9805ea commit 944d79f
Show file tree
Hide file tree
Showing 5 changed files with 173 additions and 16 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -194,6 +194,7 @@ export function RequestCheckout<T extends PendingListItem>({
<ArrowBack
size="large"
mr={3}
data-testid="close-checkout"
onClick={onClose}
style={{ cursor: 'pointer' }}
/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -113,12 +113,12 @@ export type BulkAction = {
* over if this prop is supplied
*/
tooltip?: string;
action: (
selectedResources: {
unifiedResourceId: string;
resource: SharedUnifiedResource['resource'];
}[]
) => void;
action: (selectedResources: SelectedResource[]) => void;
};

export type SelectedResource = {
unifiedResourceId: string;
resource: SharedUnifiedResource['resource'];
};

export type FilterKind = {
Expand Down
88 changes: 78 additions & 10 deletions web/packages/teleterm/src/ui/DocumentCluster/UnifiedResources.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -189,6 +189,10 @@ export function UnifiedResources(props: {

const requestStarted = accessRequestsService.getAddedItemsCount() > 0;

const getAddedItemsCount = useCallback(() => {
return accessRequestsService.getAddedItemsCount();
}, [accessRequestsService]);

const getAccessRequestButton = useCallback(
(resource: UnifiedResourceResponse) => {
const isResourceAdded = addedResources?.has(resource.resource.uri);
Expand Down Expand Up @@ -219,6 +223,13 @@ export function UnifiedResources(props: {
]
);

const bulkAddResources = useCallback(
(resources: UnifiedResourceResponse[]) => {
accessRequestsService.addOrRemoveResources(resources);
},
[accessRequestsService]
);

return (
<Resources
getAccessRequestButton={getAccessRequestButton}
Expand All @@ -232,6 +243,8 @@ export function UnifiedResources(props: {
canUseConnectMyComputer={canUseConnectMyComputer}
openConnectMyComputerDocument={openConnectMyComputerDocument}
onResourcesRefreshRequest={onResourcesRefreshRequest}
bulkAddResources={bulkAddResources}
getAddedItemsCount={getAddedItemsCount}
discoverUrl={discoverUrl}
integratedAccessRequests={integratedAccessRequests}
// Reset the component state when query params object change.
Expand All @@ -254,7 +267,9 @@ const Resources = memo(
openConnectMyComputerDocument(): void;
onResourcesRefreshRequest: ResourcesContext['onResourcesRefreshRequest'];
discoverUrl: string;
getAccessRequestButton?: (resource: UnifiedResourceResponse) => JSX.Element;
getAccessRequestButton: (resource: UnifiedResourceResponse) => JSX.Element;
getAddedItemsCount: () => number;
bulkAddResources: (resources: UnifiedResourceResponse[]) => void;
integratedAccessRequests: IntegratedAccessRequests;
}) => {
const appContext = useAppContext();
Expand Down Expand Up @@ -321,6 +336,44 @@ const Resources = memo(
return cleanup;
}, [onResourcesRefreshRequest, fetch]);

const { getAccessRequestButton } = props;
// The action callback in the requestAccess action has access to
// `SharedUnifiedResource['resource']`, but `props.bulkAddResources` accepts
// `UnifiedResourceResponse`. Because of that, we need to to have the
// getUnifiedResourceFromSharedResource function.
const { sharedResources, getUnifiedResourceFromSharedResource } =
useMemo(() => {
const sharedResources: SharedUnifiedResource[] = [];
const sharedResourceToUnifiedResource = new Map<
SharedUnifiedResource['resource'],
UnifiedResourceResponse
>();

resources.forEach(resource => {
let sharedResource = mapToSharedResource(resource);
const accessRequestButton = getAccessRequestButton(resource);
if (accessRequestButton) {
sharedResource.ui.ActionButton = accessRequestButton;
}

sharedResources.push(sharedResource);
sharedResourceToUnifiedResource.set(
sharedResource.resource,
resource
);
});

const getUnifiedResourceFromSharedResource =
sharedResourceToUnifiedResource.get.bind(
sharedResourceToUnifiedResource
);

return {
sharedResources,
getUnifiedResourceFromSharedResource,
};
}, [resources, getAccessRequestButton]);

const resourceIds =
props.userPreferences.clusterPreferences?.pinnedResources?.resourceIds;
const { updateUserPreferences } = props;
Expand All @@ -340,6 +393,29 @@ const Resources = memo(
params={props.queryParams}
setParams={props.onParamsChange}
unifiedResourcePreferencesAttempt={props.userPreferencesAttempt}
bulkActions={
props.integratedAccessRequests.supported === 'yes'
? [
{
key: 'requestAccess',
Icon: icons.AddCircle,
text:
props.getAddedItemsCount() > 0
? 'Add/Remove to Request'
: 'Request Access',
disabled: false,
action: selectedResources =>
props.bulkAddResources(
selectedResources.map(sharedResource =>
getUnifiedResourceFromSharedResource(
sharedResource.resource
)
)
),
},
]
: []
}
unifiedResourcePreferences={
props.userPreferences.unifiedResourcePreferences
}
Expand All @@ -352,15 +428,7 @@ const Resources = memo(
? props.integratedAccessRequests.availabilityFilter
: undefined
}
resources={resources.map(r => {
const { resource, ui } = mapToSharedResource(r);
return {
resource,
ui: {
ActionButton: props.getAccessRequestButton(r) || ui.ActionButton,
},
};
})}
resources={sharedResources}
resourcesFetchAttempt={attempt}
fetchResources={fetch}
availableKinds={[
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,67 @@ test('getAddedItemsCount() returns added resource count for pending request', ()
expect(service.getAddedItemsCount()).toBe(0);
});

test('addOrRemoveResources() adds all resources to pending request', async () => {
const { accessRequestsService: service } = getTestSetup(
getMockPendingResourceAccessRequest()
);
const server = makeServer({
uri: `${rootClusterUri}/servers/ser`,
hostname: 'ser',
});
const server2 = makeServer({
uri: `${rootClusterUri}/servers/ser2`,
hostname: 'ser2',
});

// add a single resource that isn't added should add to the request
await service.addOrRemoveResources([{ kind: 'server', resource: server }]);
let pendingAccessRequest = service.getPendingAccessRequest();
expect(
pendingAccessRequest.kind === 'resource' &&
pendingAccessRequest.resources.get(server.uri)
).toStrictEqual({
kind: 'server',
resource: { hostname: server.hostname, uri: server.uri },
});

// padding an array that contains some resources already added and some that aren't should add them all
await service.addOrRemoveResources([
{ kind: 'server', resource: server },
{ kind: 'server', resource: server2 },
]);
pendingAccessRequest = service.getPendingAccessRequest();
expect(
pendingAccessRequest.kind === 'resource' &&
pendingAccessRequest.resources.get(server.uri)
).toStrictEqual({
kind: 'server',
resource: { hostname: server.hostname, uri: server.uri },
});
expect(
pendingAccessRequest.kind === 'resource' &&
pendingAccessRequest.resources.get(server2.uri)
).toStrictEqual({
kind: 'server',
resource: { hostname: server2.hostname, uri: server2.uri },
});

// passing an array of resources that are all already added should remove all the passed resources
await service.addOrRemoveResources([
{ kind: 'server', resource: server },
{ kind: 'server', resource: server2 },
]);
pendingAccessRequest = service.getPendingAccessRequest();
expect(
pendingAccessRequest.kind === 'resource' &&
pendingAccessRequest.resources.get(server.uri)
).toStrictEqual(undefined);
expect(
pendingAccessRequest.kind === 'resource' &&
pendingAccessRequest.resources.get(server2.uri)
).toStrictEqual(undefined);
});

test('addOrRemoveResource() adds resource to pending request', async () => {
const { accessRequestsService: service } = getTestSetup(
getMockPendingResourceAccessRequest()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,33 @@ export class AccessRequestsService {
});
}

async addOrRemoveResources(requestedResources: ResourceRequest[]) {
if (!(await this.canUpdateRequest('resource'))) {
return;
}
this.setState(draftState => {
if (draftState.pending.kind !== 'resource') {
draftState.pending = {
kind: 'resource',
resources: new Map(),
};
}

const { resources } = draftState.pending;
const allAdded = requestedResources.every(r =>
resources.has(r.resource.uri)
);

requestedResources.forEach(request => {
if (allAdded) {
resources.delete(request.resource.uri);
} else {
resources.set(request.resource.uri, getRequiredProperties(request));
}
});
});
}

async addResource(request: ResourceRequest): Promise<void> {
if (!(await this.canUpdateRequest('resource'))) {
return;
Expand Down

0 comments on commit 944d79f

Please sign in to comment.