[ARVADOS-WORKBENCH2] created: 2.4.0-42-gf35d7197

Git user git at public.arvados.org
Mon Apr 25 21:53:09 UTC 2022


        at  f35d7197b88370eac78d26d55bf7157b523536df (commit)


commit f35d7197b88370eac78d26d55bf7157b523536df
Author: Lucas Di Pentima <lucas.dipentima at curii.com>
Date:   Mon Apr 25 18:52:35 2022 -0300

    16115: pre-vacations WIP commit.
    
    Arvados-DCO-1.1-Signed-off-by: Lucas Di Pentima <lucas.dipentima at curii.com>

diff --git a/src/services/api-client-authorization-service/api-client-authorization-service.ts b/src/services/api-client-authorization-service/api-client-authorization-service.ts
index 012fdb15..7c985dbb 100644
--- a/src/services/api-client-authorization-service/api-client-authorization-service.ts
+++ b/src/services/api-client-authorization-service/api-client-authorization-service.ts
@@ -33,11 +33,11 @@ export class ApiClientAuthorizationService extends CommonService<ApiClientAuthor
         }
         return this.list({
             filters: new FilterBuilder()
-                .addEqual("scopes", JSON.stringify([
+                .addEqual("scopes", [
                     `GET /arvados/v1/collections/${uuid}`,
                     `GET /arvados/v1/collections/${uuid}/`,
-                    "GET /arvados/v1/keep_services/accessible",
-                ])).getFilters()
+                    "GET /arvados/v1/keep_services/accessible"
+                ]).getFilters()
         });
     }
 }
