const childrenCollection = Array.prototype.slice.call(Cypress.$(body).find('dict')[0].children);
const map = {};
let i, j = 2;
-
+
for (i=0; i < childrenCollection.length; i += j) {
map[childrenCollection[i].outerText] = childrenCollection[i + 1].outerText;
}
// on this loop may pass an assertion from the first iteration by looking
// for the same file name.
const fileName = isWritable ? 'bar' : 'foo';
+ const subDirName = 'subdir';
cy.createGroup(adminUser.token, {
name: 'Shared project',
group_class: 'project',
name: 'Test collection',
owner_uuid: this.sharedGroup.uuid,
properties: { someKey: 'someValue' },
- manifest_text: `. 37b51d194a7513e45b56f6524f2d51f2+3 0:3:${fileName}\n`
+ manifest_text: `. 37b51d194a7513e45b56f6524f2d51f2+3 0:3:${fileName}\n./${subDirName} 37b51d194a7513e45b56f6524f2d51f2+3 0:3:${fileName}\n`
})
.as('testCollection').then(function () {
// Share the group with active user.
.should(`${isWritable ? '' : 'not.'}contain`, 'Upload data');
}
});
+ // Test context menus
cy.get('[data-cy=collection-files-panel]')
.contains(fileName).rightclick({ force: true });
cy.get('[data-cy=context-menu]')
.and(`${isWritable ? '' : 'not.'}contain`, 'Rename')
.and(`${isWritable ? '' : 'not.'}contain`, 'Remove');
cy.get('body').click(); // Collapse the menu
+ cy.get('[data-cy=collection-files-panel]')
+ .contains(subDirName).rightclick({ force: true });
+ cy.get('[data-cy=context-menu]')
+ .should('not.contain', 'Download')
+ .and('contain', 'Open in new tab')
+ .and('contain', 'Copy to clipboard')
+ .and(`${isWritable ? '' : 'not.'}contain`, 'Rename')
+ .and(`${isWritable ? '' : 'not.'}contain`, 'Remove');
+ cy.get('body').click(); // Collapse the menu
// Hamburger 'more options' menu button
cy.get('[data-cy=collection-files-panel-options-btn]')
.click()
cy.get('[data-cy=collection-files-panel-options-btn]')
.click()
cy.get('[data-cy=context-menu]')
- // .should('contain', 'Download selected')
.should(`${isWritable ? '' : 'not.'}contain`, 'Remove selected')
cy.get('body').click(); // Collapse the menu
- // File item 'more options' button
- cy.get('[data-cy=file-item-options-btn')
- .click()
- cy.get('[data-cy=context-menu]')
- .should('contain', 'Download')
- .and(`${isWritable ? '' : 'not.'}contain`, 'Remove');
- cy.get('body').click(); // Collapse the menu
})
})
})
cy.get('[data-cy=form-dialog]')
.should('contain', 'Rename')
.within(() => {
- cy.get('input').type(`{selectall}{backspace}${to}`);
+ cy.get('input')
+ .type('{selectall}{backspace}')
+ .type(to, { parseSpecialCharSequences: false });
});
cy.get('[data-cy=form-submit-btn]').click();
cy.get('[data-cy=collection-files-panel]')
// Create new collection
cy.get('[data-cy=side-panel-button]').click();
cy.get('[data-cy=side-panel-new-collection]').click();
- const collName = `Test collection (${Math.floor(999999 * Math.random())})`;
+ // Name between brackets tests bugfix #17582
+ const collName = `[Test collection (${Math.floor(999999 * Math.random())})]`;
cy.get('[data-cy=form-dialog]')
.should('contain', 'New collection')
.within(() => {
"lodash.mergewith": "4.6.2",
"lodash.template": "4.5.0",
"mem": "4.0.0",
+ "moment": "2.29.1",
"parse-duration": "0.4.4",
"prop-types": "15.7.2",
"query-string": "6.9.0",
import { resourceActionSet } from '~/views-components/context-menu/action-sets/resource-action-set';
import { favoriteActionSet } from "~/views-components/context-menu/action-sets/favorite-action-set";
import { collectionFilesActionSet, readOnlyCollectionFilesActionSet } from '~/views-components/context-menu/action-sets/collection-files-action-set';
-import { collectionFilesItemActionSet, readOnlyCollectionFilesItemActionSet } from '~/views-components/context-menu/action-sets/collection-files-item-action-set';
+import { collectionDirectoryItemActionSet, collectionFileItemActionSet, readOnlyCollectionDirectoryItemActionSet, readOnlyCollectionFileItemActionSet } from '~/views-components/context-menu/action-sets/collection-files-item-action-set';
import { collectionFilesNotSelectedActionSet } from '~/views-components/context-menu/action-sets/collection-files-not-selected-action-set';
import { collectionActionSet, collectionAdminActionSet, oldCollectionVersionActionSet, readOnlyCollectionActionSet } from '~/views-components/context-menu/action-sets/collection-action-set';
import { processActionSet } from '~/views-components/context-menu/action-sets/process-action-set';
addMenuActionSet(ContextMenuKind.COLLECTION_FILES, collectionFilesActionSet);
addMenuActionSet(ContextMenuKind.READONLY_COLLECTION_FILES, readOnlyCollectionFilesActionSet);
addMenuActionSet(ContextMenuKind.COLLECTION_FILES_NOT_SELECTED, collectionFilesNotSelectedActionSet);
-addMenuActionSet(ContextMenuKind.COLLECTION_FILES_ITEM, collectionFilesItemActionSet);
-addMenuActionSet(ContextMenuKind.READONLY_COLLECTION_FILES_ITEM, readOnlyCollectionFilesItemActionSet);
+addMenuActionSet(ContextMenuKind.COLLECTION_DIRECTORY_ITEM, collectionDirectoryItemActionSet);
+addMenuActionSet(ContextMenuKind.READONLY_COLLECTION_DIRECTORY_ITEM, readOnlyCollectionDirectoryItemActionSet);
+addMenuActionSet(ContextMenuKind.COLLECTION_FILE_ITEM, collectionFileItemActionSet);
+addMenuActionSet(ContextMenuKind.READONLY_COLLECTION_FILE_ITEM, readOnlyCollectionFileItemActionSet);
addMenuActionSet(ContextMenuKind.COLLECTION, collectionActionSet);
addMenuActionSet(ContextMenuKind.READONLY_COLLECTION, readOnlyCollectionActionSet);
addMenuActionSet(ContextMenuKind.OLD_VERSION_COLLECTION, oldCollectionVersionActionSet);
it("#create", async () => {
axiosMock
- .onPost("/resource")
+ .onPost("/resources")
.reply(200, { owner_uuid: "ownerUuidValue" });
- const commonResourceService = new CommonResourceService(axiosInstance, "resource", actions);
+ const commonResourceService = new CommonResourceService(axiosInstance, "resources", actions);
const resource = await commonResourceService.create({ ownerUuid: "ownerUuidValue" });
expect(resource).toEqual({ ownerUuid: "ownerUuidValue" });
});
it("#create maps request params to snake case", async () => {
axiosInstance.post = jest.fn(() => Promise.resolve({data: {}}));
- const commonResourceService = new CommonResourceService(axiosInstance, "resource", actions);
+ const commonResourceService = new CommonResourceService(axiosInstance, "resources", actions);
await commonResourceService.create({ ownerUuid: "ownerUuidValue" });
- expect(axiosInstance.post).toHaveBeenCalledWith("/resource", {owner_uuid: "ownerUuidValue"});
+ expect(axiosInstance.post).toHaveBeenCalledWith("/resources", {resource: {owner_uuid: "ownerUuidValue"}});
});
it("#create ignores fields listed as readonly", async () => {
axiosInstance.post = jest.fn(() => Promise.resolve({data: {}}));
- const commonResourceService = new CommonResourceService(axiosInstance, "resource", actions);
+ const commonResourceService = new CommonResourceService(axiosInstance, "resources", actions);
// UUID fields are read-only on all resources.
await commonResourceService.create({ uuid: "this should be ignored", ownerUuid: "ownerUuidValue" });
- expect(axiosInstance.post).toHaveBeenCalledWith("/resource", {owner_uuid: "ownerUuidValue"});
+ expect(axiosInstance.post).toHaveBeenCalledWith("/resources", {resource: {owner_uuid: "ownerUuidValue"}});
});
it("#update ignores fields listed as readonly", async () => {
axiosInstance.put = jest.fn(() => Promise.resolve({data: {}}));
- const commonResourceService = new CommonResourceService(axiosInstance, "resource", actions);
+ const commonResourceService = new CommonResourceService(axiosInstance, "resources", actions);
// UUID fields are read-only on all resources.
await commonResourceService.update('resource-uuid', { uuid: "this should be ignored", ownerUuid: "ownerUuidValue" });
- expect(axiosInstance.put).toHaveBeenCalledWith("/resource/resource-uuid", {owner_uuid: "ownerUuidValue"});
+ expect(axiosInstance.put).toHaveBeenCalledWith("/resources/resource-uuid", {resource: {owner_uuid: "ownerUuidValue"}});
});
it("#delete", async () => {
axiosMock
- .onDelete("/resource/uuid")
+ .onDelete("/resources/uuid")
.reply(200, { deleted_at: "now" });
- const commonResourceService = new CommonResourceService(axiosInstance, "resource", actions);
+ const commonResourceService = new CommonResourceService(axiosInstance, "resources", actions);
const resource = await commonResourceService.delete("uuid");
expect(resource).toEqual({ deletedAt: "now" });
});
it("#get", async () => {
axiosMock
- .onGet("/resource/uuid")
+ .onGet("/resources/uuid")
.reply(200, {
modified_at: "now",
properties: {
}
});
- const commonResourceService = new CommonResourceService(axiosInstance, "resource", actions);
+ const commonResourceService = new CommonResourceService(axiosInstance, "resources", actions);
const resource = await commonResourceService.get("uuid");
// Only first level keys are mapped to camel case
expect(resource).toEqual({
it("#list", async () => {
axiosMock
- .onGet("/resource")
+ .onGet("/resources")
.reply(200, {
kind: "kind",
offset: 2,
items_available: 20
});
- const commonResourceService = new CommonResourceService(axiosInstance, "resource", actions);
+ const commonResourceService = new CommonResourceService(axiosInstance, "resources", actions);
const resource = await commonResourceService.list({ limit: 10, offset: 1 });
// First level keys are mapped to camel case inside "items" arrays
expect(resource).toEqual({
it("#list using POST when query string is too big", async () => {
axiosMock
- .onAny("/resource")
+ .onAny("/resources")
.reply(200);
const tooBig = 'x'.repeat(1500);
- const commonResourceService = new CommonResourceService(axiosInstance, "resource", actions);
+ const commonResourceService = new CommonResourceService(axiosInstance, "resources", actions);
await commonResourceService.list({ filters: tooBig });
expect(axiosMock.history.get.length).toBe(0);
expect(axiosMock.history.post.length).toBe(1);
it("#list using GET when query string is not too big", async () => {
axiosMock
- .onAny("/resource")
+ .onAny("/resources")
.reply(200);
const notTooBig = 'x'.repeat(1480);
- const commonResourceService = new CommonResourceService(axiosInstance, "resource", actions);
+ const commonResourceService = new CommonResourceService(axiosInstance, "resources", actions);
await commonResourceService.list({ filters: notTooBig });
expect(axiosMock.history.post.length).toBe(0);
expect(axiosMock.history.get.length).toBe(1);
// SPDX-License-Identifier: AGPL-3.0
import { AxiosInstance } from "axios";
+import * as _ from "lodash";
import { Resource } from "src/models/resource";
import { ApiActions } from "~/services/api/api-actions";
import { CommonService } from "~/services/common-service/common-service";
}
create(data?: Partial<T>) {
+ let payload: any;
if (data !== undefined) {
this.readOnlyFields.forEach( field => delete data[field] );
+ payload = {
+ [this.resourceType.slice(0, -1)]: CommonService.mapKeys(_.snakeCase)(data),
+ };
}
- return super.create(data);
+ return super.create(payload);
}
update(uuid: string, data: Partial<T>) {
+ let payload: any;
if (data !== undefined) {
this.readOnlyFields.forEach( field => delete data[field] );
+ payload = {
+ [this.resourceType.slice(0, -1)]: CommonService.mapKeys(_.snakeCase)(data),
+ };
}
- return super.update(uuid, data);
+ return super.update(uuid, payload);
}
}
constructor(serverApi: AxiosInstance, resourceType: string, actions: ApiActions, readOnlyFields: string[] = []) {
this.serverApi = serverApi;
- this.resourceType = '/' + resourceType;
+ this.resourceType = resourceType;
this.actions = actions;
this.readOnlyFields = readOnlyFields;
}
create(data?: Partial<T>, showErrors?: boolean) {
return CommonService.defaultResponse(
this.serverApi
- .post<T>(this.resourceType, data && CommonService.mapKeys(_.snakeCase)(data)),
+ .post<T>(`/${this.resourceType}`, data && CommonService.mapKeys(_.snakeCase)(data)),
this.actions,
true, // mapKeys
showErrors
this.validateUuid(uuid);
return CommonService.defaultResponse(
this.serverApi
- .delete(this.resourceType + '/' + uuid),
+ .delete(`/${this.resourceType}/${uuid}`),
this.actions
);
}
this.validateUuid(uuid);
return CommonService.defaultResponse(
this.serverApi
- .get<T>(this.resourceType + '/' + uuid),
+ .get<T>(`/${this.resourceType}/${uuid}`),
this.actions,
true, // mapKeys
showErrors
if (QueryString.stringify(params).length <= 1500) {
return CommonService.defaultResponse(
- this.serverApi.get(this.resourceType, { params }),
+ this.serverApi.get(`/${this.resourceType}`, { params }),
this.actions
);
} else {
}
});
return CommonService.defaultResponse(
- this.serverApi.post(this.resourceType, formData, {
+ this.serverApi.post(`/${this.resourceType}`, formData, {
params: {
_method: 'GET'
}
this.validateUuid(uuid);
return CommonService.defaultResponse(
this.serverApi
- .put<T>(this.resourceType + '/' + uuid, data && CommonService.mapKeys(_.snakeCase)(data)),
+ .put<T>(`/${this.resourceType}/${uuid}`, data && CommonService.mapKeys(_.snakeCase)(data)),
this.actions
);
}
const projectService = new ProjectService(axiosInstance, actions);
const resource = await projectService.create({ name: "nameValue" });
expect(axiosInstance.post).toHaveBeenCalledWith("/groups", {
- name: "nameValue",
- group_class: "project"
+ group: {
+ name: "nameValue",
+ group_class: "project"
+ }
});
});
return null;
};
-const getRemoteHostConfig = async (remoteHost: string, useApiClient?: AxiosInstance): Promise<Config | null> => {
+export const getRemoteHostConfig = async (remoteHost: string, useApiClient?: AxiosInstance): Promise<Config | null> => {
const apiClient = useApiClient || Axios.create({ headers: {} });
let url = remoteHost;
import { ServiceRepository, createServices } from "~/services/services";
import { configureStore, RootStore } from "../store";
import { createBrowserHistory } from "history";
-import { Config, mockConfig } from '~/common/config';
+import { mockConfig } from '~/common/config';
import { ApiActions } from "~/services/api/api-actions";
import { ACCOUNT_LINK_STATUS_KEY } from '~/services/link-account-service/link-account-service';
-import Axios from "axios";
+import Axios, { AxiosInstance } from "axios";
import MockAdapter from "axios-mock-adapter";
import { ImportMock } from 'ts-mock-imports';
import * as servicesModule from "~/services/services";
+import * as authActionSessionModule from "./auth-action-session";
import { SessionStatus } from "~/models/session";
+import { getRemoteHostConfig } from "./auth-action-session";
describe('auth-actions', () => {
- const axiosInst = Axios.create({ headers: {} });
- const axiosMock = new MockAdapter(axiosInst);
+ let axiosInst: AxiosInstance;
+ let axiosMock: MockAdapter;
let store: RootStore;
let services: ServiceRepository;
let importMocks: any[];
beforeEach(() => {
- axiosMock.reset();
+ axiosInst = Axios.create({ headers: {} });
+ axiosMock = new MockAdapter(axiosInst);
services = createServices(mockConfig({}), actions, axiosInst);
store = configureStore(createBrowserHistory(), services, config);
localStorage.clear();
localStorage.setItem(API_TOKEN_KEY, "token");
const config: any = {
- rootUrl: "https://zzzzz.arvadosapi.com",
+ rootUrl: "https://zzzzz.example.com",
uuidPrefix: "zzzzz",
remoteHosts: { },
apiRevision: 12345678,
expect(store.getState().auth.extraApiToken).not.toBe(extraToken);
});
+ it('requests remote token data to login cluster', async () => {
+ const localClusterTokenExpiration = "2020-01-01T00:00:00.000Z";
+ const loginClusterTokenExpiration = "2140-01-01T00:00:00.000Z";
+ axiosMock
+ .onGet("/users/current")
+ .reply(200, {
+ email: "test@test.com",
+ first_name: "John",
+ last_name: "Doe",
+ uuid: "zzzz1-tpzed-abcefg",
+ owner_uuid: "ownerUuid",
+ is_admin: false,
+ is_active: true,
+ username: "jdoe",
+ prefs: {}
+ })
+ .onGet("https://zzzz1.example.com/discovery/v1/apis/arvados/v1/rest")
+ .reply(200, {
+ baseUrl: "https://zzzz1.example.com/arvados/v1",
+ keepWebServiceUrl: "",
+ keepWebInlineServiceUrl: "",
+ remoteHosts: {},
+ rootUrl: "https://zzzz1.example.com",
+ uuidPrefix: "zzzz1",
+ websocketUrl: "",
+ workbenchUrl: "",
+ workbench2Url: "",
+ revision: 12345678
+ })
+ // Local cluster -- cached token
+ .onGet("https://zzzzz.example.com/arvados/v1/api_client_authorizations/current")
+ .reply(200, {
+ uuid: 'zzzz1-gj3su-aaaaaaa',
+ expires_at: localClusterTokenExpiration,
+ api_token: 'tokensecret',
+ })
+ // Login cluster -- authoritative token copy
+ .onGet("https://zzzz1.example.com/arvados/v1/api_client_authorizations/current")
+ .reply(200, {
+ uuid: 'zzzz1-gj3su-aaaaaaa',
+ expires_at: loginClusterTokenExpiration,
+ api_token: 'tokensecret',
+ });
+
+ const config: any = {
+ rootUrl: "https://zzzzz.example.com",
+ uuidPrefix: "zzzzz",
+ remoteHosts: { zzzz1: "zzzz1.example.com" },
+ apiRevision: 12345678,
+ clusterConfig: {
+ Login: { LoginCluster: "zzzz1" },
+ },
+ };
+
+ const remoteHostConfig = await getRemoteHostConfig(config.remoteHosts.zzzz1, axiosInst);
+ expect(remoteHostConfig).not.toBeFalsy;
+ services = createServices(remoteHostConfig!, actions, axiosInst);
+
+ importMocks.push(ImportMock.mockFunction(authActionSessionModule, 'getRemoteHostConfig', remoteHostConfig));
+ importMocks.push(ImportMock.mockFunction(servicesModule, 'createServices', services));
+
+ sessionStorage.setItem(ACCOUNT_LINK_STATUS_KEY, "0");
+ localStorage.setItem(API_TOKEN_KEY, "v2/zzzz1-gj3su-aaaaaaa/tokensecret");
+
+ await store.dispatch(initAuth(config));
+ expect(store.getState().auth.apiToken).toBeDefined();
+ expect(localClusterTokenExpiration).not.toBe(loginClusterTokenExpiration);
+ expect(store.getState().auth.apiTokenExpiration).toEqual(new Date(loginClusterTokenExpiration));
+ });
+
it('should initialise state with user and api token from local storage', (done) => {
axiosMock
.onGet("/users/current")
.reply(200, {
expires_at: "2140-01-01T00:00:00.000Z"
})
- .onGet("https://xc59z.arvadosapi.com/discovery/v1/apis/arvados/v1/rest")
+ .onGet("https://xc59z.example.com/discovery/v1/apis/arvados/v1/rest")
.reply(200, {
- baseUrl: "https://xc59z.arvadosapi.com/arvados/v1",
+ baseUrl: "https://xc59z.example.com/arvados/v1",
keepWebServiceUrl: "",
keepWebInlineServiceUrl: "",
remoteHosts: {},
- rootUrl: "https://xc59z.arvadosapi.com",
+ rootUrl: "https://xc59z.example.com",
uuidPrefix: "xc59z",
websocketUrl: "",
workbenchUrl: "",
localStorage.setItem(API_TOKEN_KEY, "token");
const config: any = {
- rootUrl: "https://zzzzz.arvadosapi.com",
+ rootUrl: "https://zzzzz.example.com",
uuidPrefix: "zzzzz",
- remoteHosts: { xc59z: "xc59z.arvadosapi.com" },
+ remoteHosts: { xc59z: "xc59z.example.com" },
apiRevision: 12345678,
clusterConfig: {
Login: { LoginCluster: "" },
},
},
remoteHosts: {
- "xc59z": "xc59z.arvadosapi.com",
+ "xc59z": "xc59z.example.com",
},
- rootUrl: "https://zzzzz.arvadosapi.com",
+ rootUrl: "https://zzzzz.example.com",
uuidPrefix: "zzzzz",
},
sshKeys: [],
},
},
"remoteHosts": {
- "xc59z": "xc59z.arvadosapi.com",
+ "xc59z": "xc59z.example.com",
},
- "rootUrl": "https://zzzzz.arvadosapi.com",
+ "rootUrl": "https://zzzzz.example.com",
"uuidPrefix": "zzzzz",
},
"xc59z": mockConfig({
apiRevision: 12345678,
- baseUrl: "https://xc59z.arvadosapi.com/arvados/v1",
- rootUrl: "https://xc59z.arvadosapi.com",
+ baseUrl: "https://xc59z.example.com/arvados/v1",
+ rootUrl: "https://xc59z.example.com",
uuidPrefix: "xc59z"
})
},
remoteHosts: {
- zzzzz: "zzzzz.arvadosapi.com",
- xc59z: "xc59z.arvadosapi.com"
+ zzzzz: "zzzzz.example.com",
+ xc59z: "xc59z.example.com"
},
sessions: [{
"active": true,
"clusterId": "zzzzz",
"email": "test@test.com",
"loggedIn": true,
- "remoteHost": "https://zzzzz.arvadosapi.com",
+ "remoteHost": "https://zzzzz.example.com",
"status": 2,
"token": "token",
"name": "John Doe",
"clusterId": "xc59z",
"email": "",
"loggedIn": false,
- "remoteHost": "xc59z.arvadosapi.com",
+ "remoteHost": "xc59z.example.com",
"status": 2,
"token": "",
"name": "",
import { cancelLinking } from '~/store/link-account-panel/link-account-panel-actions';
import { progressIndicatorActions } from "~/store/progress-indicator/progress-indicator-actions";
import { WORKBENCH_LOADING_SCREEN } from '~/store/workbench/workbench-actions';
-import { addRemoteConfig } from './auth-action-session';
+import { addRemoteConfig, getRemoteHostConfig } from './auth-action-session';
import { getTokenV2 } from '~/models/api-client-authorization';
export const authActions = unionize({
if (token && token !== "undefined") {
dispatch(progressIndicatorActions.START_WORKING(WORKBENCH_LOADING_SCREEN));
try {
- await dispatch<any>(saveApiToken(token)); // .then(() => {
- await dispatch(progressIndicatorActions.STOP_WORKING(WORKBENCH_LOADING_SCREEN));
- } catch (e) {
+ await dispatch<any>(saveApiToken(token));
+ } finally {
dispatch(progressIndicatorActions.STOP_WORKING(WORKBENCH_LOADING_SCREEN));
}
}
};
export const saveApiToken = (token: string) => async (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository): Promise<any> => {
- const config = dispatch<any>(getConfig);
+ let config: any;
+ const tokenParts = token.split('/');
+ const auth = getState().auth;
+ config = dispatch<any>(getConfig);
+
+ // If federated token, get user & token data from the token issuing cluster
+ if (tokenParts.length === 3 && tokenParts[1].substring(0, 5) !== auth.localCluster) {
+ config = await getRemoteHostConfig(auth.remoteHosts[tokenParts[1].substring(0, 5)]);
+ }
+
const svc = createServices(config, { progressFn: () => { }, errorFn: () => { } });
setAuthorizationHeader(svc, token);
try {
dispatch(collectionPanelFilesAction.TOGGLE_COLLECTION_FILE_SELECTION({ id: item.id }));
},
onItemMenuOpen: (event, item, isWritable) => {
+ const isDirectory = item.data.type === 'directory';
dispatch<any>(openContextMenu(
event,
{
menuKind: isWritable
- ? ContextMenuKind.COLLECTION_FILES_ITEM
- : ContextMenuKind.READONLY_COLLECTION_FILES_ITEM,
+ ? isDirectory
+ ? ContextMenuKind.COLLECTION_DIRECTORY_ITEM
+ : ContextMenuKind.COLLECTION_FILE_ITEM
+ : isDirectory
+ ? ContextMenuKind.READONLY_COLLECTION_DIRECTORY_ITEM
+ : ContextMenuKind.READONLY_COLLECTION_FILE_ITEM,
kind: ResourceKind.COLLECTION,
name: item.data.name,
uuid: item.id,
import { ContextMenuActionSet } from "~/views-components/context-menu/context-menu-action-set";
import { collectionPanelFilesAction, openMultipleFilesRemoveDialog } from "~/store/collection-panel/collection-panel-files/collection-panel-files-actions";
import { openCollectionPartialCopyDialog, openCollectionPartialCopyToSelectedCollectionDialog } from '~/store/collections/collection-partial-copy-actions';
-// import { DownloadCollectionFileAction } from "~/views-components/context-menu/actions/download-collection-file-action";
+// These action sets are used on the multi-select actions button.
export const readOnlyCollectionFilesActionSet: ContextMenuActionSet = [[
{
name: "Select all",
dispatch(collectionPanelFilesAction.UNSELECT_ALL_COLLECTION_FILES());
}
},
- // { // Disabled for now as we need to create backend version of this feature which will be less buggy
- // component: DownloadCollectionFileAction,
- // execute: () => { return; }
- // },
{
name: "Create a new collection with selected",
execute: dispatch => {
import { CollectionFileViewerAction } from '~/views-components/context-menu/actions/collection-file-viewer-action';
import { CollectionCopyToClipboardAction } from "../actions/collection-copy-to-clipboard-action";
-export const readOnlyCollectionFilesItemActionSet: ContextMenuActionSet = [[
- {
- component: DownloadCollectionFileAction,
- execute: () => { return; }
- },
+export const readOnlyCollectionDirectoryItemActionSet: ContextMenuActionSet = [[
{
component: CollectionFileViewerAction,
execute: () => { return; },
}
]];
-export const collectionFilesItemActionSet: ContextMenuActionSet = readOnlyCollectionFilesItemActionSet.concat([[
+export const readOnlyCollectionFileItemActionSet: ContextMenuActionSet = [[
+ {
+ component: DownloadCollectionFileAction,
+ execute: () => { return; }
+ },
+ ...readOnlyCollectionDirectoryItemActionSet.reduce((prev, next) => prev.concat(next), []),
+]];
+
+const writableActionSet: ContextMenuActionSet = [[
{
name: "Rename",
icon: RenameIcon,
dispatch<any>(openFileRemoveDialog(resource.uuid));
}
}
-]]);
\ No newline at end of file
+]];
+
+export const collectionDirectoryItemActionSet: ContextMenuActionSet = readOnlyCollectionDirectoryItemActionSet.concat(writableActionSet);
+
+export const collectionFileItemActionSet: ContextMenuActionSet = readOnlyCollectionFileItemActionSet.concat(writableActionSet);
\ No newline at end of file
const { resource } = state.contextMenu;
const currentCollectionUuid = state.collectionPanel.item ? state.collectionPanel.item.uuid : '';
const { keepWebServiceUrl } = state.auth.config;
- if (resource && (
- resource.menuKind === ContextMenuKind.COLLECTION_FILES_ITEM ||
- resource.menuKind === ContextMenuKind.READONLY_COLLECTION_FILES_ITEM)) {
+ if (resource && [
+ ContextMenuKind.COLLECTION_FILE_ITEM,
+ ContextMenuKind.READONLY_COLLECTION_FILE_ITEM,
+ ContextMenuKind.COLLECTION_DIRECTORY_ITEM,
+ ContextMenuKind.READONLY_COLLECTION_DIRECTORY_ITEM ].indexOf(resource.menuKind as ContextMenuKind) > -1) {
const file = getNodeValue(resource.uuid)(state.collectionPanelFiles);
if (file) {
return {
const mapStateToProps = (state: RootState) => {
const { resource } = state.contextMenu;
const currentCollectionUuid = state.collectionPanel.item ? state.collectionPanel.item.uuid : '';
- if (resource && (
- resource.menuKind === ContextMenuKind.COLLECTION_FILES_ITEM ||
- resource.menuKind === ContextMenuKind.READONLY_COLLECTION_FILES_ITEM)) {
+ if (resource && [
+ ContextMenuKind.COLLECTION_FILE_ITEM,
+ ContextMenuKind.READONLY_COLLECTION_FILE_ITEM,
+ ContextMenuKind.COLLECTION_DIRECTORY_ITEM,
+ ContextMenuKind.READONLY_COLLECTION_DIRECTORY_ITEM ].indexOf(resource.menuKind as ContextMenuKind) > -1) {
const file = getNodeValue(resource.uuid)(state.collectionPanelFiles);
if (file) {
const fileUrl = sanitizeToken(getInlineFileUrl(
const mapStateToProps = (state: RootState) => {
const { resource } = state.contextMenu;
const currentCollectionUuid = state.collectionPanel.item ? state.collectionPanel.item.uuid : '';
- if (resource && (
- resource.menuKind === ContextMenuKind.COLLECTION_FILES_ITEM ||
- resource.menuKind === ContextMenuKind.READONLY_COLLECTION_FILES_ITEM)) {
+ if (resource && [
+ ContextMenuKind.COLLECTION_FILE_ITEM,
+ ContextMenuKind.READONLY_COLLECTION_FILE_ITEM,
+ ContextMenuKind.COLLECTION_DIRECTORY_ITEM,
+ ContextMenuKind.READONLY_COLLECTION_DIRECTORY_ITEM ].indexOf(resource.menuKind as ContextMenuKind) > -1) {
const file = getNodeValue(resource.uuid)(state.collectionPanelFiles);
if (file) {
return {
TRASH = "Trash",
COLLECTION_FILES = "CollectionFiles",
READONLY_COLLECTION_FILES = "ReadOnlyCollectionFiles",
- COLLECTION_FILES_ITEM = "CollectionFilesItem",
- READONLY_COLLECTION_FILES_ITEM = "ReadOnlyCollectionFilesItem",
+ COLLECTION_FILE_ITEM = "CollectionFileItem",
+ COLLECTION_DIRECTORY_ITEM = "CollectionDirectoryItem",
+ READONLY_COLLECTION_FILE_ITEM = "ReadOnlyCollectionFileItem",
+ READONLY_COLLECTION_DIRECTORY_ITEM = "ReadOnlyCollectionDirectoryItem",
COLLECTION_FILES_NOT_SELECTED = "CollectionFilesNotSelected",
COLLECTION = 'Collection',
COLLECTION_ADMIN = 'CollectionAdmin',
import { snackbarActions, SnackbarKind } from '~/store/snackbar/snackbar-actions';
import { getNewExtraToken } from '~/store/auth/auth-action';
import { DetailsAttributeComponent } from '~/components/details-attribute/details-attribute';
+import * as moment from 'moment';
type CssRules = 'link' | 'paper' | 'button' | 'actionButton' | 'codeBlock';
render() {
const { classes, open, closeDialog, ...data } = this.props;
const tokenExpiration = data.tokenExpiration
- ? data.tokenExpiration.toLocaleString()
+ ? `${data.tokenExpiration.toLocaleString()} (${moment(data.tokenExpiration).fromNow()})`
: `This token does not have an expiration date`;
return <Dialog
coproc arvboot (~/go/bin/arvados-server boot \
-type test \
-config ${ARVADOS_CONF} \
+ -no-workbench1 \
-own-temporary-database \
-timeout 20m 2> ${ARVADOS_LOG})
trap cleanup ERR EXIT
dependencies:
minimist "^1.2.5"
-moment@^2.27.0:
+moment@2.29.1, moment@^2.27.0:
version "2.29.1"
resolved "https://registry.yarnpkg.com/moment/-/moment-2.29.1.tgz#b2be769fa31940be9eeea6469c075e35006fa3d3"
integrity sha512-kHmoybcPV8Sqy59DwNDY3Jefr64lK/by/da0ViFcuA4DH0vQg5Q6Ze5VimxkfQNSC+Mls/Kx53s7TjP1RhFEDQ==