[arvados-workbench2] created: 2.4.1-5-g41f1383f
git repository hosting
git at public.arvados.org
Wed Aug 3 20:31:33 UTC 2022
at 41f1383f355af8340d789f4b364d6eb72de4497d (commit)
commit 41f1383f355af8340d789f4b364d6eb72de4497d
Author: Stephen Smith <stephen at curii.com>
Date: Tue Aug 2 11:46:53 2022 -0400
Merge branch '19305-project-update-dialog-properties' into main. Closes #19305
Arvados-DCO-1.1-Signed-off-by: Stephen Smith <stephen at curii.com>
diff --git a/cypress/integration/project.spec.js b/cypress/integration/project.spec.js
index c4983e3e..b2f6f33d 100644
--- a/cypress/integration/project.spec.js
+++ b/cypress/integration/project.spec.js
@@ -85,6 +85,39 @@ describe('Project tests', function() {
// Pink is not in the test vocab
{IDTAGCOLORS: ['IDVALCOLORS3', 'Pink', 'IDVALCOLORS1']});
});
+
+ // Open project edit via breadcrumbs
+ cy.get('[data-cy=breadcrumbs]').contains(projName).rightclick();
+ cy.get('[data-cy=context-menu]').contains('Edit').click();
+ cy.get('[data-cy=form-dialog]').within(() => {
+ cy.get('[data-cy=resource-properties-list]').within(() => {
+ cy.get('div[role=button]').contains('Color: Magenta');
+ cy.get('div[role=button]').contains('Color: Pink');
+ cy.get('div[role=button]').contains('Color: Yellow');
+ });
+ });
+ // Add another property
+ cy.get('[data-cy=resource-properties-form]').within(() => {
+ cy.get('[data-cy=property-field-key]').within(() => {
+ cy.get('input').type('Animal');
+ });
+ cy.get('[data-cy=property-field-value]').within(() => {
+ cy.get('input').type('Dog');
+ });
+ cy.root().submit();
+ });
+ cy.get('[data-cy=form-submit-btn]').click();
+ // Reopen edit via breadcrumbs and verify properties
+ cy.get('[data-cy=breadcrumbs]').contains(projName).rightclick();
+ cy.get('[data-cy=context-menu]').contains('Edit').click();
+ cy.get('[data-cy=form-dialog]').within(() => {
+ cy.get('[data-cy=resource-properties-list]').within(() => {
+ cy.get('div[role=button]').contains('Color: Magenta');
+ cy.get('div[role=button]').contains('Color: Pink');
+ cy.get('div[role=button]').contains('Color: Yellow');
+ cy.get('div[role=button]').contains('Animal: Dog');
+ });
+ });
});
it('creates new project on home project and then a subproject inside it', function() {
diff --git a/src/store/projects/project-update-actions.ts b/src/store/projects/project-update-actions.ts
index 52abfd3f..a6e67485 100644
--- a/src/store/projects/project-update-actions.ts
+++ b/src/store/projects/project-update-actions.ts
@@ -22,6 +22,8 @@ import { projectPanelActions } from 'store/project-panel/project-panel-action';
import { GroupClass } from "models/group";
import { Participant } from "views-components/sharing-dialog/participant-select";
import { ProjectProperties } from "./project-create-actions";
+import { getResource } from "store/resources/resources";
+import { ProjectResource } from "models/project";
export interface ProjectUpdateFormDialogData {
uuid: string;
@@ -37,7 +39,9 @@ export const PROJECT_UPDATE_FORM_SELECTOR = formValueSelector(PROJECT_UPDATE_FOR
export const openProjectUpdateDialog = (resource: ProjectUpdateFormDialogData) =>
(dispatch: Dispatch, getState: () => RootState) => {
- dispatch(initialize(PROJECT_UPDATE_FORM_NAME, resource));
+ // Get complete project resource from store to handle consumers passing in partial resources
+ const project = getResource<ProjectResource>(resource.uuid)(getState().resources);
+ dispatch(initialize(PROJECT_UPDATE_FORM_NAME, project));
dispatch(dialogActions.OPEN_DIALOG({
id: PROJECT_UPDATE_FORM_NAME,
data: {
diff --git a/src/views-components/resource-properties/resource-properties-list.tsx b/src/views-components/resource-properties/resource-properties-list.tsx
index a7b58252..47d7729b 100644
--- a/src/views-components/resource-properties/resource-properties-list.tsx
+++ b/src/views-components/resource-properties/resource-properties-list.tsx
@@ -38,7 +38,7 @@ ResourcePropertiesListActionProps & WithStyles<CssRules>;
const List = withStyles(styles)(
({ classes, handleDelete, properties }: ResourcePropertiesListProps) =>
- <div>
+ <div data-cy="resource-properties-list">
{properties &&
Object.keys(properties).map(k =>
Array.isArray(properties[k])
@@ -63,4 +63,4 @@ export const resourcePropertiesList = (formName: string) =>
(dispatch: Dispatch): ResourcePropertiesListActionProps => ({
handleDelete: (key: string, value: string) => dispatch<any>(removePropertyFromResourceForm(key, value, formName))
})
- )(List);
\ No newline at end of file
+ )(List);
commit f0e2e73a18bff1e73c799f2da16bbc66210edf12
Author: Stephen Smith <stephen at curii.com>
Date: Fri Jul 22 16:51:25 2022 -0400
Merge branch '18965-login-flow-destination' into main. Closes #18965
Arvados-DCO-1.1-Signed-off-by: Stephen Smith <stephen at curii.com>
diff --git a/src/services/auth-service/auth-service.ts b/src/services/auth-service/auth-service.ts
index 548dbcaa..52bfa29e 100644
--- a/src/services/auth-service/auth-service.ts
+++ b/src/services/auth-service/auth-service.ts
@@ -68,13 +68,16 @@ export class AuthService {
}
}
+ public setTargetUrl(url: string) {
+ localStorage.setItem(TARGET_URL, url);
+ }
+
public removeTargetURL() {
localStorage.removeItem(TARGET_URL);
- sessionStorage.removeItem(TARGET_URL);
}
public getTargetURL() {
- return this.getStorage().getItem(TARGET_URL);
+ return localStorage.getItem(TARGET_URL);
}
public removeApiToken() {
@@ -113,7 +116,7 @@ export class AuthService {
const currentUrl = `${window.location.protocol}//${window.location.host}/token`;
const homeClusterHost = remoteHosts[homeCluster];
const rd = new URL(window.location.href);
- this.getStorage().setItem(TARGET_URL, rd.pathname + rd.search);
+ this.setTargetUrl(rd.pathname + rd.search);
window.location.assign(`https://${homeClusterHost}/login?${(uuidPrefix !== homeCluster && homeCluster !== loginCluster) ? "remote=" + uuidPrefix + "&" : ""}return_to=${currentUrl}`);
}
diff --git a/src/views-components/login-form/login-form.tsx b/src/views-components/login-form/login-form.tsx
index aac13642..3aa9e3f2 100644
--- a/src/views-components/login-form/login-form.tsx
+++ b/src/views-components/login-form/login-form.tsx
@@ -12,6 +12,7 @@ import { AxiosPromise } from 'axios';
import { DispatchProp } from 'react-redux';
import { saveApiToken } from 'store/auth/auth-action';
import { navigateToRootProject } from 'store/navigation/navigation-action';
+import { replace } from 'react-router-redux';
type CssRules = 'root' | 'loginBtn' | 'card' | 'wrapper' | 'progress';
@@ -87,8 +88,11 @@ export const LoginForm = withStyles(styles)(
setSubmitting(false);
if (response.data.uuid && response.data.api_token) {
const apiToken = `v2/${response.data.uuid}/${response.data.api_token}`;
+ const rd = new URL(window.location.href);
+ const rdUrl = rd.pathname + rd.search;
dispatch<any>(saveApiToken(apiToken)).finally(
- () => dispatch(navigateToRootProject));
+ () => rdUrl === '/' ? dispatch(navigateToRootProject) : dispatch(replace(rdUrl))
+ );
} else {
setError(true);
setHelperText(response.data.message || 'Please try again');
commit 955e8da986be952c0d29e46f7305af64f95dffef
Author: Stephen Smith <stephen at curii.com>
Date: Wed Jun 22 15:55:18 2022 -0400
Merge branch '19153-sharing-links-inline' into main. Closes #19153
Arvados-DCO-1.1-Signed-off-by: Stephen Smith <stephen at curii.com>
diff --git a/cypress/integration/project.spec.js b/cypress/integration/project.spec.js
index 7371ed06..c4983e3e 100644
--- a/cypress/integration/project.spec.js
+++ b/cypress/integration/project.spec.js
@@ -82,7 +82,8 @@ describe('Project tests', function() {
.then(function() {
expect(this.projects).to.have.lengthOf(1);
expect(this.projects[0].properties).to.deep.equal(
- {IDTAGCOLORS: 'IDVALCOLORS3'});
+ // Pink is not in the test vocab
+ {IDTAGCOLORS: ['IDVALCOLORS3', 'Pink', 'IDVALCOLORS1']});
});
});
@@ -245,7 +246,7 @@ describe('Project tests', function() {
cy.getAll('@mainProject')
.then(function ([mainProject]) {
cy.loginAs(adminUser);
-
+
cy.get('[data-cy=side-panel-tree]').contains('Groups').click();
cy.get('[data-cy=uuid]').eq(0).invoke('text').then(uuid => {
@@ -277,4 +278,4 @@ describe('Project tests', function() {
});
});
});
-});
\ No newline at end of file
+});
diff --git a/src/common/redirect-to.test.ts b/src/common/redirect-to.test.ts
index e25d7be9..0168fd80 100644
--- a/src/common/redirect-to.test.ts
+++ b/src/common/redirect-to.test.ts
@@ -8,7 +8,7 @@ describe('redirect-to', () => {
const { location } = window;
const config: any = {
keepWebServiceUrl: 'http://localhost',
- keepWebServiceInlineUrl: 'http://localhost'
+ keepWebServiceInlineUrl: 'http://localhost-inline'
};
const redirectTo = '/test123';
const locationTemplate = {
@@ -36,7 +36,7 @@ describe('redirect-to', () => {
delete window.location;
window.location = {
...locationTemplate,
- href: `${location.href}?redirectTo=${redirectTo}`,
+ href: `${location.href}?redirectToDownload=${redirectTo}`,
} as any;
Object.defineProperty(window, 'localStorage', {
value: {
@@ -51,7 +51,7 @@ describe('redirect-to', () => {
storeRedirects();
// then
- expect(window.localStorage.setItem).toHaveBeenCalledWith('redirectTo', redirectTo);
+ expect(window.localStorage.setItem).toHaveBeenCalledWith('redirectToDownload', redirectTo);
});
});
@@ -60,7 +60,7 @@ describe('redirect-to', () => {
delete window.location;
window.location = {
...locationTemplate,
- href: `${location.href}?redirectTo=${redirectTo}`,
+ href: `${location.href}?redirectToDownload=${redirectTo}`,
} as any;;
Object.defineProperty(window, 'localStorage', {
value: {
diff --git a/src/common/redirect-to.ts b/src/common/redirect-to.ts
index 77be742f..d8fecde4 100644
--- a/src/common/redirect-to.ts
+++ b/src/common/redirect-to.ts
@@ -2,34 +2,57 @@
//
// SPDX-License-Identifier: AGPL-3.0
+import { getInlineFileUrl } from 'views-components/context-menu/actions/helpers';
import { Config } from './config';
-const REDIRECT_TO_KEY = 'redirectTo';
+export const REDIRECT_TO_DOWNLOAD_KEY = 'redirectToDownload';
+export const REDIRECT_TO_PREVIEW_KEY = 'redirectToPreview';
+
+const getRedirectKeyFromUrl = (href: string): string | null => {
+ switch (true) {
+ case href.indexOf(REDIRECT_TO_DOWNLOAD_KEY) > -1:
+ return REDIRECT_TO_DOWNLOAD_KEY;
+ case href.indexOf(REDIRECT_TO_PREVIEW_KEY) > -1:
+ return REDIRECT_TO_PREVIEW_KEY;
+ default:
+ return null;
+ }
+}
+
+const getRedirectKeyFromStorage = (localStorage: Storage): string | null => {
+ if (localStorage.getItem(REDIRECT_TO_DOWNLOAD_KEY)) {
+ return REDIRECT_TO_DOWNLOAD_KEY;
+ } else if (localStorage.getItem(REDIRECT_TO_PREVIEW_KEY)) {
+ return REDIRECT_TO_PREVIEW_KEY;
+ }
+ return null;
+}
export const storeRedirects = () => {
- let redirectUrl;
const { location: { href }, localStorage } = window;
+ const redirectKey = getRedirectKeyFromUrl(href);
- if (href.indexOf(REDIRECT_TO_KEY) > -1) {
- redirectUrl = href.split(`${REDIRECT_TO_KEY}=`)[1];
- }
-
- if (localStorage && redirectUrl) {
- localStorage.setItem(REDIRECT_TO_KEY, redirectUrl);
+ if (localStorage && redirectKey) {
+ localStorage.setItem(redirectKey, href.split(`${redirectKey}=`)[1]);
}
};
export const handleRedirects = (token: string, config: Config) => {
const { localStorage } = window;
- const { keepWebServiceUrl } = config;
-
- if (localStorage && localStorage.getItem(REDIRECT_TO_KEY)) {
- const redirectUrl = localStorage.getItem(REDIRECT_TO_KEY);
- localStorage.removeItem(REDIRECT_TO_KEY);
-
- if (redirectUrl) {
- const sep = redirectUrl.indexOf("?") > -1 ? "&" : "?";
- window.location.href = `${keepWebServiceUrl}${redirectUrl}${sep}api_token=${token}`;
+ const { keepWebServiceUrl, keepWebInlineServiceUrl } = config;
+
+ if (localStorage) {
+ const redirectKey = getRedirectKeyFromStorage(localStorage);
+ const redirectPath = redirectKey ? localStorage.getItem(redirectKey) : '';
+ redirectKey && localStorage.removeItem(redirectKey);
+
+ if (redirectKey && redirectPath) {
+ const sep = redirectPath.indexOf("?") > -1 ? "&" : "?";
+ let redirectUrl = `${keepWebServiceUrl}${redirectPath}${sep}api_token=${token}`;
+ if (redirectKey === REDIRECT_TO_PREVIEW_KEY) {
+ redirectUrl = getInlineFileUrl(redirectUrl, keepWebServiceUrl, keepWebInlineServiceUrl);
+ }
+ window.location.href = redirectUrl;
}
}
};
diff --git a/src/views-components/context-menu/actions/copy-to-clipboard-action.tsx b/src/views-components/context-menu/actions/copy-to-clipboard-action.tsx
index a1dc5950..c3408740 100644
--- a/src/views-components/context-menu/actions/copy-to-clipboard-action.tsx
+++ b/src/views-components/context-menu/actions/copy-to-clipboard-action.tsx
@@ -11,7 +11,7 @@ import { getClipboardUrl } from "./helpers";
export const CopyToClipboardAction = (props: { href?: any, download?: any, onClick?: () => void, kind?: string, currentCollectionUuid?: string; }) => {
const copyToClipboard = () => {
if (props.href) {
- const clipboardUrl = getClipboardUrl(props.href);
+ const clipboardUrl = getClipboardUrl(props.href, true, true);
copy(clipboardUrl);
}
@@ -30,4 +30,4 @@ export const CopyToClipboardAction = (props: { href?: any, download?: any, onCli
</ListItemText>
</ListItem>
: null;
-};
\ No newline at end of file
+};
diff --git a/src/views-components/context-menu/actions/helpers.test.ts b/src/views-components/context-menu/actions/helpers.test.ts
index b2cb4a55..b3b7f7f8 100644
--- a/src/views-components/context-menu/actions/helpers.test.ts
+++ b/src/views-components/context-menu/actions/helpers.test.ts
@@ -25,7 +25,7 @@ describe('helpers', () => {
const result = getClipboardUrl(url);
// then
- expect(result).toBe('http://localhost?redirectTo=https://example.com/c=zzzzz-4zz18-0123456789abcde/LIMS/1.html');
+ expect(result).toBe('http://localhost?redirectToDownload=https://example.com/c=zzzzz-4zz18-0123456789abcde/LIMS/1.html');
});
});
@@ -67,4 +67,4 @@ describe('helpers', () => {
expect(result).toBe('https://download.example.com/c=zzzzz-4zz18-0123456789abcde/t=v2/a/b/LIMS/1.html');
});
});
-});
\ No newline at end of file
+});
diff --git a/src/views-components/context-menu/actions/helpers.ts b/src/views-components/context-menu/actions/helpers.ts
index 159b1c18..f196074d 100644
--- a/src/views-components/context-menu/actions/helpers.ts
+++ b/src/views-components/context-menu/actions/helpers.ts
@@ -2,6 +2,7 @@
//
// SPDX-License-Identifier: AGPL-3.0
+import { REDIRECT_TO_DOWNLOAD_KEY, REDIRECT_TO_PREVIEW_KEY } from "common/redirect-to";
import { extractUuidKind, ResourceKind } from "models/resource";
export const sanitizeToken = (href: string, tokenAsQueryParam = true): string => {
@@ -13,11 +14,12 @@ export const sanitizeToken = (href: string, tokenAsQueryParam = true): string =>
return `${[prefix, ...rest].join('/')}${tokenAsQueryParam ? `${sep}api_token=${token}` : ''}`;
};
-export const getClipboardUrl = (href: string, shouldSanitizeToken = true): string => {
+export const getClipboardUrl = (href: string, shouldSanitizeToken = true, inline = false): string => {
const { origin } = window.location;
const url = shouldSanitizeToken ? sanitizeToken(href, false) : href;
+ const redirectKey = inline ? REDIRECT_TO_PREVIEW_KEY : REDIRECT_TO_DOWNLOAD_KEY;
- return shouldSanitizeToken ? `${origin}?redirectTo=${url}` : `${origin}${url}`;
+ return shouldSanitizeToken ? `${origin}?${redirectKey}=${url}` : `${origin}${url}`;
};
export const getInlineFileUrl = (url: string, keepWebSvcUrl: string, keepWebInlineSvcUrl: string): string => {
commit bc6ed3985124a18d2c104b794ebc5a73ab872af1
Author: Daniel Kutyła <daniel.kutyla at contractors.roche.com>
Date: Tue Jun 21 11:16:57 2022 +0200
Merge branch '18203-Support-setting-multi-properties-at-once' into main
closes #18203
Arvados-DCO-1.1-Signed-off-by: Daniel Kutyła <daniel.kutyla at contractors.roche.com>
diff --git a/cypress/integration/collection.spec.js b/cypress/integration/collection.spec.js
index b62a3441..5e8d1fe5 100644
--- a/cypress/integration/collection.spec.js
+++ b/cypress/integration/collection.spec.js
@@ -419,6 +419,20 @@ describe('Collection panel tests', function () {
});
});
+ it('shows collection owner', () => {
+ cy.createCollection(adminUser.token, {
+ name: `Test collection ${Math.floor(Math.random() * 999999)}`,
+ owner_uuid: activeUser.user.uuid,
+ manifest_text: ". 37b51d194a7513e45b56f6524f2d51f2+3 0:3:bar\n"
+ })
+ .as('testCollection').then((testCollection) => {
+ cy.loginAs(activeUser);
+ cy.goToPath(`/collections/${testCollection.uuid}`);
+ cy.wait(5000);
+ cy.get('[data-cy=collection-info-panel]').contains(`Collection User`);
+ });
+ });
+
it('tries to rename a file with illegal names', function () {
// Creates the collection using the admin token so we can set up
// a bogus manifest text without block signatures.
diff --git a/cypress/integration/project.spec.js b/cypress/integration/project.spec.js
index 0017e416..7371ed06 100644
--- a/cypress/integration/project.spec.js
+++ b/cypress/integration/project.spec.js
@@ -28,7 +28,7 @@ describe('Project tests', function() {
cy.clearLocalStorage();
});
- it('creates a new project with properties', function() {
+ it('creates a new project with multiple properties', function() {
const projName = `Test project (${Math.floor(999999 * Math.random())})`;
cy.loginAs(activeUser);
cy.get('[data-cy=side-panel-button]').click();
@@ -51,9 +51,26 @@ describe('Project tests', function() {
cy.get('input').type('Magenta');
});
cy.root().submit();
+ cy.get('[data-cy=property-field-value]').within(() => {
+ cy.get('input').type('Pink');
+ });
+ cy.root().submit();
+ cy.get('[data-cy=property-field-value]').within(() => {
+ cy.get('input').type('Yellow');
+ });
+ cy.root().submit();
});
// Confirm proper vocabulary labels are displayed on the UI.
cy.get('[data-cy=form-dialog]').should('contain', 'Color: Magenta');
+ cy.get('[data-cy=form-dialog]').should('contain', 'Color: Pink');
+ cy.get('[data-cy=form-dialog]').should('contain', 'Color: Yellow');
+
+ cy.get('[data-cy=resource-properties-form]').within(() => {
+ cy.get('[data-cy=property-field-key]').within(() => {
+ cy.get('input').focus();
+ });
+ cy.get('[data-cy=property-field-key]').should('not.contain', 'Color');
+ });
// Create project and confirm the properties' real values.
cy.get('[data-cy=form-submit-btn]').click();
diff --git a/src/views-components/collection-properties/create-collection-properties-form.tsx b/src/views-components/collection-properties/create-collection-properties-form.tsx
index 3f19e158..fb18bb1a 100644
--- a/src/views-components/collection-properties/create-collection-properties-form.tsx
+++ b/src/views-components/collection-properties/create-collection-properties-form.tsx
@@ -2,7 +2,7 @@
//
// SPDX-License-Identifier: AGPL-3.0
-import { reduxForm, reset } from 'redux-form';
+import { reduxForm, change } from 'redux-form';
import { withStyles } from '@material-ui/core';
import {
COLLECTION_CREATE_PROPERTIES_FORM_NAME,
@@ -13,6 +13,7 @@ import {
ResourcePropertiesFormData
} from 'views-components/resource-properties-form/resource-properties-form';
import { addPropertyToResourceForm } from 'store/resources/resources-actions';
+import { PROPERTY_VALUE_FIELD_NAME } from 'views-components/resource-properties-form/property-value-field';
const Form = withStyles(
({ spacing }) => (
@@ -27,6 +28,6 @@ export const CreateCollectionPropertiesForm = reduxForm<ResourcePropertiesFormDa
form: COLLECTION_CREATE_PROPERTIES_FORM_NAME,
onSubmit: (data, dispatch) => {
dispatch(addPropertyToResourceForm(data, COLLECTION_CREATE_FORM_NAME));
- dispatch(reset(COLLECTION_CREATE_PROPERTIES_FORM_NAME));
+ dispatch(change(COLLECTION_CREATE_PROPERTIES_FORM_NAME, PROPERTY_VALUE_FIELD_NAME, ''));
}
})(Form);
\ No newline at end of file
diff --git a/src/views-components/collection-properties/update-collection-properties-form.tsx b/src/views-components/collection-properties/update-collection-properties-form.tsx
index 9092c7cc..3ab425f1 100644
--- a/src/views-components/collection-properties/update-collection-properties-form.tsx
+++ b/src/views-components/collection-properties/update-collection-properties-form.tsx
@@ -2,7 +2,7 @@
//
// SPDX-License-Identifier: AGPL-3.0
-import { reduxForm, reset } from 'redux-form';
+import { reduxForm, change } from 'redux-form';
import { withStyles } from '@material-ui/core';
import {
COLLECTION_UPDATE_FORM_NAME,
@@ -13,6 +13,7 @@ import {
ResourcePropertiesFormData
} from 'views-components/resource-properties-form/resource-properties-form';
import { addPropertyToResourceForm } from 'store/resources/resources-actions';
+import { PROPERTY_VALUE_FIELD_NAME } from 'views-components/resource-properties-form/property-value-field';
const Form = withStyles(
({ spacing }) => (
@@ -27,6 +28,6 @@ export const UpdateCollectionPropertiesForm = reduxForm<ResourcePropertiesFormDa
form: COLLECTION_UPDATE_PROPERTIES_FORM_NAME,
onSubmit: (data, dispatch) => {
dispatch(addPropertyToResourceForm(data, COLLECTION_UPDATE_FORM_NAME));
- dispatch(reset(COLLECTION_UPDATE_PROPERTIES_FORM_NAME));
+ dispatch(change(COLLECTION_UPDATE_PROPERTIES_FORM_NAME, PROPERTY_VALUE_FIELD_NAME, ''));
}
})(Form);
\ No newline at end of file
diff --git a/src/views-components/project-properties/create-project-properties-form.tsx b/src/views-components/project-properties/create-project-properties-form.tsx
index 8c26523e..5a6d9df6 100644
--- a/src/views-components/project-properties/create-project-properties-form.tsx
+++ b/src/views-components/project-properties/create-project-properties-form.tsx
@@ -2,7 +2,7 @@
//
// SPDX-License-Identifier: AGPL-3.0
-import { reduxForm, reset } from 'redux-form';
+import { reduxForm, change } from 'redux-form';
import { withStyles } from '@material-ui/core';
import {
PROJECT_CREATE_PROPERTIES_FORM_NAME,
@@ -13,6 +13,7 @@ import {
ResourcePropertiesFormData
} from 'views-components/resource-properties-form/resource-properties-form';
import { addPropertyToResourceForm } from 'store/resources/resources-actions';
+import { PROPERTY_VALUE_FIELD_NAME } from 'views-components/resource-properties-form/property-value-field';
const Form = withStyles(
({ spacing }) => (
@@ -27,6 +28,6 @@ export const CreateProjectPropertiesForm = reduxForm<ResourcePropertiesFormData>
form: PROJECT_CREATE_PROPERTIES_FORM_NAME,
onSubmit: (data, dispatch) => {
dispatch(addPropertyToResourceForm(data, PROJECT_CREATE_FORM_NAME));
- dispatch(reset(PROJECT_CREATE_PROPERTIES_FORM_NAME));
+ dispatch(change(PROJECT_CREATE_PROPERTIES_FORM_NAME, PROPERTY_VALUE_FIELD_NAME, ''));
}
})(Form);
\ No newline at end of file
diff --git a/src/views-components/project-properties/update-project-properties-form.tsx b/src/views-components/project-properties/update-project-properties-form.tsx
index 0b5554bc..9bce50ab 100644
--- a/src/views-components/project-properties/update-project-properties-form.tsx
+++ b/src/views-components/project-properties/update-project-properties-form.tsx
@@ -2,7 +2,7 @@
//
// SPDX-License-Identifier: AGPL-3.0
-import { reduxForm, reset } from 'redux-form';
+import { reduxForm, change } from 'redux-form';
import { withStyles } from '@material-ui/core';
import {
PROJECT_UPDATE_PROPERTIES_FORM_NAME,
@@ -13,6 +13,7 @@ import {
ResourcePropertiesFormData
} from 'views-components/resource-properties-form/resource-properties-form';
import { addPropertyToResourceForm } from 'store/resources/resources-actions';
+import { PROPERTY_VALUE_FIELD_NAME } from 'views-components/resource-properties-form/property-value-field';
const Form = withStyles(
({ spacing }) => (
@@ -27,6 +28,6 @@ export const UpdateProjectPropertiesForm = reduxForm<ResourcePropertiesFormData>
form: PROJECT_UPDATE_PROPERTIES_FORM_NAME,
onSubmit: (data, dispatch) => {
dispatch(addPropertyToResourceForm(data, PROJECT_UPDATE_FORM_NAME));
- dispatch(reset(PROJECT_UPDATE_PROPERTIES_FORM_NAME));
+ dispatch(change(PROJECT_UPDATE_PROPERTIES_FORM_NAME, PROPERTY_VALUE_FIELD_NAME, ''));
}
})(Form);
\ No newline at end of file
diff --git a/src/views-components/resource-properties-form/property-field-common.tsx b/src/views-components/resource-properties-form/property-field-common.tsx
index c4dc494b..dad17284 100644
--- a/src/views-components/resource-properties-form/property-field-common.tsx
+++ b/src/views-components/resource-properties-form/property-field-common.tsx
@@ -14,6 +14,7 @@ export interface VocabularyProp {
export interface ValidationProp {
skipValidation?: boolean;
+ clearPropertyKeyOnSelect?: boolean;
}
export const mapStateToProps = (state: RootState, ownProps: ValidationProp): VocabularyProp & ValidationProp => ({
diff --git a/src/views-components/resource-properties-form/property-key-field.tsx b/src/views-components/resource-properties-form/property-key-field.tsx
index 0be4527a..0b08ad6d 100644
--- a/src/views-components/resource-properties-form/property-key-field.tsx
+++ b/src/views-components/resource-properties-form/property-key-field.tsx
@@ -30,9 +30,10 @@ export const PROPERTY_KEY_FIELD_NAME = 'key';
export const PROPERTY_KEY_FIELD_ID = 'keyID';
export const PropertyKeyField = connectVocabulary(
- ({ vocabulary, skipValidation }: VocabularyProp & ValidationProp) =>
+ ({ vocabulary, skipValidation, clearPropertyKeyOnSelect }: VocabularyProp & ValidationProp) =>
<span data-cy='property-field-key'>
<Field
+ clearPropertyKeyOnSelect
name={PROPERTY_KEY_FIELD_NAME}
component={PropertyKeyInput}
vocabulary={vocabulary}
@@ -40,7 +41,7 @@ export const PropertyKeyField = connectVocabulary(
</span>
);
-const PropertyKeyInput = ({ vocabulary, ...props }: WrappedFieldProps & VocabularyProp) =>
+const PropertyKeyInput = ({ vocabulary, ...props }: WrappedFieldProps & VocabularyProp & { clearPropertyKeyOnSelect?: boolean }) =>
<FormName children={data => (
<Autocomplete
{...buildProps(props)}
@@ -51,6 +52,11 @@ const PropertyKeyInput = ({ vocabulary, ...props }: WrappedFieldProps & Vocabula
? `${s.label} (${s.synonyms.join('; ')})`
: s.label
}
+ onFocus={() => {
+ if (props.clearPropertyKeyOnSelect && props.input.value) {
+ props.meta.dispatch(reset(props.meta.form));
+ }
+ }}
onSelect={handleSelect(PROPERTY_KEY_FIELD_ID, data.form, props.input, props.meta)}
onBlur={() => {
// Case-insensitive search for the key in the vocabulary
diff --git a/src/views-components/resource-properties-form/resource-properties-form.tsx b/src/views-components/resource-properties-form/resource-properties-form.tsx
index 979d772e..25d0f2bb 100644
--- a/src/views-components/resource-properties-form/resource-properties-form.tsx
+++ b/src/views-components/resource-properties-form/resource-properties-form.tsx
@@ -16,16 +16,17 @@ export interface ResourcePropertiesFormData {
[PROPERTY_KEY_FIELD_ID]: string;
[PROPERTY_VALUE_FIELD_NAME]: string;
[PROPERTY_VALUE_FIELD_ID]: string;
+ clearPropertyKeyOnSelect?: boolean;
}
-export type ResourcePropertiesFormProps = {uuid: string; } & InjectedFormProps<ResourcePropertiesFormData, {uuid: string; }> & WithStyles<GridClassKey>;
+export type ResourcePropertiesFormProps = {uuid: string; clearPropertyKeyOnSelect?: boolean } & InjectedFormProps<ResourcePropertiesFormData, {uuid: string; }> & WithStyles<GridClassKey>;
-export const ResourcePropertiesForm = ({ handleSubmit, change, submitting, invalid, classes, uuid }: ResourcePropertiesFormProps ) => {
+export const ResourcePropertiesForm = ({ handleSubmit, change, submitting, invalid, classes, uuid, clearPropertyKeyOnSelect }: ResourcePropertiesFormProps ) => {
change('uuid', uuid); // Sets the uuid field to the uuid of the resource.
return <form data-cy='resource-properties-form' onSubmit={handleSubmit}>
<Grid container spacing={16} classes={classes}>
<Grid item xs>
- <PropertyKeyField />
+ <PropertyKeyField clearPropertyKeyOnSelect />
</Grid>
<Grid item xs>
<PropertyValueField />
commit 5fd300479fd3597b44165517dc61bcc3d9951b14
Author: Lucas Di Pentima <lucas.dipentima at curii.com>
Date: Fri Jun 10 11:58:39 2022 -0300
Merge branch '19177-sharing-urls-ui-config'. Closes #19177
Arvados-DCO-1.1-Signed-off-by: Lucas Di Pentima <lucas.dipentima at curii.com>
diff --git a/src/common/config.ts b/src/common/config.ts
index 2518c95e..2954d704 100644
--- a/src/common/config.ts
+++ b/src/common/config.ts
@@ -50,6 +50,7 @@ export interface ClusterConfigJSON {
}
};
Workbench: {
+ DisableSharingURLsUI: boolean;
ArvadosDocsite: string;
FileViewersConfigURL: string;
WelcomePageHTML: string;
@@ -233,6 +234,7 @@ export const mockClusterConfigJSON = (config: Partial<ClusterConfigJSON>): Clust
WebShell: { ExternalURL: "" },
},
Workbench: {
+ DisableSharingURLsUI: false,
ArvadosDocsite: "",
FileViewersConfigURL: "",
WelcomePageHTML: "",
diff --git a/src/store/sharing-dialog/sharing-dialog-actions.ts b/src/store/sharing-dialog/sharing-dialog-actions.ts
index 367eea81..cdc6c0c7 100644
--- a/src/store/sharing-dialog/sharing-dialog-actions.ts
+++ b/src/store/sharing-dialog/sharing-dialog-actions.ts
@@ -118,13 +118,14 @@ export const deleteSharingToken = (uuid: string) => async (dispatch: Dispatch, g
const loadSharingDialog = async (dispatch: Dispatch, getState: () => RootState, { apiClientAuthorizationService }: ServiceRepository) => {
const dialog = getDialog<SharingDialogData>(getState().dialog, SHARING_DIALOG_NAME);
+ const sharingURLsDisabled = getState().auth.config.clusterConfig.Workbench.DisableSharingURLsUI;
if (dialog) {
dispatch(progressIndicatorActions.START_WORKING(SHARING_DIALOG_NAME));
try {
const resourceUuid = dialog.data.resourceUuid;
await dispatch<any>(initializeManagementForm);
// For collections, we need to load the public sharing tokens
- if (extractUuidObjectType(resourceUuid) === ResourceObjectType.COLLECTION) {
+ if (!sharingURLsDisabled && extractUuidObjectType(resourceUuid) === ResourceObjectType.COLLECTION) {
const sharingTokens = await apiClientAuthorizationService.listCollectionSharingTokens(resourceUuid);
dispatch(resourcesActions.SET_RESOURCES([...sharingTokens.items]));
}
diff --git a/src/views-components/sharing-dialog/sharing-dialog-component.test.tsx b/src/views-components/sharing-dialog/sharing-dialog-component.test.tsx
new file mode 100644
index 00000000..36447a8d
--- /dev/null
+++ b/src/views-components/sharing-dialog/sharing-dialog-component.test.tsx
@@ -0,0 +1,71 @@
+// Copyright (C) The Arvados Authors. All rights reserved.
+//
+// SPDX-License-Identifier: AGPL-3.0
+
+import React from 'react';
+import { mount, configure } from 'enzyme';
+import Adapter from 'enzyme-adapter-react-16';
+import { Provider } from 'react-redux';
+import { combineReducers, createStore } from 'redux';
+
+import SharingDialogComponent, {
+ SharingDialogComponentProps,
+} from './sharing-dialog-component';
+import {
+ extractUuidObjectType,
+ ResourceObjectType
+} from 'models/resource';
+
+configure({ adapter: new Adapter() });
+
+describe("<SharingDialogComponent />", () => {
+ let props: SharingDialogComponentProps;
+ let store;
+
+ beforeEach(() => {
+ const initialAuthState = {
+ config: {
+ keepWebServiceUrl: 'http://example.com/',
+ keepWebInlineServiceUrl: 'http://*.collections.example.com/',
+ }
+ }
+ store = createStore(combineReducers({
+ auth: (state: any = initialAuthState, action: any) => state,
+ }));
+
+ props = {
+ open: true,
+ loading: false,
+ saveEnabled: false,
+ sharedResourceUuid: 'zzzzz-4zz18-zzzzzzzzzzzzzzz',
+ privateAccess: true,
+ sharingURLsNr: 2,
+ sharingURLsDisabled: false,
+ onClose: jest.fn(),
+ onSave: jest.fn(),
+ onCreateSharingToken: jest.fn(),
+ refreshPermissions: jest.fn(),
+ };
+ });
+
+ it("show sharing urls tab on collections when not disabled", () => {
+ expect(props.sharingURLsDisabled).toBe(false);
+ expect(props.sharingURLsNr).toBe(2);
+ expect(extractUuidObjectType(props.sharedResourceUuid) === ResourceObjectType.COLLECTION).toBe(true);
+ let wrapper = mount(<Provider store={store}><SharingDialogComponent {...props} /></Provider>);
+ expect(wrapper.html()).toContain('Sharing URLs (2)');
+
+ // disable Sharing URLs UI
+ props.sharingURLsDisabled = true;
+ wrapper = mount(<Provider store={store}><SharingDialogComponent {...props} /></Provider>);
+ expect(wrapper.html()).not.toContain('Sharing URLs');
+ });
+
+ it("does not show sharing urls on non-collection resources", () => {
+ props.sharedResourceUuid = 'zzzzz-j7d0g-0123456789abcde';
+ expect(extractUuidObjectType(props.sharedResourceUuid) === ResourceObjectType.COLLECTION).toBe(false);
+ expect(props.sharingURLsDisabled).toBe(false);
+ let wrapper = mount(<Provider store={store}><SharingDialogComponent {...props} /></Provider>);
+ expect(wrapper.html()).not.toContain('Sharing URLs');
+ });
+});
\ 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 15d7f660..b2f31397 100644
--- a/src/views-components/sharing-dialog/sharing-dialog-component.tsx
+++ b/src/views-components/sharing-dialog/sharing-dialog-component.tsx
@@ -47,6 +47,7 @@ export interface SharingDialogDataProps {
sharedResourceUuid: string;
sharingURLsNr: number;
privateAccess: boolean;
+ sharingURLsDisabled: boolean;
}
export interface SharingDialogActionProps {
onClose: () => void;
@@ -58,11 +59,13 @@ enum SharingDialogTab {
PERMISSIONS = 0,
URLS = 1,
}
-export default (props: SharingDialogDataProps & SharingDialogActionProps) => {
+export type SharingDialogComponentProps = SharingDialogDataProps & SharingDialogActionProps;
+
+export default (props: SharingDialogComponentProps) => {
const { open, loading, saveEnabled, sharedResourceUuid,
- sharingURLsNr, privateAccess,
+ sharingURLsNr, privateAccess, sharingURLsDisabled,
onClose, onSave, onCreateSharingToken, refreshPermissions } = props;
- const showTabs = extractUuidObjectType(sharedResourceUuid) === ResourceObjectType.COLLECTION;
+ const showTabs = !sharingURLsDisabled && extractUuidObjectType(sharedResourceUuid) === ResourceObjectType.COLLECTION;
const [tabNr, setTabNr] = React.useState<number>(SharingDialogTab.PERMISSIONS);
const [expDate, setExpDate] = React.useState<Date>();
const [withExpiration, setWithExpiration] = React.useState<boolean>(false);
@@ -151,7 +154,8 @@ export default (props: SharingDialogDataProps & SharingDialogActionProps) => {
</Grid>
</>
}
- { tabNr === SharingDialogTab.PERMISSIONS && privateAccess && sharingURLsNr > 0 &&
+ { tabNr === SharingDialogTab.PERMISSIONS && !sharingURLsDisabled &&
+ privateAccess && sharingURLsNr > 0 &&
<Grid item md={12}>
<Typography variant='caption' align='center' color='error'>
Although there aren't specific permissions set, this is publicly accessible via Sharing URL(s).
diff --git a/src/views-components/sharing-dialog/sharing-dialog.tsx b/src/views-components/sharing-dialog/sharing-dialog.tsx
index 6b488e44..01cd390b 100644
--- a/src/views-components/sharing-dialog/sharing-dialog.tsx
+++ b/src/views-components/sharing-dialog/sharing-dialog.tsx
@@ -35,18 +35,21 @@ type Props = WithDialogProps<string> & WithProgressStateProps;
const mapStateToProps = (state: RootState, { working, ...props }: Props): SharingDialogDataProps => {
const dialog = getDialog<SharingDialogData>(state.dialog, SHARING_DIALOG_NAME);
const sharedResourceUuid = dialog?.data.resourceUuid || '';
+ const sharingURLsDisabled = state.auth.config.clusterConfig.Workbench.DisableSharingURLsUI;
return ({
...props,
saveEnabled: hasChanges(state),
loading: working,
sharedResourceUuid,
- sharingURLsNr: (filterResources(
- (resource: ApiClientAuthorization) =>
+ sharingURLsDisabled,
+ sharingURLsNr: !sharingURLsDisabled
+ ? (filterResources( (resource: ApiClientAuthorization) =>
resource.kind === ResourceKind.API_CLIENT_AUTHORIZATION &&
resource.scopes.includes(`GET /arvados/v1/collections/${sharedResourceUuid}`) &&
resource.scopes.includes(`GET /arvados/v1/collections/${sharedResourceUuid}/`) &&
resource.scopes.includes('GET /arvados/v1/keep_services/accessible')
- )(state.resources) as ApiClientAuthorization[]).length,
+ )(state.resources) as ApiClientAuthorization[]).length
+ : 0,
privateAccess: getSharingPublicAccessFormData(state)?.visibility === VisibilityLevel.PRIVATE,
})
};
-----------------------------------------------------------------------
hooks/post-receive
--
More information about the arvados-commits
mailing list