tail_uuid: activeUser.user.uuid
})
cy.visit(`/collections/${this.testCollection.uuid}`);
- // Check that name & uuid are correct.
+
+ cy.get('[data-cy=linear-progress]').should('exist');
cy.get('[data-cy=linear-progress]').should('not.exist');
+
+ // Check that name & uuid are correct.
cy.get('[data-cy=collection-info-panel]')
.should('contain', this.testCollection.name)
.and('contain', this.testCollection.uuid)
.as('testCollection').then(function() {
cy.loginAs(activeUser);
cy.visit(`/collections/${this.testCollection.uuid}`);
+
+ cy.get('[data-cy=linear-progress]').should('exist');
+ cy.get('[data-cy=linear-progress]').should('not.exist');
+
const nameTransitions = [
['bar', '&'],
['&', 'foo'],
['I ❤️ ⛵️', '...']
];
nameTransitions.forEach(([from, to]) => {
- cy.get('[data-cy=linear-progress]').should('not.exist');
cy.get('[data-cy=collection-files-panel]')
.contains(`${from}`).rightclick();
cy.get('[data-cy=context-menu]')
.as('testCollection').then(function() {
cy.loginAs(activeUser);
cy.visit(`/collections/${this.testCollection.uuid}`);
- // Rename 'bar' to 'subdir/foo'
+
+ cy.get('[data-cy=linear-progress]').should('exist');
cy.get('[data-cy=linear-progress]').should('not.exist');
+
+ // Rename 'bar' to 'subdir/foo'
cy.get('[data-cy=collection-files-panel]')
.contains('bar').rightclick();
cy.get('[data-cy=context-menu]')
});
});
- it('tries to rename a file with an illegal names', function() {
+ 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.
cy.createCollection(adminUser.token, {
.as('testCollection').then(function() {
cy.loginAs(activeUser);
cy.visit(`/collections/${this.testCollection.uuid}`);
+
+ cy.get('[data-cy=linear-progress]').should('exist');
+ cy.get('[data-cy=linear-progress]').should('not.exist');
+
const illegalNamesFromUI = [
['.', "Name cannot be '.' or '..'"],
['..', "Name cannot be '.' or '..'"],
['//foo', 'Empty dir name not allowed']
]
illegalNamesFromUI.forEach(([name, errMsg]) => {
- cy.get('[data-cy=linear-progress]').should('not.exist');
cy.get('[data-cy=collection-files-panel]')
.contains('bar').rightclick();
cy.get('[data-cy=context-menu]')
// Check the old version displays as what it is.
cy.loginAs(activeUser)
cy.visit(`/collections/${oldVersionUuid}`);
+
+ cy.get('[data-cy=linear-progress]').should('exist');
+ cy.get('[data-cy=linear-progress]').should('not.exist');
+
cy.get('[data-cy=collection-info-panel]').should('contain', 'This is an old version');
cy.get('[data-cy=read-only-icon]').should('exist');
cy.get('[data-cy=collection-info-panel]').should('contain', colName);
// Visit collection, check basic information
cy.loginAs(activeUser)
cy.visit(`/collections/${this.collection.uuid}`);
+
+ cy.get('[data-cy=linear-progress]').should('exist');
+ cy.get('[data-cy=linear-progress]').should('not.exist');
+
cy.get('[data-cy=collection-info-panel]').should('not.contain', 'This is an old version');
cy.get('[data-cy=read-only-icon]').should('not.exist');
cy.get('[data-cy=collection-version-number]').should('contain', '1');
//
// SPDX-License-Identifier: AGPL-3.0
-describe('Collection panel tests', function () {
+describe('Multi-file deletion tests', function () {
let activeUser;
let adminUser;
.as('testCollection').then(function () {
cy.loginAs(activeUser);
cy.visit(`/collections/${this.testCollection.uuid}`);
+
+ cy.get('[data-cy=linear-progress]').should('exist');
+ cy.get('[data-cy=linear-progress]').should('not.exist');
+
cy.get('[data-cy=collection-files-panel]').within(() => {
cy.get('[type="checkbox"]').first().check();
cy.get('[type="checkbox"]').last().check();
cy.loginAs(activeUser);
cy.visit(`/collections/${this.testCollection.uuid}`);
+ cy.get('[data-cy=linear-progress]').should('exist');
+ cy.get('[data-cy=linear-progress]').should('not.exist');
+
cy.get('[data-cy=virtual-file-tree] > div > i').first().click();
cy.get('[data-cy=collection-files-panel]')
.should('contain', 'foo');
Cypress.Commands.add(
"loginAs", (user) => {
cy.visit(`/token/?api_token=${user.token}`);
+ cy.get('[data-cy=loading-spinner]').should('exist');
+ cy.get('[data-cy=loading-spinner]').should('not.exist');
cy.url().should('contain', '/projects/');
cy.get('div#root').should('contain', 'Arvados Workbench (zzzzz)');
cy.get('div#root').should('not.contain', 'Your account is inactive');
// SPDX-License-Identifier: AGPL-3.0
import * as React from "react";
-import { shallow, configure } from "enzyme";
+import { configure, mount } from "enzyme";
import { FileThumbnail } from "./file-thumbnail";
import { CollectionFileType } from '../../models/collection-file';
import * as Adapter from 'enzyme-adapter-react-16';
+import { Provider } from "react-redux";
+import { combineReducers, createStore } from "redux";
configure({ adapter: new Adapter() });
'default': () => true,
}));
+let store;
+
describe("<FileThumbnail />", () => {
let file;
beforeEach(() => {
+ const initialAuthState = {
+ config: {
+ keepWebServiceUrl: 'http://example.com/',
+ keepWebInlineServiceUrl: 'http://*.collections.example.com/',
+ }
+ }
+ store = createStore(combineReducers({
+ auth: (state: any = initialAuthState, action: any) => state,
+ }));
+
file = {
name: 'test-image',
type: CollectionFileType.FILE,
});
it("renders file thumbnail with proper src", () => {
- const fileThumbnail = shallow(<FileThumbnail file={file} />);
- expect(fileThumbnail.html()).toBe('<img class="Component-thumbnail-1" alt="test-image" src="http://example.com/c=zzzzz-4zz18-0123456789abcde/test-image.jpg?api_token=v2/zzzzz-gj3su-0123456789abcde/xxxxxxtokenxxxxx"/>');
+ const fileThumbnail = mount(<Provider store={store}><FileThumbnail file={file} /></Provider>);
+ expect(fileThumbnail.html()).toBe('<img class="Component-thumbnail-1" alt="test-image" src="http://zzzzz-4zz18-0123456789abcde.collections.example.com/test-image.jpg?api_token=v2/zzzzz-gj3su-0123456789abcde/xxxxxxtokenxxxxx">');
});
});
import { withStyles, WithStyles } from '@material-ui/core';
import { FileTreeData } from '~/components/file-tree/file-tree-data';
import { CollectionFileType } from '~/models/collection-file';
-import { sanitizeToken } from "~/views-components/context-menu/actions/helpers";
+import { getInlineFileUrl, sanitizeToken } from "~/views-components/context-menu/actions/helpers";
+import { connect } from "react-redux";
+import { RootState } from "~/store/store";
export interface FileThumbnailProps {
file: FileTreeData;
}
}));
-const ImageFileThumbnail = imageFileThumbnailStyle(
- ({ classes, file }: WithStyles<ImageFileThumbnailCssRules> & FileThumbnailProps) =>
+interface ImageFileThumbnailProps {
+ keepWebServiceUrl: string;
+ keepWebInlineServiceUrl: string;
+}
+
+const mapStateToProps = ({ auth }: RootState): ImageFileThumbnailProps => ({
+ keepWebServiceUrl: auth.config.keepWebServiceUrl,
+ keepWebInlineServiceUrl: auth.config.keepWebInlineServiceUrl,
+});
+
+const ImageFileThumbnail = connect(mapStateToProps)(imageFileThumbnailStyle(
+ ({ classes, file, keepWebServiceUrl, keepWebInlineServiceUrl }: WithStyles<ImageFileThumbnailCssRules> & FileThumbnailProps & ImageFileThumbnailProps) =>
<img
className={classes.thumbnail}
alt={file.name}
- src={sanitizeToken(file.url)} />
-);
+ src={sanitizeToken(getInlineFileUrl(file.url, keepWebServiceUrl, keepWebInlineServiceUrl))} />
+));
import { FileViewerAction } from '~/views-components/context-menu/actions/file-viewer-action';
import { getNodeValue } from "~/models/tree";
import { ContextMenuKind } from '~/views-components/context-menu/context-menu';
+import { getInlineFileUrl, sanitizeToken } from "./helpers";
const mapStateToProps = (state: RootState) => {
const { resource } = state.contextMenu;
resource.menuKind === ContextMenuKind.READONLY_COLLECTION_FILES_ITEM)) {
const file = getNodeValue(resource.uuid)(state.collectionPanelFiles);
if (file) {
+ const fileUrl = sanitizeToken(getInlineFileUrl(
+ file.url,
+ state.auth.config.keepWebServiceUrl,
+ state.auth.config.keepWebInlineServiceUrl), true);
return {
- href: file.url.replace(state.auth.config.keepWebServiceUrl, state.auth.config.keepWebInlineServiceUrl),
+ href: fileUrl,
kind: 'file',
currentCollectionUuid
};
// SPDX-License-Identifier: AGPL-3.0
import * as React from "react";
-import { connect } from 'react-redux';
import { ListItemIcon, ListItemText, ListItem } from "@material-ui/core";
import { OpenIcon } from "~/components/icon/icon";
-import { sanitizeToken } from "./helpers";
-import { RootState } from "~/store/store";
export const FileViewerAction = (props: any) => {
- const {
- keepWebServiceUrl,
- keepWebInlineServiceUrl,
- } = props;
-
return props.href
? <a
style={{ textDecoration: 'none' }}
- href={sanitizeToken(props.href.replace(keepWebServiceUrl, keepWebInlineServiceUrl), true)}
+ href={props.href}
target="_blank"
onClick={props.onClick}>
<ListItem button>
</a>
: null;
};
-
-const mapStateToProps = ({ auth }: RootState): any => ({
- keepWebServiceUrl: auth.config.keepWebServiceUrl,
- keepWebInlineServiceUrl: auth.config.keepWebInlineServiceUrl,
-});
-
-
-export default connect(mapStateToProps, null)(FileViewerAction);
//
// SPDX-License-Identifier: AGPL-3.0
-import { sanitizeToken, getClipboardUrl } from "./helpers";
+import { sanitizeToken, getClipboardUrl, getInlineFileUrl } from "./helpers";
describe('helpers', () => {
// given
expect(result).toBe('http://localhost?redirectTo=https://example.com/c=zzzzz/LIMS/1.html');
});
});
+
+ describe('getInlineFileUrl', () => {
+ it('should add the collection\'s uuid to the hostname', () => {
+ // when
+ const webDavUrlA = 'https://*.collections.example.com/';
+ const webDavUrlB = 'https://*--collections.example.com/';
+ const webDavDownloadUrl = 'https://example.com/';
+
+ // then
+ expect(getInlineFileUrl(url, webDavDownloadUrl, webDavUrlA))
+ .toBe('https://zzzzz.collections.example.com/t=v2/a/b/LIMS/1.html');
+ expect(getInlineFileUrl(url, webDavDownloadUrl, webDavUrlB))
+ .toBe('https://zzzzz--collections.example.com/t=v2/a/b/LIMS/1.html');
+ });
+
+ it('should keep the url the same when no inline url available', () => {
+ // when
+ const webDavUrl = '';
+ const webDavDownloadUrl = 'https://example.com/';
+ const result = getInlineFileUrl(url, webDavDownloadUrl, webDavUrl);
+
+ // then
+ expect(result).toBe('https://example.com/c=zzzzz/t=v2/a/b/LIMS/1.html');
+ });
+
+ it('should replace the url when available', () => {
+ // when
+ const webDavUrl = 'https://download.example.com/';
+ const webDavDownloadUrl = 'https://example.com/';
+ const result = getInlineFileUrl(url, webDavDownloadUrl, webDavUrl);
+
+ // then
+ expect(result).toBe('https://download.example.com/c=zzzzz/t=v2/a/b/LIMS/1.html');
+ });
+ });
});
\ No newline at end of file
return shouldSanitizeToken ? `${origin}?redirectTo=${url}` : `${origin}${url}`;
};
+
+export const getInlineFileUrl = (url: string, keepWebSvcUrl: string, keepWebInlineSvcUrl: string): string => {
+ const collUuidMatch = url.match(/\/c=([a-z0-9-]+)\//);
+ if (collUuidMatch === null) { return ''; }
+ const collUuid = collUuidMatch[1];
+ let inlineUrl = keepWebInlineSvcUrl !== ""
+ ? url.replace(keepWebSvcUrl, keepWebInlineSvcUrl)
+ : url;
+ let uuidOnHostname = false;
+ // Inline URLs as 'https://*.collections.example.com' or
+ // 'https://*--collections.example.com' should get the uuid on their hostnames
+ // See: https://doc.arvados.org/v2.1/api/keep-web-urls.html
+ if (inlineUrl.indexOf('*.') > -1) {
+ inlineUrl = inlineUrl.replace('*.', `${collUuid}.`);
+ uuidOnHostname = true;
+ } else if (inlineUrl.indexOf('*--') > -1) {
+ inlineUrl = inlineUrl.replace('*--', `${collUuid}--`);
+ uuidOnHostname = true;
+ }
+ if (uuidOnHostname) {
+ inlineUrl = inlineUrl.replace(`/c=${collUuid}`, '');
+ }
+ return inlineUrl;
+};
\ No newline at end of file
export const WorkbenchLoadingScreen = withStyles(styles)(({ classes }: WithStyles<CssRules>) =>
<Grid container direction="column" alignItems='center' justify='center' className={classes.root}>
<img src='/arvados_logo.png' className={classes.img} />
- <CircularProgress />
+ <CircularProgress data-cy='loading-spinner' />
</Grid>
);