\ No newline at end of file
diff --git a/src/services/api/filter-builder.ts b/src/services/api/filter-builder.ts
index d1a4fd08..4809e7a8 100644
--- a/src/services/api/filter-builder.ts
+++ b/src/services/api/filter-builder.ts
@@ -9,7 +9,7 @@ export function joinFilters(...filters: string[]) {
 export class FilterBuilder {
     constructor(private filters = "") { }
 
-    public addEqual(field: string, value?: string | boolean | null, resourcePrefix?: string) {
+    public addEqual(field: string, value?: string | string[] | boolean | null, resourcePrefix?: string) {
         return this.addCondition(field, "=", value, "", "", resourcePrefix);
     }
 
diff --git a/src/store/sharing-dialog/sharing-dialog-actions.ts b/src/store/sharing-dialog/sharing-dialog-actions.ts
index 4c0b8825..91703d5c 100644
--- a/src/store/sharing-dialog/sharing-dialog-actions.ts
+++ b/src/store/sharing-dialog/sharing-dialog-actions.ts
@@ -19,7 +19,9 @@ import { differenceWith } from "lodash";
 import { withProgress } from "store/progress-indicator/with-progress";
 import { progressIndicatorActions } from 'store/progress-indicator/progress-indicator-actions';
 import { snackbarActions, SnackbarKind } from "../snackbar/snackbar-actions";
-import { extractUuidKind, ResourceKind } from "models/resource";
+import { extractUuidKind, extractUuidObjectType, ResourceKind, ResourceObjectType } from "models/resource";
+import { ApiClientAuthorizationService } from "services/api-client-authorization-service/api-client-authorization-service";
+import { resourcesActions } from "store/resources/resources-actions";
 
 export const openSharingDialog = (resourceUuid: string, refresh?: () => void) =>
     (dispatch: Dispatch) => {
@@ -41,6 +43,7 @@ export const saveSharingDialogChanges = async (dispatch: Dispatch, getState: ()
     await dispatch<any>(sendInvitations);
     dispatch(reset(SHARING_INVITATION_FORM_NAME));
     await dispatch<any>(loadSharingDialog);
+    dispatch(progressIndicatorActions.STOP_WORKING(SHARING_DIALOG_NAME));
 
     const dialog = getDialog<SharingDialogData>(getState().dialog, SHARING_DIALOG_NAME);
     if (dialog && dialog.data.refresh) {
@@ -57,31 +60,40 @@ export const sendSharingInvitations = async (dispatch: Dispatch, getState: () =>
         kind: SnackbarKind.SUCCESS,
     }));
     dispatch(progressIndicatorActions.STOP_WORKING(SHARING_DIALOG_NAME));
-    
+
     const dialog = getDialog<SharingDialogData>(getState().dialog, SHARING_DIALOG_NAME);
     if (dialog && dialog.data.refresh) {
         dialog.data.refresh();
     }
 };
 
-interface SharingDialogData {
+export interface SharingDialogData {
     resourceUuid: string;
     refresh: () => void;
 }
 
-const loadSharingDialog = async (dispatch: Dispatch, getState: () => RootState, { permissionService }: ServiceRepository) => {
+const loadSharingDialog = async (dispatch: Dispatch, getState: () => RootState, { permissionService, apiClientAuthorizationService }: ServiceRepository) => {
 
     const dialog = getDialog<SharingDialogData>(getState().dialog, SHARING_DIALOG_NAME);
     if (dialog) {
         dispatch(progressIndicatorActions.START_WORKING(SHARING_DIALOG_NAME));
         try {
-            const { items } = await permissionService.listResourcePermissions(dialog.data.resourceUuid);
+            const resourceUuid = dialog.data.resourceUuid;
+            const { items } = await permissionService.listResourcePermissions(resourceUuid);
             dispatch<any>(initializePublicAccessForm(items));
             await dispatch<any>(initializeManagementForm(items));
-            dispatch(progressIndicatorActions.STOP_WORKING(SHARING_DIALOG_NAME));
+            // For collections, we need to load the public sharing tokens
+            if (extractUuidObjectType(resourceUuid) === ResourceObjectType.COLLECTION) {
+                const sharingTokens = await apiClientAuthorizationService.listCollectionSharingTokens(resourceUuid);
+                dispatch(resourcesActions.SET_RESOURCES([sharingTokens.items]));
+            }
         } catch (e) {
-            dispatch(snackbarActions.OPEN_SNACKBAR({ message: 'You do not have access to share this item', hideDuration: 2000, kind: SnackbarKind.ERROR }));
+            dispatch(snackbarActions.OPEN_SNACKBAR({
+                message: 'You do not have access to share this item',
+                hideDuration: 2000,
+                kind: SnackbarKind.ERROR }));
             dispatch(dialogActions.CLOSE_DIALOG({ id: SHARING_DIALOG_NAME }));
+        } finally {
             dispatch(progressIndicatorActions.STOP_WORKING(SHARING_DIALOG_NAME));
         }
     }
@@ -108,8 +120,8 @@ const initializeManagementForm = (permissionLinks: PermissionResource[]) =>
         };
 
         const managementPermissions = permissionLinks
-            .filter(item =>
-                item.tailUuid !== getPublicGroupUuid(getState()))
+            // .filter(item =>
+            //     item.tailUuid !== getPublicGroupUuid(getState()))
             .map(({ tailUuid, name, uuid }) => ({
                 email: getEmail(tailUuid),
                 permissions: name as PermissionLevel,
@@ -178,19 +190,14 @@ const saveManagementChanges = async (_: Dispatch, getState: () => RootState, { p
     const { user } = state.auth;
     const dialog = getDialog<string>(state.dialog, SHARING_DIALOG_NAME);
     if (dialog && user) {
-
         const { initialPermissions, permissions } = getSharingMangementFormData(state);
         const { visibility } = getSharingPublicAccessFormData(state);
 
-
         if (visibility === VisibilityLevel.PRIVATE) {
-
             for (const permission of initialPermissions) {
                 await permissionService.delete(permission.permissionUuid);
             }
-
         } else {
-
             const cancelledPermissions = differenceWith(
                 initialPermissions,
                 permissions,
@@ -204,7 +211,6 @@ const saveManagementChanges = async (_: Dispatch, getState: () => RootState, { p
             for (const permission of permissions) {
                 await permissionService.update(permission.permissionUuid, { name: permission.permissions });
             }
-
         }
     }
 };
diff --git a/src/views-components/sharing-dialog/advanced-view-switch.tsx b/src/views-components/sharing-dialog/advanced-view-switch.tsx
index 969128be..37517fd6 100644
--- a/src/views-components/sharing-dialog/advanced-view-switch.tsx
+++ b/src/views-components/sharing-dialog/advanced-view-switch.tsx
@@ -22,4 +22,3 @@ export const connectAdvancedViewSwitch = (Component: React.ComponentType<Advance
             return <Component {...this.state} {...this} />;
         }
     };
-    
\ No newline at end of file
diff --git a/src/views-components/sharing-dialog/sharing-dialog-component.tsx b/src/views-components/sharing-dialog/sharing-dialog-component.tsx
index be15cce6..2610e40b 100644
--- a/src/views-components/sharing-dialog/sharing-dialog-component.tsx
+++ b/src/views-components/sharing-dialog/sharing-dialog-component.tsx
@@ -3,26 +3,47 @@
 // SPDX-License-Identifier: AGPL-3.0
 
 import React from 'react';
-import { Dialog, DialogTitle, Button, Grid, DialogContent, CircularProgress, Paper } from '@material-ui/core';
+import {
+    Dialog,
+    DialogTitle,
+    Button,
+    Grid,
+    DialogContent,
+    CircularProgress,
+    Paper,
+    Tabs,
+    Tab
+} from '@material-ui/core';
+import {
+    StyleRulesCallback,
+    WithStyles,
+    withStyles
+} from '@material-ui/core/styles';
 import { DialogActions } from 'components/dialog-actions/dialog-actions';
-import { StyleRulesCallback, WithStyles, withStyles } from '@material-ui/core/styles';
-
+import { SharingDialogContent } from './sharing-dialog-content';
+import { SharingURLsContent } from './sharing-urls';
+import { extractUuidObjectType, ResourceObjectType } from 'models/resource';
 
 export interface SharingDialogDataProps {
     open: boolean;
     loading: boolean;
     saveEnabled: boolean;
-    advancedEnabled: boolean;
-    children: React.ReactNode;
+    // advancedEnabled: boolean;
+    // children: React.ReactNode;
+    sharedResourceUuid: string;
 }
 export interface SharingDialogActionProps {
     onClose: () => void;
     onExited: () => void;
     onSave: () => void;
-    onAdvanced: () => void;
+    // onAdvanced: () => void;
+    // onTabChange?: (event: React.ChangeEvent<{}>, value: number) => void;
 }
 export default (props: SharingDialogDataProps & SharingDialogActionProps) => {
-    const { children, open, loading, advancedEnabled, saveEnabled, onAdvanced, onClose, onExited, onSave } = props;
+    const { open, loading, saveEnabled, sharedResourceUuid,
+        onClose, onExited, onSave } = props;
+    const showSharingURLTab = extractUuidObjectType(sharedResourceUuid) === ResourceObjectType.COLLECTION
+    const [tabNr, setTabNr] = React.useState<number>(0);
     return <Dialog
         {...{ open, onClose, onExited }}
         className="sharing-dialog"
@@ -32,13 +53,20 @@ export default (props: SharingDialogDataProps & SharingDialogActionProps) => {
         disableEscapeKeyDown>
         <DialogTitle>
             Sharing settings
-            </DialogTitle>
+        </DialogTitle>
+        { showSharingURLTab &&
+        <Tabs value={tabNr} onChange={(_, tb) => setTabNr(tb)}>
+            <Tab label="With users/groups" />
+            <Tab label="Sharing URLs" />
+        </Tabs>
+        }
         <DialogContent>
-            {children}
+            { tabNr === 0 && <SharingDialogContent /> }
+            { tabNr === 1 && <SharingURLsContent uuid={sharedResourceUuid} />}
         </DialogContent>
         <DialogActions>
             <Grid container spacing={8}>
-                {advancedEnabled &&
+                {/* {props.advancedEnabled && !tabNr &&
                     <Grid item>
                         <Button
                             color='primary'
@@ -46,7 +74,7 @@ export default (props: SharingDialogDataProps & SharingDialogActionProps) => {
                             Advanced
                     </Button>
                     </Grid>
-                }
+                } */}
                 <Grid item xs />
                 <Grid item>
                     <Button onClick={onClose}>
diff --git a/src/views-components/sharing-dialog/sharing-dialog-content.tsx b/src/views-components/sharing-dialog/sharing-dialog-content.tsx
index 15df2245..c9a5929f 100644
--- a/src/views-components/sharing-dialog/sharing-dialog-content.tsx
+++ b/src/views-components/sharing-dialog/sharing-dialog-content.tsx
@@ -3,25 +3,21 @@
 // SPDX-License-Identifier: AGPL-3.0
 
 import React from 'react';
-import { Grid, Typography } from '@material-ui/core';
+import { Grid } from '@material-ui/core';
 
 import { SharingInvitationForm } from './sharing-invitation-form';
 import { SharingManagementForm } from './sharing-management-form';
 import { SharingPublicAccessForm } from './sharing-public-access-form';
 
-export const SharingDialogContent = (props: { advancedViewOpen: boolean }) =>
+export const SharingDialogContent = () =>
     <Grid container direction='column' spacing={24}>
-        {props.advancedViewOpen &&
-            <>
-                <Grid item>
-                    <Typography variant='subtitle1'>
-                        Who can access
-                    </Typography>
-                    <SharingPublicAccessForm />
-                    <SharingManagementForm />
-                </Grid>
-            </>
-        }
+        <Grid item>
+            {/* <Typography variant='subtitle1'>
+                Who can access
+            </Typography> */}
+            {/* <SharingPublicAccessForm /> */}
+            <SharingManagementForm />
+        </Grid>
         <Grid item>
             <SharingInvitationForm />
         </Grid>
diff --git a/src/views-components/sharing-dialog/sharing-dialog.tsx b/src/views-components/sharing-dialog/sharing-dialog.tsx
index fe3b8396..feddc206 100644
--- a/src/views-components/sharing-dialog/sharing-dialog.tsx
+++ b/src/views-components/sharing-dialog/sharing-dialog.tsx
@@ -2,50 +2,66 @@
 //
 // SPDX-License-Identifier: AGPL-3.0
 
+import React from 'react';
 import { compose, Dispatch } from 'redux';
 import { connect } from 'react-redux';
-
-import React from 'react';
-import { connectSharingDialog, saveSharingDialogChanges, connectSharingDialogProgress, sendSharingInvitations } from 'store/sharing-dialog/sharing-dialog-actions';
-import { WithDialogProps } from 'store/dialog/with-dialog';
 import { RootState } from 'store/store';
-
-import SharingDialogComponent, { SharingDialogDataProps, SharingDialogActionProps } from './sharing-dialog-component';
+import {
+    connectSharingDialog,
+    saveSharingDialogChanges,
+    connectSharingDialogProgress,
+    sendSharingInvitations,
+    SharingDialogData
+} from 'store/sharing-dialog/sharing-dialog-actions';
+import { WithDialogProps } from 'store/dialog/with-dialog';
+import SharingDialogComponent, {
+    SharingDialogDataProps,
+    SharingDialogActionProps
+} from './sharing-dialog-component';
 import { SharingDialogContent } from './sharing-dialog-content';
-import { connectAdvancedViewSwitch, AdvancedViewSwitchInjectedProps } from './advanced-view-switch';
-import { hasChanges } from 'store/sharing-dialog/sharing-dialog-types';
+import {
+    connectAdvancedViewSwitch,
+    AdvancedViewSwitchInjectedProps
+} from './advanced-view-switch';
+import { hasChanges, SHARING_DIALOG_NAME } from 'store/sharing-dialog/sharing-dialog-types';
 import { WithProgressStateProps } from 'store/progress-indicator/with-progress';
+import { getDialog } from 'store/dialog/dialog-reducer';
+import { extractUuidObjectType, ResourceObjectType } from 'models/resource';
 
-type Props = WithDialogProps<string> & AdvancedViewSwitchInjectedProps & WithProgressStateProps;
+type Props = WithDialogProps<string> & WithProgressStateProps;
 
-const mapStateToProps = (state: RootState, { advancedViewOpen, working, ...props }: Props): SharingDialogDataProps => ({
+const mapStateToProps = (state: RootState, { working, ...props }: Props): SharingDialogDataProps => {
+    const dialog = getDialog<SharingDialogData>(state.dialog, SHARING_DIALOG_NAME);
+    return ({
     ...props,
     saveEnabled: hasChanges(state),
     loading: working,
-    advancedEnabled: !advancedViewOpen,
-    children: <SharingDialogContent {...{ advancedViewOpen }} />,
-});
+    // advancedEnabled: !advancedViewOpen,
+    // children: <SharingDialogContent />,
+    sharedResourceUuid: dialog?.data.resourceUuid || '',
+    })
+};
 
-const mapDispatchToProps = (dispatch: Dispatch, { toggleAdvancedView, advancedViewOpen, ...props }: Props): SharingDialogActionProps => ({
+const mapDispatchToProps = (dispatch: Dispatch, { ...props }: Props): SharingDialogActionProps => ({
     ...props,
     onClose: props.closeDialog,
     onExited: () => {
-        if (advancedViewOpen) {
-            toggleAdvancedView();
-        }
+        // if (advancedViewOpen) {
+        //     toggleAdvancedView();
+        // }
     },
     onSave: () => {
-        if (advancedViewOpen) {
+        // if (advancedViewOpen) {
             dispatch<any>(saveSharingDialogChanges);
-        } else {
-            dispatch<any>(sendSharingInvitations);
-        }
+        // } else {
+            // dispatch<any>(sendSharingInvitations);
+        // }
     },
-    onAdvanced: toggleAdvancedView,
+    // onAdvanced: toggleAdvancedView,
 });
 
 export const SharingDialog = compose(
-    connectAdvancedViewSwitch,
+    // connectAdvancedViewSwitch,
     connectSharingDialog,
     connectSharingDialogProgress,
     connect(mapStateToProps, mapDispatchToProps)
diff --git a/src/views-components/sharing-dialog/sharing-urls-component.tsx b/src/views-components/sharing-dialog/sharing-urls-component.tsx
new file mode 100644
index 00000000..0d54fa8c
--- /dev/null
+++ b/src/views-components/sharing-dialog/sharing-urls-component.tsx
@@ -0,0 +1,33 @@
+// Copyright (C) The Arvados Authors. All rights reserved.
+//
+// SPDX-License-Identifier: AGPL-3.0
+
+import React from 'react';
+import {
+    Grid,
+    Typography
+} from '@material-ui/core';
+import { ApiClientAuthorization } from 'models/api-client-authorization';
+
+export interface SharingURLsComponentDataProps {
+    sharingTokens: ApiClientAuthorization[];
+    sharingURLsPrefix: string;
+}
+
+export interface SharingURLsComponentActionProps {
+    onCreateSharingToken: (colUuid: string) => void;
+    onDeleteSharingToken: (uuid: string) => void;
+}
+
+type SharingURLsComponentProps = SharingURLsComponentDataProps & SharingURLsComponentActionProps;
+
+export const SharingURLsComponent = (props: SharingURLsComponentProps ) =>
+    <Grid container direction='column' spacing={24}>
+        {props.sharingTokens.map(token => (
+        <Grid item key={token.uuid}>
+            <Typography>
+                Sharing URLsss
+            </Typography>
+        </Grid>
+        ))}
+    </Grid>
diff --git a/src/views-components/sharing-dialog/sharing-urls.tsx b/src/views-components/sharing-dialog/sharing-urls.tsx
new file mode 100644
index 00000000..86051fb3
--- /dev/null
+++ b/src/views-components/sharing-dialog/sharing-urls.tsx
@@ -0,0 +1,44 @@
+// Copyright (C) The Arvados Authors. All rights reserved.
+//
+// SPDX-License-Identifier: AGPL-3.0
+
+import React from 'react';
+import { RootState } from 'store/store';
+import { connect } from 'react-redux';
+import { Dispatch } from 'redux';
+import { ApiClientAuthorization } from 'models/api-client-authorization';
+import { filterResources } from 'store/resources/resources';
+import { ResourceKind } from 'models/resource';
+import {
+    SharingURLsComponent,
+    SharingURLsComponentActionProps,
+    SharingURLsComponentDataProps
+} from './sharing-urls-component';
+
+const mapStateToProps =
+    (state: RootState, ownProps: { uuid: string }): SharingURLsComponentDataProps => {
+        const sharingTokens = filterResources(
+            (resource: ApiClientAuthorization) =>
+                resource.kind === ResourceKind.API_CLIENT_AUTHORIZATION &&
+                resource.scopes.includes(`GET /arvados/v1/collections/${ownProps.uuid}`) &&
+                resource.scopes.includes(`GET /arvados/v1/collections/${ownProps.uuid}/`) &&
+                resource.scopes.includes('GET /arvados/v1/keep_services/accessible')
+            )(state.resources) as ApiClientAuthorization[];
+        const sharingURLsPrefix = state.auth.config.keepWebServiceUrl;
+        return {
+            sharingTokens,
+            sharingURLsPrefix,
+        }
+    }
+
+const mapDispatchToProps = (dispatch: Dispatch): SharingURLsComponentActionProps => ({
+    onCreateSharingToken(colUuid: string) {
+        // dispatch(createSharingToken(colUuid));
+    },
+    onDeleteSharingToken(uuid: string) {
+        // dispatch(deleteSharingToken(uuid));
+    }
+})
+
+export const SharingURLsContent = connect(mapStateToProps, mapDispatchToProps)(SharingURLsComponent)
+

commit 042f8362da7b7e835d2451169eecafc5ea4d258d
Author: Lucas Di Pentima <lucas.dipentima at curii.com>
Date:   Thu Apr 21 15:03:09 2022 -0300

    16115: Removes unused import.
    
    Arvados-DCO-1.1-Signed-off-by: Lucas Di Pentima <lucas.dipentima at curii.com>

diff --git a/src/services/api-client-authorization-service/api-client-authorization-service.test.ts b/src/services/api-client-authorization-service/api-client-authorization-service.test.ts
index cc1942cf..31fa046e 100644
--- a/src/services/api-client-authorization-service/api-client-authorization-service.test.ts
+++ b/src/services/api-client-authorization-service/api-client-authorization-service.test.ts
@@ -3,7 +3,6 @@
 // SPDX-License-Identifier: AGPL-3.0
 
 import axios, { AxiosInstance } from "axios";
-// import MockAdapter from 'axios-mock-adapter';
 import { ApiClientAuthorizationService } from "./api-client-authorization-service";
 
 
@@ -12,7 +11,6 @@ describe('ApiClientAuthorizationService', () => {
     let serverApi: AxiosInstance;
     let actions;
 
-
     beforeEach(() => {
         serverApi = axios.create();
         actions = {

commit 7baacecfced2112da01ae9b9709109d63f4dfcc3
Author: Lucas Di Pentima <lucas.dipentima at curii.com>
Date:   Mon Apr 18 16:43:39 2022 -0300

    16115: Adds sharing token management methods to ACA service, with tests.
    
    Arvados-DCO-1.1-Signed-off-by: Lucas Di Pentima <lucas.dipentima at curii.com>

diff --git a/src/services/api-client-authorization-service/api-client-authorization-service.test.ts b/src/services/api-client-authorization-service/api-client-authorization-service.test.ts
new file mode 100644
index 00000000..cc1942cf
--- /dev/null
+++ b/src/services/api-client-authorization-service/api-client-authorization-service.test.ts
@@ -0,0 +1,70 @@
+// Copyright (C) The Arvados Authors. All rights reserved.
+//
+// SPDX-License-Identifier: AGPL-3.0
+
+import axios, { AxiosInstance } from "axios";
+// import MockAdapter from 'axios-mock-adapter';
+import { ApiClientAuthorizationService } from "./api-client-authorization-service";
+
+
+describe('ApiClientAuthorizationService', () => {
+    let apiClientAuthorizationService: ApiClientAuthorizationService;
+    let serverApi: AxiosInstance;
+    let actions;
+
+
+    beforeEach(() => {
+        serverApi = axios.create();
+        actions = {
+            progressFn: jest.fn(),
+        } as any;
+        apiClientAuthorizationService = new ApiClientAuthorizationService(serverApi, actions);
+    });
+
+    describe('createCollectionSharingToken', () => {
+        it('should return error on invalid collection uuid', () => {
+            expect(() => apiClientAuthorizationService.createCollectionSharingToken("foo")).toThrowError("UUID foo is not a collection");
+        });
+
+        it('should make a create request with proper scopes', async () => {
+            serverApi.post = jest.fn(() => Promise.resolve(
+                { data: { uuid: 'zzzzz-4zz18-0123456789abcde' } }
+            ));
+            const uuid = 'zzzzz-4zz18-0123456789abcde'
+            await apiClientAuthorizationService.createCollectionSharingToken(uuid);
+            expect(serverApi.post).toHaveBeenCalledWith(
+                '/api_client_authorizations', {
+                    scopes: [
+                        `GET /arvados/v1/collections/${uuid}`,
+                        `GET /arvados/v1/collections/${uuid}/`,
+                        `GET /arvados/v1/keep_services/accessible`,
+                    ]
+                }
+            );
+        });
+    });
+
+    describe('listCollectionSharingToken', () => {
+        it('should return error on invalid collection uuid', () => {
+            expect(() => apiClientAuthorizationService.listCollectionSharingTokens("foo")).toThrowError("UUID foo is not a collection");
+        });
+
+        it('should make a list request with proper scopes', async () => {
+            serverApi.get = jest.fn(() => Promise.resolve(
+                { data: { items: [{}] } }
+            ));
+            const uuid = 'zzzzz-4zz18-0123456789abcde'
+            await apiClientAuthorizationService.listCollectionSharingTokens(uuid);
+            expect(serverApi.get).toHaveBeenCalledWith(
+                `/api_client_authorizations`, {params: {
+                    filters: '[["scopes","=","' + JSON.stringify([
+                        `GET /arvados/v1/collections/${uuid}`,
+                        `GET /arvados/v1/collections/${uuid}/`,
+                        `GET /arvados/v1/keep_services/accessible`,
+                    ]) + '"]]',
+                    select: undefined,
+                }}
+            );
+        });
+    });
+});
\ No newline at end of file
diff --git a/src/services/api-client-authorization-service/api-client-authorization-service.ts b/src/services/api-client-authorization-service/api-client-authorization-service.ts
index 386c9747..012fdb15 100644
--- a/src/services/api-client-authorization-service/api-client-authorization-service.ts
+++ b/src/services/api-client-authorization-service/api-client-authorization-service.ts
@@ -5,10 +5,39 @@
 import { AxiosInstance } from "axios";
 import { ApiActions } from 'services/api/api-actions';
 import { ApiClientAuthorization } from 'models/api-client-authorization';
-import { CommonService } from 'services/common-service/common-service';
+import { CommonService, ListResults } from 'services/common-service/common-service';
+import { extractUuidObjectType, ResourceObjectType } from "models/resource";
+import { FilterBuilder } from "services/api/filter-builder";
 
 export class ApiClientAuthorizationService extends CommonService<ApiClientAuthorization> {
     constructor(serverApi: AxiosInstance, actions: ApiActions) {
         super(serverApi, "api_client_authorizations", actions);
     }
-} 
\ No newline at end of file
+
+    createCollectionSharingToken(uuid: string): Promise<ApiClientAuthorization> {
+        if (extractUuidObjectType(uuid) !== ResourceObjectType.COLLECTION) {
+            throw new Error(`UUID ${uuid} is not a collection`);
+        }
+        return this.create({
+            scopes: [
+                `GET /arvados/v1/collections/${uuid}`,
+                `GET /arvados/v1/collections/${uuid}/`,
+                `GET /arvados/v1/keep_services/accessible`,
+            ]
+        });
+    }
+
+    listCollectionSharingTokens(uuid: string): Promise<ListResults<ApiClientAuthorization>> {
+        if (extractUuidObjectType(uuid) !== ResourceObjectType.COLLECTION) {
+            throw new Error(`UUID ${uuid} is not a collection`);
+        }
+        return this.list({
+            filters: new FilterBuilder()
+                .addEqual("scopes", JSON.stringify([
+                    `GET /arvados/v1/collections/${uuid}`,
+                    `GET /arvados/v1/collections/${uuid}/`,
+                    "GET /arvados/v1/keep_services/accessible",
+                ])).getFilters()
+        });
+    }
+}
\ No newline at end of file

-----------------------------------------------------------------------


hooks/post-receive
-- 




More information about the arvados-commits mailing list