cy.goToPath(`/collections/${this.testCollection.uuid}`);
const names = [
- 'bar', // initial name already set
- '&',
- 'foo',
- '&',
- 'I ❤️ ⛵️',
- '...',
- '#..',
- 'some name with whitespaces',
- 'some name with #2',
- 'is this name legal? I hope it is',
- 'some_file.pdf#',
- 'some_file.pdf?',
- '?some_file.pdf',
- 'some%file.pdf',
- 'some%2Ffile.pdf',
- 'some%22file.pdf',
- 'some%20file.pdf',
+ "bar", // initial name already set
+ "&",
+ "foo",
+ "&",
+ "I ❤️ ⛵️",
+ "...",
+ "#..",
+ "some name with whitespaces",
+ "some name with #2",
+ "is this name legal? I hope it is",
+ "some_file.pdf#",
+ "some_file.pdf?",
+ "?some_file.pdf",
+ "some%file.pdf",
+ "some%2Ffile.pdf",
+ "some%22file.pdf",
+ "some%20file.pdf",
"G%C3%BCnter's%20file.pdf",
- 'table%&?*2',
- 'bar' // make sure we can go back to the original name as a last step
+ "table%&?*2",
+ "bar", // make sure we can go back to the original name as a last step
];
- cy.intercept({method: 'PUT', url: '**/arvados/v1/collections/*'}).as('renameRequest');
++ cy.intercept({ method: "PUT", url: "**/arvados/v1/collections/*" }).as("renameRequest");
eachPair(names, (from, to) => {
- cy.waitForDom().get('[data-cy=collection-files-panel]')
- .contains(`${from}`).rightclick();
- cy.get('[data-cy=context-menu]')
- .contains('Rename')
- .click();
- cy.get('[data-cy=form-dialog]')
- .should('contain', 'Rename')
+ cy.waitForDom().get("[data-cy=collection-files-panel]").contains(`${from}`).rightclick();
+ cy.get("[data-cy=context-menu]").contains("Rename").click();
+ cy.get("[data-cy=form-dialog]")
+ .should("contain", "Rename")
+ .within(() => {
+ cy.get("input").type("{selectall}{backspace}").type(to, { parseSpecialCharSequences: false });
+ });
+ cy.get("[data-cy=form-submit-btn]").click();
++ cy.wait("@renameRequest");
+ cy.get("[data-cy=collection-files-panel]").should("not.contain", `${from}`).and("contain", `${to}`);
+ });
+ });
+ });
+
+ it("renames a file to a different directory", function () {
+ // Creates the collection using the admin token so we can set up
+ // a bogus manifest text without block signatures.
+ 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(function () {
+ cy.loginAs(activeUser);
+ cy.goToPath(`/collections/${this.testCollection.uuid}`);
+
+ ["subdir", "G%C3%BCnter's%20file", "table%&?*2"].forEach(subdir => {
+ cy.waitForDom().get("[data-cy=collection-files-panel]").contains("bar").rightclick();
+ cy.get("[data-cy=context-menu]").contains("Rename").click();
+ cy.get("[data-cy=form-dialog]")
+ .should("contain", "Rename")
+ .within(() => {
+ cy.get("input").type(`{selectall}{backspace}${subdir}/foo`);
+ });
+ cy.get("[data-cy=form-submit-btn]").click();
+ cy.get("[data-cy=collection-files-panel]").should("not.contain", "bar").and("contain", subdir);
+ cy.get("[data-cy=collection-files-panel]").contains(subdir).click();
+
+ // Rename 'subdir/foo' to 'bar'
+ cy.wait(1000);
+ cy.get("[data-cy=collection-files-panel]").contains("foo").rightclick();
+ cy.get("[data-cy=context-menu]").contains("Rename").click();
+ cy.get("[data-cy=form-dialog]")
+ .should("contain", "Rename")
.within(() => {
- cy.get('input')
- .type('{selectall}{backspace}')
- .type(to, { parseSpecialCharSequences: false });
+ cy.get("input").should("have.value", `${subdir}/foo`).type(`{selectall}{backspace}bar`);
});
- cy.get('[data-cy=form-submit-btn]').click();
- cy.wait('@renameRequest');
- cy.get('[data-cy=collection-files-panel]')
- .should('not.contain', `${from}`)
- .and('contain', `${to}`);
- })
+ cy.get("[data-cy=form-submit-btn]").click();
+
+ // need to wait for dialog to dismiss
+ cy.get("[data-cy=form-dialog]").should("not.exist");
+
+ cy.waitForDom().get("[data-cy=collection-files-panel]").contains("Home").click();
+
+ cy.wait(2000);
+ cy.get("[data-cy=collection-files-panel]")
+ .should("contain", subdir) // empty dir kept
+ .and("contain", "bar");
+
+ cy.get("[data-cy=collection-files-panel]").contains(subdir).rightclick();
+ cy.get("[data-cy=context-menu]").contains("Remove").click();
+ cy.get("[data-cy=confirmation-dialog-ok-btn]").click();
+ cy.get("[data-cy=form-dialog]").should("not.exist");
+ });
});
});
- it('renames a file to a different directory', function () {
++ it("renames a file to a different directory", function () {
+ // Creates the collection using the admin token so we can set up
+ // a bogus manifest text without block signatures.
+ 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"
++ manifest_text: ". 37b51d194a7513e45b56f6524f2d51f2+3 0:3:bar\n",
+ })
- .as('testCollection').then(function () {
++ .as("testCollection")
++ .then(function () {
+ cy.loginAs(activeUser);
+ cy.goToPath(`/collections/${this.testCollection.uuid}`);
+
- ['subdir', 'G%C3%BCnter\'s%20file', 'table%&?*2'].forEach((subdir) => {
- cy.waitForDom().get('[data-cy=collection-files-panel]')
- .contains('bar').rightclick();
- cy.get('[data-cy=context-menu]')
- .contains('Rename')
- .click();
- cy.get('[data-cy=form-dialog]')
- .should('contain', 'Rename')
++ ["subdir", "G%C3%BCnter's%20file", "table%&?*2"].forEach(subdir => {
++ cy.waitForDom().get("[data-cy=collection-files-panel]").contains("bar").rightclick();
++ cy.get("[data-cy=context-menu]").contains("Rename").click();
++ cy.get("[data-cy=form-dialog]")
++ .should("contain", "Rename")
+ .within(() => {
- cy.get('input').type(`{selectall}{backspace}${subdir}/foo`);
++ cy.get("input").type(`{selectall}{backspace}${subdir}/foo`);
+ });
- cy.get('[data-cy=form-submit-btn]').click();
- cy.get('[data-cy=collection-files-panel]')
- .should('not.contain', 'bar')
- .and('contain', subdir);
- cy.get('[data-cy=collection-files-panel]').contains(subdir).click();
++ cy.get("[data-cy=form-submit-btn]").click();
++ cy.get("[data-cy=collection-files-panel]").should("not.contain", "bar").and("contain", subdir);
++ cy.get("[data-cy=collection-files-panel]").contains(subdir).click();
+
+ // Rename 'subdir/foo' to 'bar'
+ cy.wait(1000);
- cy.get('[data-cy=collection-files-panel]')
- .contains('foo').rightclick();
- cy.get('[data-cy=context-menu]')
- .contains('Rename')
- .click();
- cy.get('[data-cy=form-dialog]')
- .should('contain', 'Rename')
++ cy.get("[data-cy=collection-files-panel]").contains("foo").rightclick();
++ cy.get("[data-cy=context-menu]").contains("Rename").click();
++ cy.get("[data-cy=form-dialog]")
++ .should("contain", "Rename")
+ .within(() => {
- cy.get('input')
- .should('have.value', `${subdir}/foo`)
- .type(`{selectall}{backspace}bar`);
++ cy.get("input").should("have.value", `${subdir}/foo`).type(`{selectall}{backspace}bar`);
+ });
- cy.get('[data-cy=form-submit-btn]').click();
++ cy.get("[data-cy=form-submit-btn]").click();
+
+ // need to wait for dialog to dismiss
- cy.get('[data-cy=form-dialog]').should('not.exist');
++ cy.get("[data-cy=form-dialog]").should("not.exist");
+
- cy.waitForDom().get('[data-cy=collection-files-panel]')
- .contains('Home')
- .click();
++ cy.waitForDom().get("[data-cy=collection-files-panel]").contains("Home").click();
+
+ cy.wait(2000);
- cy.get('[data-cy=collection-files-panel]')
- .should('contain', subdir) // empty dir kept
- .and('contain', 'bar');
-
- cy.get('[data-cy=collection-files-panel]')
- .contains(subdir).rightclick();
- cy.get('[data-cy=context-menu]')
- .contains('Remove')
- .click();
- cy.get('[data-cy=confirmation-dialog-ok-btn]').click();
- cy.get('[data-cy=form-dialog]').should('not.exist');
++ cy.get("[data-cy=collection-files-panel]")
++ .should("contain", subdir) // empty dir kept
++ .and("contain", "bar");
++
++ cy.get("[data-cy=collection-files-panel]").contains(subdir).rightclick();
++ cy.get("[data-cy=context-menu]").contains("Remove").click();
++ cy.get("[data-cy=confirmation-dialog-ok-btn]").click();
++ cy.get("[data-cy=form-dialog]").should("not.exist");
+ });
+ });
+ });
+
- it('shows collection owner', () => {
+ it("shows collection owner", () => {
cy.createCollection(adminUser.token, {
name: `Test collection ${Math.floor(Math.random() * 999999)}`,
owner_uuid: activeUser.user.uuid,
name: colName,
owner_uuid: activeUser.user.uuid,
preserve_version: true,
- manifest_text: ". 37b51d194a7513e45b56f6524f2d51f2+3 0:3:foo 0:3:bar\n"
+ manifest_text: ". 37b51d194a7513e45b56f6524f2d51f2+3 0:3:foo 0:3:bar\n",
})
- .as('collection').then(function () {
+ .as("collection")
+ .then(function () {
// Visit collection, check basic information
- cy.loginAs(activeUser)
+ cy.loginAs(activeUser);
cy.goToPath(`/collections/${this.collection.uuid}`);
- 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');
- cy.get('[data-cy=collection-info-panel]').should('contain', colName);
- cy.get('[data-cy=collection-files-panel]').should('contain', 'foo').and('contain', 'bar');
+ 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");
+ cy.get("[data-cy=collection-info-panel]").should("contain", colName);
+ cy.get("[data-cy=collection-files-panel]").should("contain", "foo").and("contain", "bar");
// Modify collection, expect version number change
- cy.get('[data-cy=collection-files-panel]').contains('foo').rightclick();
- cy.get('[data-cy=context-menu]').contains('Remove').click();
- cy.get('[data-cy=confirmation-dialog]').should('contain', 'Removing file');
- cy.get('[data-cy=confirmation-dialog-ok-btn]').click();
- cy.get('[data-cy=collection-version-number]').should('contain', '2');
- cy.get('[data-cy=collection-files-panel]').should('not.contain', 'foo').and('contain', 'bar');
+ cy.get("[data-cy=collection-files-panel]").contains("foo").rightclick();
+ cy.get("[data-cy=context-menu]").contains("Remove").click();
+ cy.get("[data-cy=confirmation-dialog]").should("contain", "Removing file");
+ cy.get("[data-cy=confirmation-dialog-ok-btn]").click();
+ cy.get("[data-cy=collection-version-number]").should("contain", "2");
+ cy.get("[data-cy=collection-files-panel]").should("not.contain", "foo").and("contain", "bar");
// Click on version number, check version browser. Click on past version.
- cy.get('[data-cy=collection-version-browser]').should('not.exist');
- cy.get('[data-cy=collection-version-number]').contains('2').click();
- cy.get('[data-cy=collection-version-browser]')
- .should('contain', 'Nr').and('contain', 'Size').and('contain', 'Date')
+ cy.get("[data-cy=collection-version-browser]").should("not.exist");
+ cy.get("[data-cy=collection-version-number]").contains("2").click();
+ cy.get("[data-cy=collection-version-browser]")
+ .should("contain", "Nr")
+ .and("contain", "Size")
+ .and("contain", "Date")
.within(() => {
// Version 1: 6 bytes in size
- cy.get('[data-cy=collection-version-browser-select-1]')
- .should('contain', '1')
- .and('contain', '6 B')
- .and('contain', adminUser.user.full_name);
+ cy.get("[data-cy=collection-version-browser-select-1]")
+ .should("contain", "1")
+ .and("contain", "6 B")
+ .and("contain", adminUser.user.full_name);
// Version 2: 3 bytes in size (one file removed)
- cy.get('[data-cy=collection-version-browser-select-2]')
- .should('contain', '2')
- .and('contain', '3 B')
- .and('contain', activeUser.user.full_name);
- cy.get('[data-cy=collection-version-browser-select-3]')
- .should('not.exist');
- cy.get('[data-cy=collection-version-browser-select-1]')
- .click();
+ cy.get("[data-cy=collection-version-browser-select-2]")
+ .should("contain", "2")
+ .and("contain", "3 B")
+ .and("contain", activeUser.user.full_name);
+ cy.get("[data-cy=collection-version-browser-select-3]").should("not.exist");
+ cy.get("[data-cy=collection-version-browser-select-1]").click();
});
- 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-version-number]').should('contain', '1');
- cy.get('[data-cy=collection-info-panel]').should('contain', colName);
- cy.get('[data-cy=collection-files-panel]')
- .should('contain', 'foo').and('contain', 'bar');
+ 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-version-number]").should("contain", "1");
+ cy.get("[data-cy=collection-info-panel]").should("contain", colName);
+ cy.get("[data-cy=collection-files-panel]").should("contain", "foo").and("contain", "bar");
// Check that only old collection action are available on context menu
- cy.get('[data-cy=collection-panel-options-btn]').click();
- cy.get('[data-cy=context-menu]')
- .should('contain', 'Restore version')
- .and('not.contain', 'Add to favorites');
- cy.get('body').click(); // Collapse the menu avoiding details panel expansion
+ cy.get("[data-cy=collection-panel-options-btn]").click();
+ cy.get("[data-cy=context-menu]").should("contain", "Restore version").and("not.contain", "Add to favorites");
+ cy.get("body").click(); // Collapse the menu avoiding details panel expansion
// Click on "head version" link, confirm that it's the latest version.
- cy.get('[data-cy=collection-info-panel]').contains('head version').click();
- 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', '2');
- cy.get('[data-cy=collection-info-panel]').should('contain', colName);
- cy.get('[data-cy=collection-files-panel]').
- should('not.contain', 'foo').and('contain', 'bar');
+ cy.get("[data-cy=collection-info-panel]").contains("head version").click();
+ 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", "2");
+ cy.get("[data-cy=collection-info-panel]").should("contain", colName);
+ cy.get("[data-cy=collection-files-panel]").should("not.contain", "foo").and("contain", "bar");
// Check that old collection action isn't available on context menu
- cy.get('[data-cy=collection-panel-options-btn]').click()
- cy.get('[data-cy=context-menu]').should('not.contain', 'Restore version')
- cy.get('body').click(); // Collapse the menu avoiding details panel expansion
+ cy.get("[data-cy=collection-panel-options-btn]").click();
+ cy.get("[data-cy=context-menu]").should("not.contain", "Restore version");
+ cy.get("body").click(); // Collapse the menu avoiding details panel expansion
// Make another change, confirm new version.
- cy.get('[data-cy=collection-panel-options-btn]').click();
- cy.get('[data-cy=context-menu]').contains('Edit collection').click();
- cy.get('[data-cy=form-dialog]')
- .should('contain', 'Edit Collection')
+ cy.get("[data-cy=collection-panel-options-btn]").click();
+ cy.get("[data-cy=context-menu]").contains("Edit collection").click();
+ cy.get("[data-cy=form-dialog]")
+ .should("contain", "Edit Collection")
.within(() => {
// appends some text
- cy.get('input').first().type(' renamed');
+ cy.get("input").first().type(" renamed");
});
- cy.get('[data-cy=form-submit-btn]').click();
- 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', '3');
- cy.get('[data-cy=collection-info-panel]').should('contain', colName + ' renamed');
- cy.get('[data-cy=collection-files-panel]')
- .should('not.contain', 'foo').and('contain', 'bar');
- cy.get('[data-cy=collection-version-browser-select-3]')
- .should('contain', '3').and('contain', '3 B');
+ cy.get("[data-cy=form-submit-btn]").click();
+ 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", "3");
+ cy.get("[data-cy=collection-info-panel]").should("contain", colName + " renamed");
+ cy.get("[data-cy=collection-files-panel]").should("not.contain", "foo").and("contain", "bar");
+ cy.get("[data-cy=collection-version-browser-select-3]").should("contain", "3").and("contain", "3 B");
// Check context menus on version browser
- cy.get("[data-cy=collection-version-browser-select-3]").rightclick({ force: true });
+ cy.waitForDom();
- cy.get('[data-cy=collection-version-browser-select-3]').rightclick()
- cy.get('[data-cy=context-menu]')
- .should('contain', 'Add to favorites')
- .and('contain', 'Make a copy')
- .and('contain', 'Edit collection');
- cy.get('body').click();
++ cy.get("[data-cy=collection-version-browser-select-3]").rightclick();
+ cy.get("[data-cy=context-menu]")
+ .should("contain", "Add to favorites")
+ .and("contain", "Make a copy")
+ .and("contain", "Edit collection");
+ cy.get("body").click();
// (and now an old version...)
- cy.get('[data-cy=collection-version-browser-select-1]').rightclick()
- cy.get('[data-cy=context-menu]')
- .should('not.contain', 'Add to favorites')
- .and('contain', 'Make a copy')
- .and('not.contain', 'Edit collection');
- cy.get('body').click();
+ cy.get("[data-cy=collection-version-browser-select-1]").rightclick();
+ cy.get("[data-cy=context-menu]")
+ .should("not.contain", "Add to favorites")
+ .and("contain", "Make a copy")
+ .and("not.contain", "Edit collection");
+ cy.get("body").click();
// Restore first version
- cy.get('[data-cy=collection-version-browser]').within(() => {
- cy.get('[data-cy=collection-version-browser-select-1]').click();
+ cy.get("[data-cy=collection-version-browser]").within(() => {
+ cy.get("[data-cy=collection-version-browser-select-1]").click();
});
- cy.get('[data-cy=collection-panel-options-btn]').click()
- cy.get('[data-cy=context-menu]').contains('Restore version').click();
- cy.get('[data-cy=confirmation-dialog]').should('contain', 'Restore version');
- cy.get('[data-cy=confirmation-dialog-ok-btn]').click();
- cy.get('[data-cy=collection-info-panel]')
- .should('not.contain', 'This is an old version');
- cy.get('[data-cy=collection-version-number]').should('contain', '4');
- cy.get('[data-cy=collection-info-panel]').should('contain', colName);
- cy.get('[data-cy=collection-files-panel]')
- .should('contain', 'foo').and('contain', 'bar');
+ cy.get("[data-cy=collection-panel-options-btn]").click();
+ cy.get("[data-cy=context-menu]").contains("Restore version").click();
+ cy.get("[data-cy=confirmation-dialog]").should("contain", "Restore version");
+ cy.get("[data-cy=confirmation-dialog-ok-btn]").click();
+ cy.get("[data-cy=collection-info-panel]").should("not.contain", "This is an old version");
+ cy.get("[data-cy=collection-version-number]").should("contain", "4");
+ cy.get("[data-cy=collection-info-panel]").should("contain", colName);
+ cy.get("[data-cy=collection-files-panel]").should("contain", "foo").and("contain", "bar");
});
});
- it("creates collection from selected files of another collection", () => {
- it('copies selected files into new collection', () => {
++ it("copies selected files into new collection", () => {
+ cy.createCollection(adminUser.token, {
+ name: `Test Collection ${Math.floor(Math.random() * 999999)}`,
+ owner_uuid: activeUser.user.uuid,
+ preserve_version: true,
- manifest_text: ". 37b51d194a7513e45b56f6524f2d51f2+3 0:3:foo 0:3:bar\n"
++ manifest_text: ". 37b51d194a7513e45b56f6524f2d51f2+3 0:3:foo 0:3:bar\n",
+ })
- .as('collection').then(function () {
++ .as("collection")
++ .then(function () {
+ // Visit collection, check basic information
- cy.loginAs(activeUser)
++ cy.loginAs(activeUser);
+ cy.goToPath(`/collections/${this.collection.uuid}`);
+
- cy.get('[data-cy=collection-files-panel]').within(() => {
- cy.get('input[type=checkbox]').first().click();
++ cy.get("[data-cy=collection-files-panel]").within(() => {
++ cy.get("input[type=checkbox]").first().click();
+ });
+
- cy.get('[data-cy=collection-files-panel-options-btn]').click();
- cy.get('[data-cy=context-menu]').contains('Copy selected into new collection').click();
++ cy.get("[data-cy=collection-files-panel-options-btn]").click();
++ cy.get("[data-cy=context-menu]").contains("Copy selected into new collection").click();
+
- cy.get('[data-cy=form-dialog]').contains('Projects').click();
++ cy.get("[data-cy=form-dialog]").contains("Projects").click();
+
- cy.get('[data-cy=form-submit-btn]').click();
++ cy.get("[data-cy=form-submit-btn]").click();
+
- cy.waitForDom().get('.layout-pane-primary', { timeout: 12000 }).contains('Projects').click();
++ cy.waitForDom().get(".layout-pane-primary", { timeout: 12000 }).contains("Projects").click();
+
- cy.waitForDom().get('main').contains(`Files extracted from: ${this.collection.name}`).click();
- cy.get('[data-cy=collection-files-panel]')
- .and('contain', 'bar');
++ cy.waitForDom().get("main").contains(`Files extracted from: ${this.collection.name}`).click();
++ cy.get("[data-cy=collection-files-panel]").and("contain", "bar");
+ });
+ });
+
- it('copies selected files into existing collection', () => {
++ it("copies selected files into existing collection", () => {
+ cy.createCollection(adminUser.token, {
+ name: `Test Collection ${Math.floor(Math.random() * 999999)}`,
+ owner_uuid: activeUser.user.uuid,
+ preserve_version: true,
- manifest_text: ". 37b51d194a7513e45b56f6524f2d51f2+3 0:3:foo 0:3:bar\n"
- }).as('sourceCollection')
++ manifest_text: ". 37b51d194a7513e45b56f6524f2d51f2+3 0:3:foo 0:3:bar\n",
++ }).as("sourceCollection");
+
+ cy.createCollection(adminUser.token, {
+ name: `Destination Collection ${Math.floor(Math.random() * 999999)}`,
+ owner_uuid: activeUser.user.uuid,
+ preserve_version: true,
- manifest_text: ""
- }).as('destinationCollection');
++ manifest_text: "",
++ }).as("destinationCollection");
+
- cy.getAll('@sourceCollection', '@destinationCollection').then(function ([sourceCollection, destinationCollection]) {
- // Visit collection, check basic information
- cy.loginAs(activeUser)
- cy.goToPath(`/collections/${sourceCollection.uuid}`);
++ cy.getAll("@sourceCollection", "@destinationCollection").then(function ([sourceCollection, destinationCollection]) {
++ // Visit collection, check basic information
++ cy.loginAs(activeUser);
++ cy.goToPath(`/collections/${sourceCollection.uuid}`);
+
- cy.get('[data-cy=collection-files-panel]').within(() => {
- cy.get('input[type=checkbox]').first().click();
- });
++ cy.get("[data-cy=collection-files-panel]").within(() => {
++ cy.get("input[type=checkbox]").first().click();
++ });
+
- cy.get('[data-cy=collection-files-panel-options-btn]').click();
- cy.get('[data-cy=context-menu]').contains('Copy selected into existing collection').click();
++ cy.get("[data-cy=collection-files-panel-options-btn]").click();
++ cy.get("[data-cy=context-menu]").contains("Copy selected into existing collection").click();
+
- cy.get('[data-cy=form-dialog]').contains(destinationCollection.name).click();
++ cy.get("[data-cy=form-dialog]").contains(destinationCollection.name).click();
+
- cy.get('[data-cy=form-submit-btn]').click();
- cy.wait(2000);
++ cy.get("[data-cy=form-submit-btn]").click();
++ cy.wait(2000);
+
- cy.goToPath(`/collections/${destinationCollection.uuid}`);
++ cy.goToPath(`/collections/${destinationCollection.uuid}`);
+
- cy.get('main').contains(destinationCollection.name).should('exist');
- cy.get('[data-cy=collection-files-panel]')
- .and('contain', 'bar');
- });
++ cy.get("main").contains(destinationCollection.name).should("exist");
++ cy.get("[data-cy=collection-files-panel]").and("contain", "bar");
++ });
+ });
+
- it('copies selected files into separate collections', () => {
++ it("copies selected files into separate collections", () => {
+ cy.createCollection(adminUser.token, {
+ name: `Test Collection ${Math.floor(Math.random() * 999999)}`,
+ owner_uuid: activeUser.user.uuid,
+ preserve_version: true,
- manifest_text: ". 37b51d194a7513e45b56f6524f2d51f2+3 0:3:foo 0:3:bar\n"
- }).as('sourceCollection')
++ manifest_text: ". 37b51d194a7513e45b56f6524f2d51f2+3 0:3:foo 0:3:bar\n",
++ }).as("sourceCollection");
+
- cy.getAll('@sourceCollection').then(function ([sourceCollection]) {
- // Visit collection, check basic information
- cy.loginAs(activeUser)
- cy.goToPath(`/collections/${sourceCollection.uuid}`);
++ cy.getAll("@sourceCollection").then(function ([sourceCollection]) {
++ // Visit collection, check basic information
++ cy.loginAs(activeUser);
++ cy.goToPath(`/collections/${sourceCollection.uuid}`);
+
- // Select both files
- cy.waitForDom().get('[data-cy=collection-files-panel]').within(() => {
- cy.get('input[type=checkbox]').first().click();
- cy.get('input[type=checkbox]').last().click();
++ // Select both files
++ cy.waitForDom()
++ .get("[data-cy=collection-files-panel]")
++ .within(() => {
++ cy.get("input[type=checkbox]").first().click();
++ cy.get("input[type=checkbox]").last().click();
+ });
+
- // Copy to separate collections
- cy.get('[data-cy=collection-files-panel-options-btn]').click();
- cy.get('[data-cy=context-menu]').contains('Copy selected into separate collections').click();
- cy.get('[data-cy=form-dialog]').contains('Projects').click();
- cy.get('[data-cy=form-submit-btn]').click();
-
- // Verify created collections
- cy.waitForDom().get('.layout-pane-primary', { timeout: 12000 }).contains('Projects').click();
- cy.get('main').contains(`File copied from collection ${sourceCollection.name}/foo`).click();
- cy.get('[data-cy=collection-files-panel]')
- .and('contain', 'foo');
- cy.get('.layout-pane-primary').contains('Projects').click();
- cy.get('main').contains(`File copied from collection ${sourceCollection.name}/bar`).click();
- cy.get('[data-cy=collection-files-panel]')
- .and('contain', 'bar');
-
- // Verify separate collection menu items not present when single file selected
- // Wait for dom for collection to re-render
- cy.waitForDom().get('[data-cy=collection-files-panel]').within(() => {
- cy.get('input[type=checkbox]').first().click();
++ // Copy to separate collections
++ cy.get("[data-cy=collection-files-panel-options-btn]").click();
++ cy.get("[data-cy=context-menu]").contains("Copy selected into separate collections").click();
++ cy.get("[data-cy=form-dialog]").contains("Projects").click();
++ cy.get("[data-cy=form-submit-btn]").click();
++
++ // Verify created collections
++ cy.waitForDom().get(".layout-pane-primary", { timeout: 12000 }).contains("Projects").click();
++ cy.get("main").contains(`File copied from collection ${sourceCollection.name}/foo`).click();
++ cy.get("[data-cy=collection-files-panel]").and("contain", "foo");
++ cy.get(".layout-pane-primary").contains("Projects").click();
++ cy.get("main").contains(`File copied from collection ${sourceCollection.name}/bar`).click();
++ cy.get("[data-cy=collection-files-panel]").and("contain", "bar");
++
++ // Verify separate collection menu items not present when single file selected
++ // Wait for dom for collection to re-render
++ cy.waitForDom()
++ .get("[data-cy=collection-files-panel]")
++ .within(() => {
++ cy.get("input[type=checkbox]").first().click();
+ });
- cy.get('[data-cy=collection-files-panel-options-btn]').click();
- cy.get('[data-cy=context-menu]').should('not.contain', 'Copy selected into separate collections');
- cy.get('[data-cy=context-menu]').should('not.contain', 'Move selected into separate collections');
- });
++ cy.get("[data-cy=collection-files-panel-options-btn]").click();
++ cy.get("[data-cy=context-menu]").should("not.contain", "Copy selected into separate collections");
++ cy.get("[data-cy=context-menu]").should("not.contain", "Move selected into separate collections");
++ });
+ });
+
- it('moves selected files into new collection', () => {
++ it("moves selected files into new collection", () => {
cy.createCollection(adminUser.token, {
name: `Test Collection ${Math.floor(Math.random() * 999999)}`,
owner_uuid: activeUser.user.uuid,
preserve_version: true,
- manifest_text: ". 37b51d194a7513e45b56f6524f2d51f2+3 0:3:foo 0:3:bar\n"
+ manifest_text: ". 37b51d194a7513e45b56f6524f2d51f2+3 0:3:foo 0:3:bar\n",
})
- .as('collection').then(function () {
+ .as("collection")
+ .then(function () {
// Visit collection, check basic information
- cy.loginAs(activeUser)
+ cy.loginAs(activeUser);
cy.goToPath(`/collections/${this.collection.uuid}`);
- cy.get('[data-cy=collection-files-panel]').within(() => {
- cy.get('input[type=checkbox]').first().click();
+ cy.get("[data-cy=collection-files-panel]").within(() => {
+ cy.get("input[type=checkbox]").first().click();
});
- cy.get('[data-cy=collection-files-panel-options-btn]').click();
- cy.get('[data-cy=context-menu]').contains('Move selected into new collection').click();
+ cy.get("[data-cy=collection-files-panel-options-btn]").click();
- cy.get("[data-cy=context-menu]").contains("Create a new collection with selected").click();
++ cy.get("[data-cy=context-menu]").contains("Move selected into new collection").click();
- cy.get('[data-cy=form-dialog]').contains('Projects').click();
+ cy.get("[data-cy=form-dialog]").contains("Projects").click();
- cy.get('[data-cy=form-submit-btn]').click();
+ cy.get("[data-cy=form-submit-btn]").click();
- cy.waitForDom().get('.layout-pane-primary', { timeout: 12000 }).contains('Projects').click();
+ cy.waitForDom().get(".layout-pane-primary", { timeout: 12000 }).contains("Projects").click();
- cy.get("main").contains(`Files extracted from: ${this.collection.name}`).should("exist");
- cy.get('main').contains(`Files moved from: ${this.collection.name}`).click();
- cy.get('[data-cy=collection-files-panel]')
- .and('contain', 'bar');
++ cy.get("main").contains(`Files moved from: ${this.collection.name}`).click();
++ cy.get("[data-cy=collection-files-panel]").and("contain", "bar");
+ });
+ });
+
- it('moves selected files into existing collection', () => {
++ it("moves selected files into existing collection", () => {
+ cy.createCollection(adminUser.token, {
+ name: `Test Collection ${Math.floor(Math.random() * 999999)}`,
+ owner_uuid: activeUser.user.uuid,
+ preserve_version: true,
- manifest_text: ". 37b51d194a7513e45b56f6524f2d51f2+3 0:3:foo 0:3:bar\n"
- }).as('sourceCollection')
++ manifest_text: ". 37b51d194a7513e45b56f6524f2d51f2+3 0:3:foo 0:3:bar\n",
++ }).as("sourceCollection");
+
+ cy.createCollection(adminUser.token, {
+ name: `Destination Collection ${Math.floor(Math.random() * 999999)}`,
+ owner_uuid: activeUser.user.uuid,
+ preserve_version: true,
- manifest_text: ""
- }).as('destinationCollection');
++ manifest_text: "",
++ }).as("destinationCollection");
+
- cy.getAll('@sourceCollection', '@destinationCollection').then(function ([sourceCollection, destinationCollection]) {
- // Visit collection, check basic information
- cy.loginAs(activeUser)
- cy.goToPath(`/collections/${sourceCollection.uuid}`);
++ cy.getAll("@sourceCollection", "@destinationCollection").then(function ([sourceCollection, destinationCollection]) {
++ // Visit collection, check basic information
++ cy.loginAs(activeUser);
++ cy.goToPath(`/collections/${sourceCollection.uuid}`);
+
- cy.get('[data-cy=collection-files-panel]').within(() => {
- cy.get('input[type=checkbox]').first().click();
- });
++ cy.get("[data-cy=collection-files-panel]").within(() => {
++ cy.get("input[type=checkbox]").first().click();
++ });
+
- cy.get('[data-cy=collection-files-panel-options-btn]').click();
- cy.get('[data-cy=context-menu]').contains('Move selected into existing collection').click();
++ cy.get("[data-cy=collection-files-panel-options-btn]").click();
++ cy.get("[data-cy=context-menu]").contains("Move selected into existing collection").click();
+
- cy.get('[data-cy=form-dialog]').contains(destinationCollection.name).click();
++ cy.get("[data-cy=form-dialog]").contains(destinationCollection.name).click();
+
- cy.get('[data-cy=form-submit-btn]').click();
- cy.wait(2000);
++ cy.get("[data-cy=form-submit-btn]").click();
++ cy.wait(2000);
+
- cy.goToPath(`/collections/${destinationCollection.uuid}`);
++ cy.goToPath(`/collections/${destinationCollection.uuid}`);
+
- cy.get('main').contains(destinationCollection.name).should('exist');
- cy.get('[data-cy=collection-files-panel]')
- .and('contain', 'bar');
- });
++ cy.get("main").contains(destinationCollection.name).should("exist");
++ cy.get("[data-cy=collection-files-panel]").and("contain", "bar");
++ });
+ });
+
- it('moves selected files into separate collections', () => {
++ it("moves selected files into separate collections", () => {
+ cy.createCollection(adminUser.token, {
+ name: `Test Collection ${Math.floor(Math.random() * 999999)}`,
+ owner_uuid: activeUser.user.uuid,
+ preserve_version: true,
- manifest_text: ". 37b51d194a7513e45b56f6524f2d51f2+3 0:3:foo 0:3:bar\n"
- }).as('sourceCollection')
++ manifest_text: ". 37b51d194a7513e45b56f6524f2d51f2+3 0:3:foo 0:3:bar\n",
++ }).as("sourceCollection");
+
- cy.getAll('@sourceCollection').then(function ([sourceCollection]) {
- // Visit collection, check basic information
- cy.loginAs(activeUser)
- cy.goToPath(`/collections/${sourceCollection.uuid}`);
-
- // Select both files
- cy.get('[data-cy=collection-files-panel]').within(() => {
- cy.get('input[type=checkbox]').first().click();
- cy.get('input[type=checkbox]').last().click();
- });
++ cy.getAll("@sourceCollection").then(function ([sourceCollection]) {
++ // Visit collection, check basic information
++ cy.loginAs(activeUser);
++ cy.goToPath(`/collections/${sourceCollection.uuid}`);
+
- // Copy to separate collections
- cy.get('[data-cy=collection-files-panel-options-btn]').click();
- cy.get('[data-cy=context-menu]').contains('Move selected into separate collections').click();
- cy.get('[data-cy=form-dialog]').contains('Projects').click();
- cy.get('[data-cy=form-submit-btn]').click();
-
- // Verify created collections
- cy.waitForDom().get('.layout-pane-primary', { timeout: 12000 }).contains('Projects').click();
- cy.get('main').contains(`File moved from collection ${sourceCollection.name}/foo`).click();
- cy.get('[data-cy=collection-files-panel]')
- .and('contain', 'foo');
- cy.get('.layout-pane-primary').contains('Projects').click();
- cy.get('main').contains(`File moved from collection ${sourceCollection.name}/bar`).click();
- cy.get('[data-cy=collection-files-panel]')
- .and('contain', 'bar');
++ // Select both files
++ cy.get("[data-cy=collection-files-panel]").within(() => {
++ cy.get("input[type=checkbox]").first().click();
++ cy.get("input[type=checkbox]").last().click();
});
++
++ // Copy to separate collections
++ cy.get("[data-cy=collection-files-panel-options-btn]").click();
++ cy.get("[data-cy=context-menu]").contains("Move selected into separate collections").click();
++ cy.get("[data-cy=form-dialog]").contains("Projects").click();
++ cy.get("[data-cy=form-submit-btn]").click();
++
++ // Verify created collections
++ cy.waitForDom().get(".layout-pane-primary", { timeout: 12000 }).contains("Projects").click();
++ cy.get("main").contains(`File moved from collection ${sourceCollection.name}/foo`).click();
++ cy.get("[data-cy=collection-files-panel]").and("contain", "foo");
++ cy.get(".layout-pane-primary").contains("Projects").click();
++ cy.get("main").contains(`File moved from collection ${sourceCollection.name}/bar`).click();
++ cy.get("[data-cy=collection-files-panel]").and("contain", "bar");
++ });
});
- it('creates new collection with properties on home project', function () {
+ it("creates new collection with properties on home project", function () {
cy.loginAs(activeUser);
cy.goToPath(`/projects/${activeUser.user.uuid}`);
- cy.get('[data-cy=breadcrumb-first]').should('contain', 'Projects');
- cy.get('[data-cy=breadcrumb-last]').should('not.exist');
+ cy.get("[data-cy=breadcrumb-first]").should("contain", "Projects");
+ cy.get("[data-cy=breadcrumb-last]").should("not.exist");
// Create new collection
- cy.get('[data-cy=side-panel-button]').click();
- cy.get('[data-cy=side-panel-new-collection]').click();
+ cy.get("[data-cy=side-panel-button]").click();
+ cy.get("[data-cy=side-panel-new-collection]").click();
// Name between brackets tests bugfix #17582
const collName = `[Test collection (${Math.floor(999999 * Math.random())})]`;
//
// SPDX-License-Identifier: AGPL-3.0
-import { ContainerState } from 'models/container';
++import { ContainerState } from "models/container";
+
-describe('Process tests', function() {
+describe("Process tests", function () {
let activeUser;
let adminUser;
});
}
- it("shows process logs", function () {
- const crName = "test_container_request";
- createContainerRequest(activeUser, crName, "arvados/jobs", ["echo", "hello world"], false, "Committed").then(function (containerRequest) {
- cy.loginAs(activeUser);
- cy.goToPath(`/processes/${containerRequest.uuid}`);
- cy.get("[data-cy=process-details]").should("contain", crName);
- cy.get("[data-cy=process-logs]").should("contain", "No logs yet").and("not.contain", "hello world");
- cy.createLog(activeUser.token, {
- object_uuid: containerRequest.container_uuid,
- properties: {
- text: "hello world",
- },
- event_type: "stdout",
- }).then(function (log) {
- cy.get("[data-cy=process-logs]", { timeout: 7000 }).should("not.contain", "No logs yet").and("contain", "hello world");
- describe('Details panel', function() {
- it('shows process details', function() {
++ describe("Details panel", function () {
++ it("shows process details", function () {
+ createContainerRequest(
+ activeUser,
+ `test_container_request ${Math.floor(Math.random() * 999999)}`,
- 'arvados/jobs',
- ['echo', 'hello world'],
- false, 'Committed')
- .then(function(containerRequest) {
++ "arvados/jobs",
++ ["echo", "hello world"],
++ false,
++ "Committed"
++ ).then(function (containerRequest) {
+ cy.loginAs(activeUser);
+ cy.goToPath(`/processes/${containerRequest.uuid}`);
- cy.get('[data-cy=process-details]').should('contain', containerRequest.name);
- cy.get('[data-cy=process-details-attributes-modifiedby-user]').contains(`Active User (${activeUser.user.uuid})`);
- cy.get('[data-cy=process-details-attributes-runtime-user]').should('not.exist');
++ cy.get("[data-cy=process-details]").should("contain", containerRequest.name);
++ cy.get("[data-cy=process-details-attributes-modifiedby-user]").contains(`Active User (${activeUser.user.uuid})`);
++ cy.get("[data-cy=process-details-attributes-runtime-user]").should("not.exist");
+ });
+
+ // Fake submitted by another user
- cy.intercept({method: 'GET', url: '**/arvados/v1/container_requests/*'}, (req) => {
- req.reply((res) => {
- res.body.modified_by_user_uuid = 'zzzzz-tpzed-000000000000000';
++ cy.intercept({ method: "GET", url: "**/arvados/v1/container_requests/*" }, req => {
++ req.reply(res => {
++ res.body.modified_by_user_uuid = "zzzzz-tpzed-000000000000000";
+ });
+ });
+
+ createContainerRequest(
+ activeUser,
+ `test_container_request ${Math.floor(Math.random() * 999999)}`,
- 'arvados/jobs',
- ['echo', 'hello world'],
- false, 'Committed')
- .then(function(containerRequest) {
++ "arvados/jobs",
++ ["echo", "hello world"],
++ false,
++ "Committed"
++ ).then(function (containerRequest) {
+ cy.loginAs(activeUser);
+ cy.goToPath(`/processes/${containerRequest.uuid}`);
- cy.get('[data-cy=process-details]').should('contain', containerRequest.name);
- cy.get('[data-cy=process-details-attributes-modifiedby-user]').contains(`zzzzz-tpzed-000000000000000`);
- cy.get('[data-cy=process-details-attributes-runtime-user]').contains(`Active User (${activeUser.user.uuid})`);
++ cy.get("[data-cy=process-details]").should("contain", containerRequest.name);
++ cy.get("[data-cy=process-details-attributes-modifiedby-user]").contains(`zzzzz-tpzed-000000000000000`);
++ cy.get("[data-cy=process-details-attributes-runtime-user]").contains(`Active User (${activeUser.user.uuid})`);
});
});
- });
- it("shows process details", function () {
- createContainerRequest(
- activeUser,
- `test_container_request ${Math.floor(Math.random() * 999999)}`,
- "arvados/jobs",
- ["echo", "hello world"],
- false,
- "Committed"
- ).then(function (containerRequest) {
- cy.loginAs(activeUser);
- cy.goToPath(`/processes/${containerRequest.uuid}`);
- cy.get("[data-cy=process-details]").should("contain", containerRequest.name);
- cy.get("[data-cy=process-details-attributes-modifiedby-user]").contains(`Active User (${activeUser.user.uuid})`);
- cy.get("[data-cy=process-details-attributes-runtime-user]").should("not.exist");
- it('should show runtime status indicators', function() {
++ it("should show runtime status indicators", function () {
+ // Setup running container with runtime_status error & warning messages
- createContainerRequest(
- activeUser,
- 'test_container_request',
- 'arvados/jobs',
- ['echo', 'hello world'],
- false, 'Committed')
- .as('containerRequest')
- .then(function(containerRequest) {
- expect(containerRequest.state).to.equal('Committed');
- expect(containerRequest.container_uuid).not.to.be.equal('');
-
- cy.getContainer(activeUser.token, containerRequest.container_uuid)
- .then(function(queuedContainer) {
- expect(queuedContainer.state).to.be.equal('Queued');
- });
- cy.updateContainer(adminUser.token, containerRequest.container_uuid, {
- state: 'Locked'
- }).then(function(lockedContainer) {
- expect(lockedContainer.state).to.be.equal('Locked');
-
- cy.updateContainer(adminUser.token, lockedContainer.uuid, {
- state: 'Running',
- runtime_status: {
- error: 'Something went wrong',
- errorDetail: 'Process exited with status 1',
- warning: 'Free disk space is low',
- }
- })
- .as('runningContainer')
- .then(function(runningContainer) {
- expect(runningContainer.state).to.be.equal('Running');
- expect(runningContainer.runtime_status).to.be.deep.equal({
- 'error': 'Something went wrong',
- 'errorDetail': 'Process exited with status 1',
- 'warning': 'Free disk space is low',
- });
++ createContainerRequest(activeUser, "test_container_request", "arvados/jobs", ["echo", "hello world"], false, "Committed")
++ .as("containerRequest")
++ .then(function (containerRequest) {
++ expect(containerRequest.state).to.equal("Committed");
++ expect(containerRequest.container_uuid).not.to.be.equal("");
++
++ cy.getContainer(activeUser.token, containerRequest.container_uuid).then(function (queuedContainer) {
++ expect(queuedContainer.state).to.be.equal("Queued");
+ });
- })
- });
++ cy.updateContainer(adminUser.token, containerRequest.container_uuid, {
++ state: "Locked",
++ }).then(function (lockedContainer) {
++ expect(lockedContainer.state).to.be.equal("Locked");
++
++ cy.updateContainer(adminUser.token, lockedContainer.uuid, {
++ state: "Running",
++ runtime_status: {
++ error: "Something went wrong",
++ errorDetail: "Process exited with status 1",
++ warning: "Free disk space is low",
++ },
++ })
++ .as("runningContainer")
++ .then(function (runningContainer) {
++ expect(runningContainer.state).to.be.equal("Running");
++ expect(runningContainer.runtime_status).to.be.deep.equal({
++ error: "Something went wrong",
++ errorDetail: "Process exited with status 1",
++ warning: "Free disk space is low",
++ });
++ });
++ });
++ });
+ // Test that the UI shows the error and warning messages
- cy.getAll('@containerRequest', '@runningContainer').then(function([containerRequest]) {
++ cy.getAll("@containerRequest", "@runningContainer").then(function ([containerRequest]) {
+ cy.loginAs(activeUser);
+ cy.goToPath(`/processes/${containerRequest.uuid}`);
- cy.get('[data-cy=process-runtime-status-error]')
- .should('contain', 'Something went wrong')
- .and('contain', 'Process exited with status 1');
- cy.get('[data-cy=process-runtime-status-warning]')
- .should('contain', 'Free disk space is low')
- .and('contain', 'No additional warning details available');
++ cy.get("[data-cy=process-runtime-status-error]")
++ .should("contain", "Something went wrong")
++ .and("contain", "Process exited with status 1");
++ cy.get("[data-cy=process-runtime-status-warning]")
++ .should("contain", "Free disk space is low")
++ .and("contain", "No additional warning details available");
+ });
+
-
+ // Force container_count for testing
+ let containerCount = 2;
- cy.intercept({method: 'GET', url: '**/arvados/v1/container_requests/*'}, (req) => {
- req.reply((res) => {
++ cy.intercept({ method: "GET", url: "**/arvados/v1/container_requests/*" }, req => {
++ req.reply(res => {
+ res.body.container_count = containerCount;
+ });
+ });
+
- cy.getAll('@containerRequest').then(function([containerRequest]) {
++ cy.getAll("@containerRequest").then(function ([containerRequest]) {
+ cy.goToPath(`/processes/${containerRequest.uuid}`);
- cy.get('[data-cy=process-runtime-status-retry-warning]', {timeout: 7000})
- .should('contain', 'Process retried 1 time');
++ cy.get("[data-cy=process-runtime-status-retry-warning]", { timeout: 7000 }).should("contain", "Process retried 1 time");
+ });
+
- cy.getAll('@containerRequest').then(function([containerRequest]) {
++ cy.getAll("@containerRequest").then(function ([containerRequest]) {
+ containerCount = 3;
+ cy.goToPath(`/processes/${containerRequest.uuid}`);
- cy.get('[data-cy=process-runtime-status-retry-warning]', {timeout: 7000})
- .should('contain', 'Process retried 2 times');
++ cy.get("[data-cy=process-runtime-status-retry-warning]", { timeout: 7000 }).should("contain", "Process retried 2 times");
+ });
});
- // Fake submitted by another user
- cy.intercept({ method: "GET", url: "**/arvados/v1/container_requests/*" }, req => {
- req.reply(res => {
- res.body.modified_by_user_uuid = "zzzzz-tpzed-000000000000000";
- it('allows copying processes', function() {
- const crName = 'first_container_request';
- const copiedCrName = 'copied_container_request';
- createContainerRequest(
- activeUser,
- crName,
- 'arvados/jobs',
- ['echo', 'hello world'],
- false, 'Committed')
- .then(function(containerRequest) {
++ it("allows copying processes", function () {
++ const crName = "first_container_request";
++ const copiedCrName = "copied_container_request";
++ createContainerRequest(activeUser, crName, "arvados/jobs", ["echo", "hello world"], false, "Committed").then(function (containerRequest) {
+ cy.loginAs(activeUser);
+ cy.goToPath(`/processes/${containerRequest.uuid}`);
- cy.get('[data-cy=process-details]').should('contain', crName);
++ cy.get("[data-cy=process-details]").should("contain", crName);
+
- cy.get('[data-cy=process-details]').find('button[title="More options"]').click();
- cy.get('ul[data-cy=context-menu]').contains("Copy and re-run process").click();
++ cy.get("[data-cy=process-details]").find('button[title="More options"]').click();
++ cy.get("ul[data-cy=context-menu]").contains("Copy and re-run process").click();
+ });
+
- cy.get('[data-cy=form-dialog]').within(() => {
- cy.get('input[name=name]').clear().type(copiedCrName);
- cy.get('[data-cy=projects-tree-home-tree-picker]').click();
- cy.get('[data-cy=form-submit-btn]').click();
++ cy.get("[data-cy=form-dialog]").within(() => {
++ cy.get("input[name=name]").clear().type(copiedCrName);
++ cy.get("[data-cy=projects-tree-home-tree-picker]").click();
++ cy.get("[data-cy=form-submit-btn]").click();
});
- cy.get('[data-cy=process-details]').should('contain', copiedCrName);
- cy.get('[data-cy=process-details]').find('button').contains('Run');
+
++ cy.get("[data-cy=process-details]").should("contain", copiedCrName);
++ cy.get("[data-cy=process-details]").find("button").contains("Run");
});
- createContainerRequest(
- activeUser,
- `test_container_request ${Math.floor(Math.random() * 999999)}`,
- "arvados/jobs",
- ["echo", "hello world"],
- false,
- "Committed"
- ).then(function (containerRequest) {
- cy.loginAs(activeUser);
- cy.goToPath(`/processes/${containerRequest.uuid}`);
- cy.get("[data-cy=process-details]").should("contain", containerRequest.name);
- cy.get("[data-cy=process-details-attributes-modifiedby-user]").contains(`zzzzz-tpzed-000000000000000`);
- cy.get("[data-cy=process-details-attributes-runtime-user]").contains(`Active User (${activeUser.user.uuid})`);
- const getFakeContainer = (fakeContainerUuid) => ({
++ const getFakeContainer = fakeContainerUuid => ({
+ href: `/containers/${fakeContainerUuid}`,
+ kind: "arvados#container",
+ etag: "ecfosljpnxfari9a8m7e4yv06",
+ uuid: fakeContainerUuid,
+ owner_uuid: "zzzzz-tpzed-000000000000000",
+ created_at: "2023-02-13T15:55:47.308915000Z",
+ modified_by_client_uuid: "zzzzz-ozdt8-q6dzdi1lcc03155",
+ modified_by_user_uuid: "zzzzz-tpzed-000000000000000",
+ modified_at: "2023-02-15T19:12:45.987086000Z",
+ command: [
- "arvados-cwl-runner",
- "--api=containers",
- "--local",
- "--project-uuid=zzzzz-j7d0g-yr18k784zplfeza",
- "/var/lib/cwl/workflow.json#main",
- "/var/lib/cwl/cwl.input.json",
++ "arvados-cwl-runner",
++ "--api=containers",
++ "--local",
++ "--project-uuid=zzzzz-j7d0g-yr18k784zplfeza",
++ "/var/lib/cwl/workflow.json#main",
++ "/var/lib/cwl/cwl.input.json",
+ ],
+ container_image: "4ad7d11381df349e464694762db14e04+303",
+ cwd: "/var/spool/cwl",
+ environment: {},
+ exit_code: null,
+ finished_at: null,
+ locked_by_uuid: null,
+ log: null,
+ output: null,
+ output_path: "/var/spool/cwl",
+ progress: null,
+ runtime_constraints: {
- API: true,
- cuda: {
- device_count: 0,
- driver_version: "",
- hardware_capability: "",
- },
- keep_cache_disk: 2147483648,
- keep_cache_ram: 0,
- ram: 1342177280,
- vcpus: 1,
++ API: true,
++ cuda: {
++ device_count: 0,
++ driver_version: "",
++ hardware_capability: "",
++ },
++ keep_cache_disk: 2147483648,
++ keep_cache_ram: 0,
++ ram: 1342177280,
++ vcpus: 1,
+ },
+ runtime_status: {},
+ started_at: null,
+ auth_uuid: null,
+ scheduling_parameters: {
- max_run_time: 0,
- partitions: [],
- preemptible: false,
++ max_run_time: 0,
++ partitions: [],
++ preemptible: false,
+ },
+ runtime_user_uuid: "zzzzz-tpzed-vllbpebicy84rd5",
+ runtime_auth_scopes: ["all"],
+ lock_count: 2,
+ gateway_address: null,
+ interactive_session_started: false,
+ output_storage_classes: ["default"],
+ output_properties: {},
+ cost: 0.0,
+ subrequests_cost: 0.0,
});
- });
- it("filters process logs by event type", function () {
- const nodeInfoLogs = [
- "Host Information",
- "Linux compute-99cb150b26149780de44b929577e1aed-19rgca8vobuvc4p 5.4.0-1059-azure #62~18.04.1-Ubuntu SMP Tue Sep 14 17:53:18 UTC 2021 x86_64 x86_64 x86_64 GNU/Linux",
- "CPU Information",
- "processor : 0",
- "vendor_id : GenuineIntel",
- "cpu family : 6",
- "model : 79",
- "model name : Intel(R) Xeon(R) CPU E5-2673 v4 @ 2.30GHz",
- ];
- const crunchRunLogs = [
- "2022-03-22T13:56:22.542417997Z using local keepstore process (pid 3733) at http://localhost:46837, writing logs to keepstore.txt in log collection",
- "2022-03-22T13:56:26.237571754Z crunch-run 2.4.0~dev20220321141729 (go1.17.1) started",
- "2022-03-22T13:56:26.244704134Z crunch-run process has uid=0(root) gid=0(root) groups=0(root)",
- "2022-03-22T13:56:26.244862836Z Executing container 'zzzzz-dz642-1wokwvcct9s9du3' using docker runtime",
- "2022-03-22T13:56:26.245037738Z Executing on host 'compute-99cb150b26149780de44b929577e1aed-19rgca8vobuvc4p'",
- ];
- const stdoutLogs = [
- "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec dui nisi, hendrerit porta sapien a, pretium dignissim purus.",
- "Integer viverra, mauris finibus aliquet ultricies, dui mauris cursus justo, ut venenatis nibh ex eget neque.",
- "In hac habitasse platea dictumst.",
- "Fusce fringilla turpis id accumsan faucibus. Donec congue congue ex non posuere. In semper mi quis tristique rhoncus.",
- "Interdum et malesuada fames ac ante ipsum primis in faucibus.",
- "Quisque fermentum tortor ex, ut suscipit velit feugiat faucibus.",
- "Donec vitae porta risus, at luctus nulla. Mauris gravida iaculis ipsum, id sagittis tortor egestas ac.",
- "Maecenas condimentum volutpat nulla. Integer lacinia maximus risus eu posuere.",
- "Donec vitae leo id augue gravida bibendum.",
- "Nam libero libero, pretium ac faucibus elementum, mattis nec ex.",
- "Nullam id laoreet nibh. Vivamus tellus metus, pretium quis justo ut, bibendum varius metus. Pellentesque vitae accumsan lorem, quis tincidunt augue.",
- "Aliquam viverra nisi nulla, et efficitur dolor mattis in.",
- "Sed at enim sit amet nulla tincidunt mattis. Aenean eget aliquet ex, non ultrices ex. Nulla ex tortor, vestibulum aliquam tempor ac, aliquam vel est.",
- "Fusce auctor faucibus libero id venenatis. Etiam sodales, odio eu cursus efficitur, quam sem blandit ex, quis porttitor enim dui quis lectus. In id tincidunt felis.",
- "Phasellus non ex quis arcu tempus faucibus molestie in sapien.",
- "Duis tristique semper dolor, vitae pulvinar risus.",
- "Aliquam tortor elit, luctus nec tortor eget, porta tristique nulla.",
- "Nulla eget mollis ipsum.",
- ];
- it('shows cancel button when appropriate', function() {
++ it("shows cancel button when appropriate", function () {
+ // Ignore collection requests
- cy.intercept({method: 'GET', url: `**/arvados/v1/collections/*`}, {
- statusCode: 200,
- body: {}
- });
++ cy.intercept(
++ { method: "GET", url: `**/arvados/v1/collections/*` },
++ {
++ statusCode: 200,
++ body: {},
++ }
++ );
- createContainerRequest(activeUser, "test_container_request", "arvados/jobs", ["echo", "hello world"], false, "Committed").then(function (
- containerRequest
- ) {
- cy.logsForContainer(activeUser.token, containerRequest.container_uuid, "node-info", nodeInfoLogs).as("nodeInfoLogs");
- cy.logsForContainer(activeUser.token, containerRequest.container_uuid, "crunch-run", crunchRunLogs).as("crunchRunLogs");
- cy.logsForContainer(activeUser.token, containerRequest.container_uuid, "stdout", stdoutLogs).as("stdoutLogs");
- cy.getAll("@stdoutLogs", "@nodeInfoLogs", "@crunchRunLogs").then(function () {
- cy.loginAs(activeUser);
+ // Uncommitted container
+ const crUncommitted = `Test process ${Math.floor(Math.random() * 999999)}`;
- createContainerRequest(
- activeUser,
- crUncommitted,
- 'arvados/jobs',
- ['echo', 'hello world'],
- false, 'Uncommitted')
- .then(function(containerRequest) {
++ createContainerRequest(activeUser, crUncommitted, "arvados/jobs", ["echo", "hello world"], false, "Uncommitted").then(function (
++ containerRequest
++ ) {
+ // Navigate to process and verify run / cancel button
cy.goToPath(`/processes/${containerRequest.uuid}`);
- // Should show main logs by default
- cy.get("[data-cy=process-logs-filter]", { timeout: 7000 }).should("contain", "Main logs");
- cy.get("[data-cy=process-logs]")
- .should("contain", stdoutLogs[Math.floor(Math.random() * stdoutLogs.length)])
- .and("not.contain", nodeInfoLogs[Math.floor(Math.random() * nodeInfoLogs.length)])
- .and("contain", crunchRunLogs[Math.floor(Math.random() * crunchRunLogs.length)]);
- // Select 'All logs'
- cy.get("[data-cy=process-logs-filter]").click();
- cy.get("body").contains("li", "All logs").click();
- cy.get("[data-cy=process-logs]")
- .should("contain", stdoutLogs[Math.floor(Math.random() * stdoutLogs.length)])
- .and("contain", nodeInfoLogs[Math.floor(Math.random() * nodeInfoLogs.length)])
- .and("contain", crunchRunLogs[Math.floor(Math.random() * crunchRunLogs.length)]);
- // Select 'node-info' logs
- cy.get("[data-cy=process-logs-filter]").click();
- cy.get("body").contains("li", "node-info").click();
- cy.get("[data-cy=process-logs]")
- .should("not.contain", stdoutLogs[Math.floor(Math.random() * stdoutLogs.length)])
- .and("contain", nodeInfoLogs[Math.floor(Math.random() * nodeInfoLogs.length)])
- .and("not.contain", crunchRunLogs[Math.floor(Math.random() * crunchRunLogs.length)]);
- // Select 'stdout' logs
- cy.get("[data-cy=process-logs-filter]").click();
- cy.get("body").contains("li", "stdout").click();
- cy.get("[data-cy=process-logs]")
- .should("contain", stdoutLogs[Math.floor(Math.random() * stdoutLogs.length)])
- .and("not.contain", nodeInfoLogs[Math.floor(Math.random() * nodeInfoLogs.length)])
- .and("not.contain", crunchRunLogs[Math.floor(Math.random() * crunchRunLogs.length)]);
+ cy.waitForDom();
- cy.get('[data-cy=process-details]').should('contain', crUncommitted);
- cy.get('[data-cy=process-run-button]').should('exist');
- cy.get('[data-cy=process-cancel-button]').should('not.exist');
++ cy.get("[data-cy=process-details]").should("contain", crUncommitted);
++ cy.get("[data-cy=process-run-button]").should("exist");
++ cy.get("[data-cy=process-cancel-button]").should("not.exist");
+ });
+
+ // Queued container
+ const crQueued = `Test process ${Math.floor(Math.random() * 999999)}`;
- const fakeCrUuid = 'zzzzz-dz642-000000000000001';
- createContainerRequest(
- activeUser,
- crQueued,
- 'arvados/jobs',
- ['echo', 'hello world'],
- false, 'Committed')
- .then(function(containerRequest) {
++ const fakeCrUuid = "zzzzz-dz642-000000000000001";
++ createContainerRequest(activeUser, crQueued, "arvados/jobs", ["echo", "hello world"], false, "Committed").then(function (
++ containerRequest
++ ) {
+ // Fake container uuid
- cy.intercept({method: 'GET', url: `**/arvados/v1/container_requests/${containerRequest.uuid}`}, (req) => {
- req.reply((res) => {
++ cy.intercept({ method: "GET", url: `**/arvados/v1/container_requests/${containerRequest.uuid}` }, req => {
++ req.reply(res => {
+ res.body.output_uuid = fakeCrUuid;
+ res.body.priority = 500;
+ res.body.state = "Committed";
+ });
+ });
+
+ // Fake container
+ const container = getFakeContainer(fakeCrUuid);
- cy.intercept({method: 'GET', url: `**/arvados/v1/container/${fakeCrUuid}`}, {
- statusCode: 200,
- body: {...container, state: "Queued", priority: 500}
- });
++ cy.intercept(
++ { method: "GET", url: `**/arvados/v1/container/${fakeCrUuid}` },
++ {
++ statusCode: 200,
++ body: { ...container, state: "Queued", priority: 500 },
++ }
++ );
+
+ // Navigate to process and verify cancel button
+ cy.goToPath(`/processes/${containerRequest.uuid}`);
+ cy.waitForDom();
- cy.get('[data-cy=process-details]').should('contain', crQueued);
- cy.get('[data-cy=process-cancel-button]').contains('Cancel');
++ cy.get("[data-cy=process-details]").should("contain", crQueued);
++ cy.get("[data-cy=process-cancel-button]").contains("Cancel");
+ });
+
+ // Locked container
+ const crLocked = `Test process ${Math.floor(Math.random() * 999999)}`;
- const fakeCrLockedUuid = 'zzzzz-dz642-000000000000002';
- createContainerRequest(
- activeUser,
- crLocked,
- 'arvados/jobs',
- ['echo', 'hello world'],
- false, 'Committed')
- .then(function(containerRequest) {
++ const fakeCrLockedUuid = "zzzzz-dz642-000000000000002";
++ createContainerRequest(activeUser, crLocked, "arvados/jobs", ["echo", "hello world"], false, "Committed").then(function (
++ containerRequest
++ ) {
+ // Fake container uuid
- cy.intercept({method: 'GET', url: `**/arvados/v1/container_requests/${containerRequest.uuid}`}, (req) => {
- req.reply((res) => {
++ cy.intercept({ method: "GET", url: `**/arvados/v1/container_requests/${containerRequest.uuid}` }, req => {
++ req.reply(res => {
+ res.body.output_uuid = fakeCrLockedUuid;
+ res.body.priority = 500;
+ res.body.state = "Committed";
+ });
+ });
+
+ // Fake container
+ const container = getFakeContainer(fakeCrLockedUuid);
- cy.intercept({method: 'GET', url: `**/arvados/v1/container/${fakeCrLockedUuid}`}, {
- statusCode: 200,
- body: {...container, state: "Locked", priority: 500}
- });
++ cy.intercept(
++ { method: "GET", url: `**/arvados/v1/container/${fakeCrLockedUuid}` },
++ {
++ statusCode: 200,
++ body: { ...container, state: "Locked", priority: 500 },
++ }
++ );
+
+ // Navigate to process and verify cancel button
+ cy.goToPath(`/processes/${containerRequest.uuid}`);
+ cy.waitForDom();
- cy.get('[data-cy=process-details]').should('contain', crLocked);
- cy.get('[data-cy=process-cancel-button]').contains('Cancel');
++ cy.get("[data-cy=process-details]").should("contain", crLocked);
++ cy.get("[data-cy=process-cancel-button]").contains("Cancel");
+ });
+
+ // On Hold container
+ const crOnHold = `Test process ${Math.floor(Math.random() * 999999)}`;
- const fakeCrOnHoldUuid = 'zzzzz-dz642-000000000000003';
- createContainerRequest(
- activeUser,
- crOnHold,
- 'arvados/jobs',
- ['echo', 'hello world'],
- false, 'Committed')
- .then(function(containerRequest) {
++ const fakeCrOnHoldUuid = "zzzzz-dz642-000000000000003";
++ createContainerRequest(activeUser, crOnHold, "arvados/jobs", ["echo", "hello world"], false, "Committed").then(function (
++ containerRequest
++ ) {
+ // Fake container uuid
- cy.intercept({method: 'GET', url: `**/arvados/v1/container_requests/${containerRequest.uuid}`}, (req) => {
- req.reply((res) => {
++ cy.intercept({ method: "GET", url: `**/arvados/v1/container_requests/${containerRequest.uuid}` }, req => {
++ req.reply(res => {
+ res.body.output_uuid = fakeCrOnHoldUuid;
+ res.body.priority = 0;
+ res.body.state = "Committed";
+ });
+ });
+
+ // Fake container
+ const container = getFakeContainer(fakeCrOnHoldUuid);
- cy.intercept({method: 'GET', url: `**/arvados/v1/container/${fakeCrOnHoldUuid}`}, {
- statusCode: 200,
- body: {...container, state: "Queued", priority: 0}
- });
++ cy.intercept(
++ { method: "GET", url: `**/arvados/v1/container/${fakeCrOnHoldUuid}` },
++ {
++ statusCode: 200,
++ body: { ...container, state: "Queued", priority: 0 },
++ }
++ );
+
+ // Navigate to process and verify cancel button
+ cy.goToPath(`/processes/${containerRequest.uuid}`);
+ cy.waitForDom();
- cy.get('[data-cy=process-details]').should('contain', crOnHold);
- cy.get('[data-cy=process-run-button]').should('exist');
- cy.get('[data-cy=process-cancel-button]').should('not.exist');
++ cy.get("[data-cy=process-details]").should("contain", crOnHold);
++ cy.get("[data-cy=process-run-button]").should("exist");
++ cy.get("[data-cy=process-cancel-button]").should("not.exist");
});
});
-
});
- it("should show runtime status indicators", function () {
- // Setup running container with runtime_status error & warning messages
- createContainerRequest(activeUser, "test_container_request", "arvados/jobs", ["echo", "hello world"], false, "Committed")
- .as("containerRequest")
- .then(function (containerRequest) {
- expect(containerRequest.state).to.equal("Committed");
- expect(containerRequest.container_uuid).not.to.be.equal("");
-
- describe('Logs panel', function() {
- it('shows live process logs', function() {
- cy.intercept({method: 'GET', url: '**/arvados/v1/containers/*'}, (req) => {
- req.reply((res) => {
++ describe("Logs panel", function () {
++ it("shows live process logs", function () {
++ cy.intercept({ method: "GET", url: "**/arvados/v1/containers/*" }, req => {
++ req.reply(res => {
+ res.body.state = ContainerState.RUNNING;
+ });
+ });
+
- const crName = 'test_container_request';
- createContainerRequest(
- activeUser,
- crName,
- 'arvados/jobs',
- ['echo', 'hello world'],
- false, 'Committed')
- .then(function(containerRequest) {
++ const crName = "test_container_request";
++ createContainerRequest(activeUser, crName, "arvados/jobs", ["echo", "hello world"], false, "Committed").then(function (containerRequest) {
+ // Create empty log file before loading process page
- cy.appendLog(adminUser.token, containerRequest.uuid, "stdout.txt", [
- ""
- ])
++ cy.appendLog(adminUser.token, containerRequest.uuid, "stdout.txt", [""]);
- cy.getContainer(activeUser.token, containerRequest.container_uuid).then(function (queuedContainer) {
- expect(queuedContainer.state).to.be.equal("Queued");
+ cy.loginAs(activeUser);
+ cy.goToPath(`/processes/${containerRequest.uuid}`);
- cy.get('[data-cy=process-details]').should('contain', crName);
- cy.get('[data-cy=process-logs]')
- .should('contain', 'No logs yet')
- .and('not.contain', 'hello world');
++ cy.get("[data-cy=process-details]").should("contain", crName);
++ cy.get("[data-cy=process-logs]").should("contain", "No logs yet").and("not.contain", "hello world");
+
+ // Append a log line
- cy.appendLog(adminUser.token, containerRequest.uuid, "stdout.txt", [
- "2023-07-18T20:14:48.128642814Z hello world"
- ]).then(() => {
- cy.get('[data-cy=process-logs]', {timeout: 7000})
- .should('not.contain', 'No logs yet')
- .and('contain', 'hello world');
++ cy.appendLog(adminUser.token, containerRequest.uuid, "stdout.txt", ["2023-07-18T20:14:48.128642814Z hello world"]).then(() => {
++ cy.get("[data-cy=process-logs]", { timeout: 7000 }).should("not.contain", "No logs yet").and("contain", "hello world");
});
- cy.updateContainer(adminUser.token, containerRequest.container_uuid, {
- state: "Locked",
- }).then(function (lockedContainer) {
- expect(lockedContainer.state).to.be.equal("Locked");
-
- cy.updateContainer(adminUser.token, lockedContainer.uuid, {
- state: "Running",
- runtime_status: {
- error: "Something went wrong",
- errorDetail: "Process exited with status 1",
- warning: "Free disk space is low",
- },
- })
- .as("runningContainer")
- .then(function (runningContainer) {
- expect(runningContainer.state).to.be.equal("Running");
- expect(runningContainer.runtime_status).to.be.deep.equal({
- error: "Something went wrong",
- errorDetail: "Process exited with status 1",
- warning: "Free disk space is low",
- });
- });
+
+ // Append new log line to different file
- cy.appendLog(adminUser.token, containerRequest.uuid, "stderr.txt", [
- "2023-07-18T20:14:49.128642814Z hello new line"
- ]).then(() => {
- cy.get('[data-cy=process-logs]', {timeout: 7000})
- .should('not.contain', 'No logs yet')
- .and('contain', 'hello new line');
++ cy.appendLog(adminUser.token, containerRequest.uuid, "stderr.txt", ["2023-07-18T20:14:49.128642814Z hello new line"]).then(() => {
++ cy.get("[data-cy=process-logs]", { timeout: 7000 }).should("not.contain", "No logs yet").and("contain", "hello new line");
});
});
- // Test that the UI shows the error and warning messages
- cy.getAll("@containerRequest", "@runningContainer").then(function ([containerRequest]) {
- cy.loginAs(activeUser);
- cy.goToPath(`/processes/${containerRequest.uuid}`);
- cy.get("[data-cy=process-runtime-status-error]").should("contain", "Something went wrong").and("contain", "Process exited with status 1");
- cy.get("[data-cy=process-runtime-status-warning]")
- .should("contain", "Free disk space is low")
- .and("contain", "No additional warning details available");
});
- // Force container_count for testing
- let containerCount = 2;
- cy.intercept({ method: "GET", url: "**/arvados/v1/container_requests/*" }, req => {
- req.reply(res => {
- res.body.container_count = containerCount;
- it('filters process logs by event type', function() {
++ it("filters process logs by event type", function () {
+ const nodeInfoLogs = [
- 'Host Information',
- 'Linux compute-99cb150b26149780de44b929577e1aed-19rgca8vobuvc4p 5.4.0-1059-azure #62~18.04.1-Ubuntu SMP Tue Sep 14 17:53:18 UTC 2021 x86_64 x86_64 x86_64 GNU/Linux',
- 'CPU Information',
- 'processor : 0',
- 'vendor_id : GenuineIntel',
- 'cpu family : 6',
- 'model : 79',
- 'model name : Intel(R) Xeon(R) CPU E5-2673 v4 @ 2.30GHz'
++ "Host Information",
++ "Linux compute-99cb150b26149780de44b929577e1aed-19rgca8vobuvc4p 5.4.0-1059-azure #62~18.04.1-Ubuntu SMP Tue Sep 14 17:53:18 UTC 2021 x86_64 x86_64 x86_64 GNU/Linux",
++ "CPU Information",
++ "processor : 0",
++ "vendor_id : GenuineIntel",
++ "cpu family : 6",
++ "model : 79",
++ "model name : Intel(R) Xeon(R) CPU E5-2673 v4 @ 2.30GHz",
+ ];
+ const crunchRunLogs = [
- '2022-03-22T13:56:22.542417997Z using local keepstore process (pid 3733) at http://localhost:46837, writing logs to keepstore.txt in log collection',
- '2022-03-22T13:56:26.237571754Z crunch-run 2.4.0~dev20220321141729 (go1.17.1) started',
- '2022-03-22T13:56:26.244704134Z crunch-run process has uid=0(root) gid=0(root) groups=0(root)',
- '2022-03-22T13:56:26.244862836Z Executing container \'zzzzz-dz642-1wokwvcct9s9du3\' using docker runtime',
- '2022-03-22T13:56:26.245037738Z Executing on host \'compute-99cb150b26149780de44b929577e1aed-19rgca8vobuvc4p\'',
++ "2022-03-22T13:56:22.542417997Z using local keepstore process (pid 3733) at http://localhost:46837, writing logs to keepstore.txt in log collection",
++ "2022-03-22T13:56:26.237571754Z crunch-run 2.4.0~dev20220321141729 (go1.17.1) started",
++ "2022-03-22T13:56:26.244704134Z crunch-run process has uid=0(root) gid=0(root) groups=0(root)",
++ "2022-03-22T13:56:26.244862836Z Executing container 'zzzzz-dz642-1wokwvcct9s9du3' using docker runtime",
++ "2022-03-22T13:56:26.245037738Z Executing on host 'compute-99cb150b26149780de44b929577e1aed-19rgca8vobuvc4p'",
+ ];
+ const stdoutLogs = [
- '2022-03-22T13:56:22.542417987Z Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec dui nisi, hendrerit porta sapien a, pretium dignissim purus.',
- '2022-03-22T13:56:22.542417997Z Integer viverra, mauris finibus aliquet ultricies, dui mauris cursus justo, ut venenatis nibh ex eget neque.',
- '2022-03-22T13:56:22.542418007Z In hac habitasse platea dictumst.',
- '2022-03-22T13:56:22.542418027Z Fusce fringilla turpis id accumsan faucibus. Donec congue congue ex non posuere. In semper mi quis tristique rhoncus.',
- '2022-03-22T13:56:22.542418037Z Interdum et malesuada fames ac ante ipsum primis in faucibus.',
- '2022-03-22T13:56:22.542418047Z Quisque fermentum tortor ex, ut suscipit velit feugiat faucibus.',
- '2022-03-22T13:56:22.542418057Z Donec vitae porta risus, at luctus nulla. Mauris gravida iaculis ipsum, id sagittis tortor egestas ac.',
- '2022-03-22T13:56:22.542418067Z Maecenas condimentum volutpat nulla. Integer lacinia maximus risus eu posuere.',
- '2022-03-22T13:56:22.542418077Z Donec vitae leo id augue gravida bibendum.',
- '2022-03-22T13:56:22.542418087Z Nam libero libero, pretium ac faucibus elementum, mattis nec ex.',
- '2022-03-22T13:56:22.542418097Z Nullam id laoreet nibh. Vivamus tellus metus, pretium quis justo ut, bibendum varius metus. Pellentesque vitae accumsan lorem, quis tincidunt augue.',
- '2022-03-22T13:56:22.542418107Z Aliquam viverra nisi nulla, et efficitur dolor mattis in.',
- '2022-03-22T13:56:22.542418117Z Sed at enim sit amet nulla tincidunt mattis. Aenean eget aliquet ex, non ultrices ex. Nulla ex tortor, vestibulum aliquam tempor ac, aliquam vel est.',
- '2022-03-22T13:56:22.542418127Z Fusce auctor faucibus libero id venenatis. Etiam sodales, odio eu cursus efficitur, quam sem blandit ex, quis porttitor enim dui quis lectus. In id tincidunt felis.',
- '2022-03-22T13:56:22.542418137Z Phasellus non ex quis arcu tempus faucibus molestie in sapien.',
- '2022-03-22T13:56:22.542418147Z Duis tristique semper dolor, vitae pulvinar risus.',
- '2022-03-22T13:56:22.542418157Z Aliquam tortor elit, luctus nec tortor eget, porta tristique nulla.',
- '2022-03-22T13:56:22.542418167Z Nulla eget mollis ipsum.',
++ "2022-03-22T13:56:22.542417987Z Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec dui nisi, hendrerit porta sapien a, pretium dignissim purus.",
++ "2022-03-22T13:56:22.542417997Z Integer viverra, mauris finibus aliquet ultricies, dui mauris cursus justo, ut venenatis nibh ex eget neque.",
++ "2022-03-22T13:56:22.542418007Z In hac habitasse platea dictumst.",
++ "2022-03-22T13:56:22.542418027Z Fusce fringilla turpis id accumsan faucibus. Donec congue congue ex non posuere. In semper mi quis tristique rhoncus.",
++ "2022-03-22T13:56:22.542418037Z Interdum et malesuada fames ac ante ipsum primis in faucibus.",
++ "2022-03-22T13:56:22.542418047Z Quisque fermentum tortor ex, ut suscipit velit feugiat faucibus.",
++ "2022-03-22T13:56:22.542418057Z Donec vitae porta risus, at luctus nulla. Mauris gravida iaculis ipsum, id sagittis tortor egestas ac.",
++ "2022-03-22T13:56:22.542418067Z Maecenas condimentum volutpat nulla. Integer lacinia maximus risus eu posuere.",
++ "2022-03-22T13:56:22.542418077Z Donec vitae leo id augue gravida bibendum.",
++ "2022-03-22T13:56:22.542418087Z Nam libero libero, pretium ac faucibus elementum, mattis nec ex.",
++ "2022-03-22T13:56:22.542418097Z Nullam id laoreet nibh. Vivamus tellus metus, pretium quis justo ut, bibendum varius metus. Pellentesque vitae accumsan lorem, quis tincidunt augue.",
++ "2022-03-22T13:56:22.542418107Z Aliquam viverra nisi nulla, et efficitur dolor mattis in.",
++ "2022-03-22T13:56:22.542418117Z Sed at enim sit amet nulla tincidunt mattis. Aenean eget aliquet ex, non ultrices ex. Nulla ex tortor, vestibulum aliquam tempor ac, aliquam vel est.",
++ "2022-03-22T13:56:22.542418127Z Fusce auctor faucibus libero id venenatis. Etiam sodales, odio eu cursus efficitur, quam sem blandit ex, quis porttitor enim dui quis lectus. In id tincidunt felis.",
++ "2022-03-22T13:56:22.542418137Z Phasellus non ex quis arcu tempus faucibus molestie in sapien.",
++ "2022-03-22T13:56:22.542418147Z Duis tristique semper dolor, vitae pulvinar risus.",
++ "2022-03-22T13:56:22.542418157Z Aliquam tortor elit, luctus nec tortor eget, porta tristique nulla.",
++ "2022-03-22T13:56:22.542418167Z Nulla eget mollis ipsum.",
+ ];
+
- createContainerRequest(
- activeUser,
- 'test_container_request',
- 'arvados/jobs',
- ['echo', 'hello world'],
- false, 'Committed')
- .then(function(containerRequest) {
- cy.appendLog(adminUser.token, containerRequest.uuid, "node-info.txt", nodeInfoLogs).as('nodeInfoLogs');
- cy.appendLog(adminUser.token, containerRequest.uuid, "crunch-run.txt", crunchRunLogs).as('crunchRunLogs');
- cy.appendLog(adminUser.token, containerRequest.uuid, "stdout.txt", stdoutLogs).as('stdoutLogs');
-
- cy.getAll('@stdoutLogs', '@nodeInfoLogs', '@crunchRunLogs').then(function() {
++ createContainerRequest(activeUser, "test_container_request", "arvados/jobs", ["echo", "hello world"], false, "Committed").then(function (
++ containerRequest
++ ) {
++ cy.appendLog(adminUser.token, containerRequest.uuid, "node-info.txt", nodeInfoLogs).as("nodeInfoLogs");
++ cy.appendLog(adminUser.token, containerRequest.uuid, "crunch-run.txt", crunchRunLogs).as("crunchRunLogs");
++ cy.appendLog(adminUser.token, containerRequest.uuid, "stdout.txt", stdoutLogs).as("stdoutLogs");
++
++ cy.getAll("@stdoutLogs", "@nodeInfoLogs", "@crunchRunLogs").then(function () {
+ cy.loginAs(activeUser);
+ cy.goToPath(`/processes/${containerRequest.uuid}`);
+ // Should show main logs by default
- cy.get('[data-cy=process-logs-filter]', {timeout: 7000}).should('contain', 'Main logs');
- cy.get('[data-cy=process-logs]')
- .should('contain', stdoutLogs[Math.floor(Math.random() * stdoutLogs.length)])
- .and('not.contain', nodeInfoLogs[Math.floor(Math.random() * nodeInfoLogs.length)])
- .and('contain', crunchRunLogs[Math.floor(Math.random() * crunchRunLogs.length)]);
++ cy.get("[data-cy=process-logs-filter]", { timeout: 7000 }).should("contain", "Main logs");
++ cy.get("[data-cy=process-logs]")
++ .should("contain", stdoutLogs[Math.floor(Math.random() * stdoutLogs.length)])
++ .and("not.contain", nodeInfoLogs[Math.floor(Math.random() * nodeInfoLogs.length)])
++ .and("contain", crunchRunLogs[Math.floor(Math.random() * crunchRunLogs.length)]);
+ // Select 'All logs'
- cy.get('[data-cy=process-logs-filter]').click();
- cy.get('body').contains('li', 'All logs').click();
- cy.get('[data-cy=process-logs]')
- .should('contain', stdoutLogs[Math.floor(Math.random() * stdoutLogs.length)])
- .and('contain', nodeInfoLogs[Math.floor(Math.random() * nodeInfoLogs.length)])
- .and('contain', crunchRunLogs[Math.floor(Math.random() * crunchRunLogs.length)]);
++ cy.get("[data-cy=process-logs-filter]").click();
++ cy.get("body").contains("li", "All logs").click();
++ cy.get("[data-cy=process-logs]")
++ .should("contain", stdoutLogs[Math.floor(Math.random() * stdoutLogs.length)])
++ .and("contain", nodeInfoLogs[Math.floor(Math.random() * nodeInfoLogs.length)])
++ .and("contain", crunchRunLogs[Math.floor(Math.random() * crunchRunLogs.length)]);
+ // Select 'node-info' logs
- cy.get('[data-cy=process-logs-filter]').click();
- cy.get('body').contains('li', 'node-info').click();
- cy.get('[data-cy=process-logs]')
- .should('not.contain', stdoutLogs[Math.floor(Math.random() * stdoutLogs.length)])
- .and('contain', nodeInfoLogs[Math.floor(Math.random() * nodeInfoLogs.length)])
- .and('not.contain', crunchRunLogs[Math.floor(Math.random() * crunchRunLogs.length)]);
++ cy.get("[data-cy=process-logs-filter]").click();
++ cy.get("body").contains("li", "node-info").click();
++ cy.get("[data-cy=process-logs]")
++ .should("not.contain", stdoutLogs[Math.floor(Math.random() * stdoutLogs.length)])
++ .and("contain", nodeInfoLogs[Math.floor(Math.random() * nodeInfoLogs.length)])
++ .and("not.contain", crunchRunLogs[Math.floor(Math.random() * crunchRunLogs.length)]);
+ // Select 'stdout' logs
- cy.get('[data-cy=process-logs-filter]').click();
- cy.get('body').contains('li', 'stdout').click();
- cy.get('[data-cy=process-logs]')
- .should('contain', stdoutLogs[Math.floor(Math.random() * stdoutLogs.length)])
- .and('not.contain', nodeInfoLogs[Math.floor(Math.random() * nodeInfoLogs.length)])
- .and('not.contain', crunchRunLogs[Math.floor(Math.random() * crunchRunLogs.length)]);
++ cy.get("[data-cy=process-logs-filter]").click();
++ cy.get("body").contains("li", "stdout").click();
++ cy.get("[data-cy=process-logs]")
++ .should("contain", stdoutLogs[Math.floor(Math.random() * stdoutLogs.length)])
++ .and("not.contain", nodeInfoLogs[Math.floor(Math.random() * nodeInfoLogs.length)])
++ .and("not.contain", crunchRunLogs[Math.floor(Math.random() * crunchRunLogs.length)]);
+ });
});
});
- cy.getAll("@containerRequest").then(function ([containerRequest]) {
- cy.goToPath(`/processes/${containerRequest.uuid}`);
- cy.get("[data-cy=process-runtime-status-retry-warning]", { timeout: 7000 }).should("contain", "Process retried 1 time");
- it('sorts combined logs', function() {
- const crName = 'test_container_request';
- createContainerRequest(
- activeUser,
- crName,
- 'arvados/jobs',
- ['echo', 'hello world'],
- false, 'Committed')
- .then(function(containerRequest) {
++ it("sorts combined logs", function () {
++ const crName = "test_container_request";
++ createContainerRequest(activeUser, crName, "arvados/jobs", ["echo", "hello world"], false, "Committed").then(function (containerRequest) {
+ cy.appendLog(adminUser.token, containerRequest.uuid, "node-info.txt", [
+ "3: nodeinfo 1",
+ "2: nodeinfo 2",
+ "1: nodeinfo 3",
+ "2: nodeinfo 4",
+ "3: nodeinfo 5",
- ]).as('node-info');
++ ]).as("node-info");
+
+ cy.appendLog(adminUser.token, containerRequest.uuid, "stdout.txt", [
+ "2023-07-18T20:14:48.128642814Z first",
- "2023-07-18T20:14:49.128642814Z third"
- ]).as('stdout');
++ "2023-07-18T20:14:49.128642814Z third",
++ ]).as("stdout");
+
- cy.appendLog(adminUser.token, containerRequest.uuid, "stderr.txt", [
- "2023-07-18T20:14:48.528642814Z second"
- ]).as('stderr');
++ cy.appendLog(adminUser.token, containerRequest.uuid, "stderr.txt", ["2023-07-18T20:14:48.528642814Z second"]).as("stderr");
+
+ cy.loginAs(activeUser);
+ cy.goToPath(`/processes/${containerRequest.uuid}`);
- cy.get('[data-cy=process-details]').should('contain', crName);
- cy.get('[data-cy=process-logs]')
- .should('contain', 'No logs yet');
++ cy.get("[data-cy=process-details]").should("contain", crName);
++ cy.get("[data-cy=process-logs]").should("contain", "No logs yet");
+
- cy.getAll('@node-info', '@stdout', '@stderr').then(() => {
++ cy.getAll("@node-info", "@stdout", "@stderr").then(() => {
+ // Verify sorted main logs
- cy.get('[data-cy=process-logs] pre', {timeout: 7000})
- .eq(0).should('contain', '2023-07-18T20:14:48.128642814Z first');
- cy.get('[data-cy=process-logs] pre')
- .eq(1).should('contain', '2023-07-18T20:14:48.528642814Z second');
- cy.get('[data-cy=process-logs] pre')
- .eq(2).should('contain', '2023-07-18T20:14:49.128642814Z third');
++ cy.get("[data-cy=process-logs] pre", { timeout: 7000 }).eq(0).should("contain", "2023-07-18T20:14:48.128642814Z first");
++ cy.get("[data-cy=process-logs] pre").eq(1).should("contain", "2023-07-18T20:14:48.528642814Z second");
++ cy.get("[data-cy=process-logs] pre").eq(2).should("contain", "2023-07-18T20:14:49.128642814Z third");
+
+ // Switch to All logs
- cy.get('[data-cy=process-logs-filter]').click();
- cy.get('body').contains('li', 'All logs').click();
++ cy.get("[data-cy=process-logs-filter]").click();
++ cy.get("body").contains("li", "All logs").click();
+ // Verify non-sorted lines were preserved
- cy.get('[data-cy=process-logs] pre')
- .eq(0).should('contain', '3: nodeinfo 1');
- cy.get('[data-cy=process-logs] pre')
- .eq(1).should('contain', '2: nodeinfo 2');
- cy.get('[data-cy=process-logs] pre')
- .eq(2).should('contain', '1: nodeinfo 3');
- cy.get('[data-cy=process-logs] pre')
- .eq(3).should('contain', '2: nodeinfo 4');
- cy.get('[data-cy=process-logs] pre')
- .eq(4).should('contain', '3: nodeinfo 5');
++ cy.get("[data-cy=process-logs] pre").eq(0).should("contain", "3: nodeinfo 1");
++ cy.get("[data-cy=process-logs] pre").eq(1).should("contain", "2: nodeinfo 2");
++ cy.get("[data-cy=process-logs] pre").eq(2).should("contain", "1: nodeinfo 3");
++ cy.get("[data-cy=process-logs] pre").eq(3).should("contain", "2: nodeinfo 4");
++ cy.get("[data-cy=process-logs] pre").eq(4).should("contain", "3: nodeinfo 5");
+ // Verify sorted logs
- cy.get('[data-cy=process-logs] pre')
- .eq(5).should('contain', '2023-07-18T20:14:48.128642814Z first');
- cy.get('[data-cy=process-logs] pre')
- .eq(6).should('contain', '2023-07-18T20:14:48.528642814Z second');
- cy.get('[data-cy=process-logs] pre')
- .eq(7).should('contain', '2023-07-18T20:14:49.128642814Z third');
++ cy.get("[data-cy=process-logs] pre").eq(5).should("contain", "2023-07-18T20:14:48.128642814Z first");
++ cy.get("[data-cy=process-logs] pre").eq(6).should("contain", "2023-07-18T20:14:48.528642814Z second");
++ cy.get("[data-cy=process-logs] pre").eq(7).should("contain", "2023-07-18T20:14:49.128642814Z third");
+ });
+ });
});
- cy.getAll("@containerRequest").then(function ([containerRequest]) {
- containerCount = 3;
- cy.goToPath(`/processes/${containerRequest.uuid}`);
- cy.get("[data-cy=process-runtime-status-retry-warning]", { timeout: 7000 }).should("contain", "Process retried 2 times");
- it('correctly generates sniplines', function() {
++ it("correctly generates sniplines", function () {
+ const SNIPLINE = `================ ✀ ================ ✀ ========= Some log(s) were skipped ========= ✀ ================ ✀ ================`;
- const crName = 'test_container_request';
- createContainerRequest(
- activeUser,
- crName,
- 'arvados/jobs',
- ['echo', 'hello world'],
- false, 'Committed')
- .then(function(containerRequest) {
-
++ const crName = "test_container_request";
++ createContainerRequest(activeUser, crName, "arvados/jobs", ["echo", "hello world"], false, "Committed").then(function (containerRequest) {
+ cy.appendLog(adminUser.token, containerRequest.uuid, "stdout.txt", [
- 'X'.repeat(63999) + '_' +
- 'O'.repeat(100) +
- '_' + 'X'.repeat(63999)
- ]).as('stdout');
++ "X".repeat(63999) + "_" + "O".repeat(100) + "_" + "X".repeat(63999),
++ ]).as("stdout");
+
+ cy.loginAs(activeUser);
+ cy.goToPath(`/processes/${containerRequest.uuid}`);
- cy.get('[data-cy=process-details]').should('contain', crName);
- cy.get('[data-cy=process-logs]')
- .should('contain', 'No logs yet');
++ cy.get("[data-cy=process-details]").should("contain", crName);
++ cy.get("[data-cy=process-logs]").should("contain", "No logs yet");
+
+ // Switch to stdout since lines are unsortable (no timestamp)
- cy.get('[data-cy=process-logs-filter]').click();
- cy.get('body').contains('li', 'stdout').click();
++ cy.get("[data-cy=process-logs-filter]").click();
++ cy.get("body").contains("li", "stdout").click();
+
- cy.getAll('@stdout').then(() => {
++ cy.getAll("@stdout").then(() => {
+ // Verify first 64KB and snipline
- cy.get('[data-cy=process-logs] pre', {timeout: 7000})
- .eq(0).should('contain', 'X'.repeat(63999) + '_\n' + SNIPLINE);
++ cy.get("[data-cy=process-logs] pre", { timeout: 7000 })
++ .eq(0)
++ .should("contain", "X".repeat(63999) + "_\n" + SNIPLINE);
+ // Verify last 64KB
- cy.get('[data-cy=process-logs] pre')
- .eq(1).should('contain', '_' + 'X'.repeat(63999));
++ cy.get("[data-cy=process-logs] pre")
++ .eq(1)
++ .should("contain", "_" + "X".repeat(63999));
+ // Verify none of the Os got through
- cy.get('[data-cy=process-logs] pre')
- .should('not.contain', 'O');
++ cy.get("[data-cy=process-logs] pre").should("not.contain", "O");
+ });
+ });
});
-
});
- const testInputs = [
- {
- definition: {
- id: "#main/input_file",
- label: "Label Description",
- type: "File",
- describe('I/O panel', function() {
++ describe("I/O panel", function () {
+ const testInputs = [
+ {
+ definition: {
- "id": "#main/input_file",
- "label": "Label Description",
- "type": "File"
++ id: "#main/input_file",
++ label: "Label Description",
++ type: "File",
+ },
+ input: {
- "input_file": {
- "basename": "input1.tar",
- "class": "File",
- "location": "keep:00000000000000000000000000000000+01/input1.tar",
- "secondaryFiles": [
++ input_file: {
++ basename: "input1.tar",
++ class: "File",
++ location: "keep:00000000000000000000000000000000+01/input1.tar",
++ secondaryFiles: [
+ {
- "basename": "input1-2.txt",
- "class": "File",
- "location": "keep:00000000000000000000000000000000+01/input1-2.txt"
++ basename: "input1-2.txt",
++ class: "File",
++ location: "keep:00000000000000000000000000000000+01/input1-2.txt",
+ },
+ {
- "basename": "input1-3.txt",
- "class": "File",
- "location": "keep:00000000000000000000000000000000+01/input1-3.txt"
++ basename: "input1-3.txt",
++ class: "File",
++ location: "keep:00000000000000000000000000000000+01/input1-3.txt",
+ },
+ {
- "basename": "input1-4.txt",
- "class": "File",
- "location": "keep:00000000000000000000000000000000+01/input1-4.txt"
- }
- ]
- }
- }
++ basename: "input1-4.txt",
++ class: "File",
++ location: "keep:00000000000000000000000000000000+01/input1-4.txt",
++ },
++ ],
++ },
++ },
+ },
+ {
+ definition: {
- "id": "#main/input_dir",
- "doc": "Doc Description",
- "type": "Directory"
++ id: "#main/input_dir",
++ doc: "Doc Description",
++ type: "Directory",
+ },
+ input: {
- "input_dir": {
- "basename": "11111111111111111111111111111111+01",
- "class": "Directory",
- "location": "keep:11111111111111111111111111111111+01"
- }
- }
++ input_dir: {
++ basename: "11111111111111111111111111111111+01",
++ class: "Directory",
++ location: "keep:11111111111111111111111111111111+01",
++ },
++ },
+ },
+ {
+ definition: {
- "id": "#main/input_bool",
- "doc": ["Doc desc 1", "Doc desc 2"],
- "type": "boolean"
++ id: "#main/input_bool",
++ doc: ["Doc desc 1", "Doc desc 2"],
++ type: "boolean",
+ },
+ input: {
- "input_bool": true,
- }
++ input_bool: true,
++ },
+ },
+ {
+ definition: {
- "id": "#main/input_int",
- "type": "int"
++ id: "#main/input_int",
++ type: "int",
+ },
+ input: {
- "input_int": 1,
- }
++ input_int: 1,
++ },
},
- input: {
- input_file: {
- basename: "input1.tar",
- class: "File",
- location: "keep:00000000000000000000000000000000+01/input1.tar",
- secondaryFiles: [
+ {
+ definition: {
- "id": "#main/input_long",
- "type": "long"
++ id: "#main/input_long",
++ type: "long",
+ },
+ input: {
- "input_long" : 1,
- }
++ input_long: 1,
++ },
+ },
+ {
+ definition: {
- "id": "#main/input_float",
- "type": "float"
++ id: "#main/input_float",
++ type: "float",
+ },
+ input: {
- "input_float": 1.5,
- }
++ input_float: 1.5,
++ },
+ },
+ {
+ definition: {
- "id": "#main/input_double",
- "type": "double"
++ id: "#main/input_double",
++ type: "double",
+ },
+ input: {
- "input_double": 1.3,
- }
++ input_double: 1.3,
++ },
+ },
+ {
+ definition: {
- "id": "#main/input_string",
- "type": "string"
++ id: "#main/input_string",
++ type: "string",
+ },
+ input: {
- "input_string": "Hello World",
- }
++ input_string: "Hello World",
++ },
+ },
+ {
+ definition: {
- "id": "#main/input_file_array",
- "type": {
- "items": "File",
- "type": "array"
- }
++ id: "#main/input_file_array",
++ type: {
++ items: "File",
++ type: "array",
++ },
+ },
+ input: {
- "input_file_array": [
++ input_file_array: [
{
- basename: "input1-2.txt",
- "basename": "input2.tar",
- "class": "File",
- "location": "keep:00000000000000000000000000000000+02/input2.tar"
++ basename: "input2.tar",
+ class: "File",
- location: "keep:00000000000000000000000000000000+01/input1-2.txt",
++ location: "keep:00000000000000000000000000000000+02/input2.tar",
},
{
- basename: "input1-3.txt",
- "basename": "input3.tar",
- "class": "File",
- "location": "keep:00000000000000000000000000000000+03/input3.tar",
- "secondaryFiles": [
++ basename: "input3.tar",
+ class: "File",
- location: "keep:00000000000000000000000000000000+01/input1-3.txt",
++ location: "keep:00000000000000000000000000000000+03/input3.tar",
++ secondaryFiles: [
+ {
- "basename": "input3-2.txt",
- "class": "File",
- "location": "keep:00000000000000000000000000000000+03/input3-2.txt"
- }
- ]
++ basename: "input3-2.txt",
++ class: "File",
++ location: "keep:00000000000000000000000000000000+03/input3-2.txt",
++ },
++ ],
},
{
- basename: "input1-4.txt",
- class: "File",
- location: "keep:00000000000000000000000000000000+01/input1-4.txt",
- "$import": "import_path"
- }
- ]
- }
++ $import: "import_path",
+ },
+ ],
+ },
},
- },
- {
- definition: {
- id: "#main/input_dir",
- doc: "Doc Description",
- type: "Directory",
- },
- input: {
- input_dir: {
- basename: "11111111111111111111111111111111+01",
- class: "Directory",
- location: "keep:11111111111111111111111111111111+01",
+ {
+ definition: {
- "id": "#main/input_dir_array",
- "type": {
- "items": "Directory",
- "type": "array"
- }
++ id: "#main/input_dir_array",
++ type: {
++ items: "Directory",
++ type: "array",
++ },
+ },
+ input: {
- "input_dir_array": [
++ input_dir_array: [
+ {
- "basename": "11111111111111111111111111111111+02",
- "class": "Directory",
- "location": "keep:11111111111111111111111111111111+02"
++ basename: "11111111111111111111111111111111+02",
++ class: "Directory",
++ location: "keep:11111111111111111111111111111111+02",
+ },
+ {
- "basename": "11111111111111111111111111111111+03",
- "class": "Directory",
- "location": "keep:11111111111111111111111111111111+03"
++ basename: "11111111111111111111111111111111+03",
++ class: "Directory",
++ location: "keep:11111111111111111111111111111111+03",
+ },
+ {
- "$import": "import_path"
- }
- ]
- }
++ $import: "import_path",
++ },
++ ],
+ },
},
- },
- {
- definition: {
- id: "#main/input_bool",
- doc: ["Doc desc 1", "Doc desc 2"],
- type: "boolean",
- },
- input: {
- input_bool: true,
+ {
+ definition: {
- "id": "#main/input_int_array",
- "type": {
- "items": "int",
- "type": "array"
- }
++ id: "#main/input_int_array",
++ type: {
++ items: "int",
++ type: "array",
++ },
+ },
+ input: {
- "input_int_array": [
++ input_int_array: [
+ 1,
+ 3,
+ 5,
+ {
- "$import": "import_path"
- }
- ]
- }
++ $import: "import_path",
++ },
++ ],
++ },
},
- },
- {
- definition: {
- id: "#main/input_int",
- type: "int",
+ {
+ definition: {
- "id": "#main/input_long_array",
- "type": {
- "items": "long",
- "type": "array"
- }
++ id: "#main/input_long_array",
++ type: {
++ items: "long",
++ type: "array",
++ },
+ },
+ input: {
- "input_long_array": [
++ input_long_array: [
+ 10,
+ 20,
+ {
- "$import": "import_path"
- }
- ]
- }
++ $import: "import_path",
++ },
++ ],
++ },
},
- input: {
- input_int: 1,
+ {
+ definition: {
- "id": "#main/input_float_array",
- "type": {
- "items": "float",
- "type": "array"
- }
++ id: "#main/input_float_array",
++ type: {
++ items: "float",
++ type: "array",
++ },
+ },
+ input: {
- "input_float_array": [
++ input_float_array: [
+ 10.2,
+ 10.4,
+ 10.6,
+ {
- "$import": "import_path"
- }
- ]
- }
++ $import: "import_path",
++ },
++ ],
++ },
},
- },
- {
- definition: {
- id: "#main/input_long",
- type: "long",
+ {
+ definition: {
- "id": "#main/input_double_array",
- "type": {
- "items": "double",
- "type": "array"
- }
++ id: "#main/input_double_array",
++ type: {
++ items: "double",
++ type: "array",
++ },
+ },
+ input: {
- "input_double_array": [
++ input_double_array: [
+ 20.1,
+ 20.2,
+ 20.3,
+ {
- "$import": "import_path"
- }
- ]
- }
++ $import: "import_path",
++ },
++ ],
++ },
},
- input: {
- input_long: 1,
+ {
+ definition: {
- "id": "#main/input_string_array",
- "type": {
- "items": "string",
- "type": "array"
- }
++ id: "#main/input_string_array",
++ type: {
++ items: "string",
++ type: "array",
++ },
+ },
+ input: {
- "input_string_array": [
++ input_string_array: [
+ "Hello",
+ "World",
+ "!",
+ {
- "$import": "import_path"
- }
- ]
- }
++ $import: "import_path",
++ },
++ ],
++ },
},
- },
- {
- definition: {
- id: "#main/input_float",
- type: "float",
+ {
+ definition: {
- "id": "#main/input_bool_include",
- "type": "boolean"
++ id: "#main/input_bool_include",
++ type: "boolean",
+ },
+ input: {
- "input_bool_include": {
- "$include": "include_path"
- }
- }
++ input_bool_include: {
++ $include: "include_path",
++ },
++ },
},
- input: {
- input_float: 1.5,
+ {
+ definition: {
- "id": "#main/input_int_include",
- "type": "int"
++ id: "#main/input_int_include",
++ type: "int",
+ },
+ input: {
- "input_int_include": {
- "$include": "include_path"
- }
- }
++ input_int_include: {
++ $include: "include_path",
++ },
++ },
},
- },
- {
- definition: {
- id: "#main/input_double",
- type: "double",
+ {
+ definition: {
- "id": "#main/input_float_include",
- "type": "float"
++ id: "#main/input_float_include",
++ type: "float",
+ },
+ input: {
- "input_float_include": {
- "$include": "include_path"
- }
- }
++ input_float_include: {
++ $include: "include_path",
++ },
++ },
},
- input: {
- input_double: 1.3,
+ {
+ definition: {
- "id": "#main/input_string_include",
- "type": "string"
++ id: "#main/input_string_include",
++ type: "string",
+ },
+ input: {
- "input_string_include": {
- "$include": "include_path"
- }
- }
++ input_string_include: {
++ $include: "include_path",
++ },
++ },
},
- },
- {
- definition: {
- id: "#main/input_string",
- type: "string",
+ {
+ definition: {
- "id": "#main/input_file_include",
- "type": "File"
++ id: "#main/input_file_include",
++ type: "File",
+ },
+ input: {
- "input_file_include": {
- "$include": "include_path"
- }
- }
++ input_file_include: {
++ $include: "include_path",
++ },
++ },
},
- input: {
- input_string: "Hello World",
+ {
+ definition: {
- "id": "#main/input_directory_include",
- "type": "Directory"
++ id: "#main/input_directory_include",
++ type: "Directory",
+ },
+ input: {
- "input_directory_include": {
- "$include": "include_path"
- }
- }
++ input_directory_include: {
++ $include: "include_path",
++ },
++ },
},
- },
- {
- definition: {
- id: "#main/input_file_array",
- type: {
- items: "File",
- type: "array",
+ {
+ definition: {
- "id": "#main/input_file_url",
- "type": "File"
++ id: "#main/input_file_url",
++ type: "File",
+ },
+ input: {
- "input_file_url": {
- "basename": "index.html",
- "class": "File",
- "location": "http://example.com/index.html"
- }
- }
- }
++ input_file_url: {
++ basename: "index.html",
++ class: "File",
++ location: "http://example.com/index.html",
++ },
+ },
+ },
- input: {
- input_file_array: [
- {
- basename: "input2.tar",
+ ];
+
+ const testOutputs = [
+ {
+ definition: {
- "id": "#main/output_file",
- "label": "Label Description",
- "type": "File"
++ id: "#main/output_file",
++ label: "Label Description",
++ type: "File",
+ },
+ output: {
- "output_file": {
- "basename": "cat.png",
- "class": "File",
- "location": "cat.png"
- }
- }
++ output_file: {
++ basename: "cat.png",
+ class: "File",
- location: "keep:00000000000000000000000000000000+02/input2.tar",
++ location: "cat.png",
+ },
- {
- basename: "input3.tar",
++ },
+ },
+ {
+ definition: {
- "id": "#main/output_file_with_secondary",
- "doc": "Doc Description",
- "type": "File"
++ id: "#main/output_file_with_secondary",
++ doc: "Doc Description",
++ type: "File",
+ },
+ output: {
- "output_file_with_secondary": {
- "basename": "main.dat",
- "class": "File",
- "location": "main.dat",
- "secondaryFiles": [
++ output_file_with_secondary: {
++ basename: "main.dat",
+ class: "File",
- location: "keep:00000000000000000000000000000000+03/input3.tar",
++ location: "main.dat",
+ secondaryFiles: [
{
- basename: "input3-2.txt",
- "basename": "secondary.dat",
- "class": "File",
- "location": "secondary.dat"
++ basename: "secondary.dat",
+ class: "File",
- location: "keep:00000000000000000000000000000000+03/input3-2.txt",
++ location: "secondary.dat",
+ },
+ {
- "basename": "secondary2.dat",
- "class": "File",
- "location": "secondary2.dat"
- }
- ]
- }
- }
++ basename: "secondary2.dat",
++ class: "File",
++ location: "secondary2.dat",
+ },
+ ],
+ },
- {
- $import: "import_path",
- },
- ],
- },
- },
- {
- definition: {
- id: "#main/input_dir_array",
- type: {
- items: "Directory",
- type: "array",
+ },
},
- input: {
- input_dir_array: [
- {
- basename: "11111111111111111111111111111111+02",
- class: "Directory",
- location: "keep:11111111111111111111111111111111+02",
- },
- {
- basename: "11111111111111111111111111111111+03",
+ {
+ definition: {
- "id": "#main/output_dir",
- "doc": ["Doc desc 1", "Doc desc 2"],
- "type": "Directory"
++ id: "#main/output_dir",
++ doc: ["Doc desc 1", "Doc desc 2"],
++ type: "Directory",
+ },
+ output: {
- "output_dir": {
- "basename": "outdir1",
- "class": "Directory",
- "location": "outdir1"
- }
- }
++ output_dir: {
++ basename: "outdir1",
+ class: "Directory",
- location: "keep:11111111111111111111111111111111+03",
++ location: "outdir1",
+ },
- {
- $import: "import_path",
- },
- ],
- },
- },
- {
- definition: {
- id: "#main/input_int_array",
- type: {
- items: "int",
- type: "array",
+ },
},
- input: {
- input_int_array: [
- 1,
- 3,
- 5,
- {
- $import: "import_path",
- },
- ],
- },
- },
- {
- definition: {
- id: "#main/input_long_array",
- type: {
- items: "long",
- type: "array",
+ {
+ definition: {
- "id": "#main/output_bool",
- "type": "boolean"
++ id: "#main/output_bool",
++ type: "boolean",
},
- },
- input: {
- input_long_array: [
- 10,
- 20,
- {
- $import: "import_path",
- },
- ],
- },
- },
- {
- definition: {
- id: "#main/input_float_array",
- type: {
- items: "float",
- type: "array",
+ output: {
- "output_bool": true
- }
++ output_bool: true,
+ },
},
- input: {
- input_float_array: [
- 10.2,
- 10.4,
- 10.6,
- {
- $import: "import_path",
- },
- ],
- },
- },
- {
- definition: {
- id: "#main/input_double_array",
- type: {
- items: "double",
- type: "array",
+ {
+ definition: {
- "id": "#main/output_int",
- "type": "int"
++ id: "#main/output_int",
++ type: "int",
},
- },
- input: {
- input_double_array: [
- 20.1,
- 20.2,
- 20.3,
- {
- $import: "import_path",
- },
- ],
- },
- },
- {
- definition: {
- id: "#main/input_string_array",
- type: {
- items: "string",
- type: "array",
+ output: {
- "output_int": 1
- }
++ output_int: 1,
+ },
},
- input: {
- input_string_array: [
- "Hello",
- "World",
- "!",
- {
- $import: "import_path",
- },
- ],
- },
- },
- {
- definition: {
- id: "#main/input_bool_include",
- type: "boolean",
- },
- input: {
- input_bool_include: {
- $include: "include_path",
+ {
+ definition: {
- "id": "#main/output_long",
- "type": "long"
++ id: "#main/output_long",
++ type: "long",
},
- },
- },
- {
- definition: {
- id: "#main/input_int_include",
- type: "int",
- },
- input: {
- input_int_include: {
- $include: "include_path",
+ output: {
- "output_long": 1
- }
++ output_long: 1,
+ },
},
- },
- {
- definition: {
- id: "#main/input_float_include",
- type: "float",
- },
- input: {
- input_float_include: {
- $include: "include_path",
+ {
+ definition: {
- "id": "#main/output_float",
- "type": "float"
++ id: "#main/output_float",
++ type: "float",
},
- },
- },
- {
- definition: {
- id: "#main/input_string_include",
- type: "string",
- },
- input: {
- input_string_include: {
- $include: "include_path",
+ output: {
- "output_float": 100.5
- }
++ output_float: 100.5,
+ },
},
- },
- {
- definition: {
- id: "#main/input_file_include",
- type: "File",
- },
- input: {
- input_file_include: {
- $include: "include_path",
+ {
+ definition: {
- "id": "#main/output_double",
- "type": "double"
++ id: "#main/output_double",
++ type: "double",
},
- },
- },
- {
- definition: {
- id: "#main/input_directory_include",
- type: "Directory",
- },
- input: {
- input_directory_include: {
- $include: "include_path",
+ output: {
- "output_double": 100.3
- }
++ output_double: 100.3,
+ },
},
- },
- {
- definition: {
- id: "#main/input_file_url",
- type: "File",
- },
- input: {
- input_file_url: {
- basename: "index.html",
- class: "File",
- location: "http://example.com/index.html",
+ {
+ definition: {
- "id": "#main/output_string",
- "type": "string"
++ id: "#main/output_string",
++ type: "string",
},
- },
- },
- ];
-
- const testOutputs = [
- {
- definition: {
- id: "#main/output_file",
- label: "Label Description",
- type: "File",
- },
- output: {
- output_file: {
- basename: "cat.png",
- class: "File",
- location: "cat.png",
+ output: {
- "output_string": "Hello output"
- }
++ output_string: "Hello output",
+ },
},
- },
- {
- definition: {
- id: "#main/output_file_with_secondary",
- doc: "Doc Description",
- type: "File",
- },
- output: {
- output_file_with_secondary: {
- basename: "main.dat",
- class: "File",
- location: "main.dat",
- secondaryFiles: [
+ {
+ definition: {
- "id": "#main/output_file_array",
- "type": {
- "items": "File",
- "type": "array"
- }
++ id: "#main/output_file_array",
++ type: {
++ items: "File",
++ type: "array",
++ },
+ },
+ output: {
- "output_file_array": [
++ output_file_array: [
{
- basename: "secondary.dat",
- "basename": "output2.tar",
- "class": "File",
- "location": "output2.tar"
++ basename: "output2.tar",
+ class: "File",
- location: "secondary.dat",
++ location: "output2.tar",
},
{
- basename: "secondary2.dat",
- "basename": "output3.tar",
- "class": "File",
- "location": "output3.tar"
- }
- ]
- }
++ basename: "output3.tar",
+ class: "File",
- location: "secondary2.dat",
++ location: "output3.tar",
+ },
+ ],
+ },
},
- },
- {
- definition: {
- id: "#main/output_dir",
- doc: ["Doc desc 1", "Doc desc 2"],
- type: "Directory",
- },
- output: {
- output_dir: {
- basename: "outdir1",
- class: "Directory",
- location: "outdir1",
+ {
+ definition: {
- "id": "#main/output_dir_array",
- "type": {
- "items": "Directory",
- "type": "array"
- }
++ id: "#main/output_dir_array",
++ type: {
++ items: "Directory",
++ type: "array",
++ },
},
- },
- },
- {
- definition: {
- id: "#main/output_bool",
- type: "boolean",
- },
- output: {
- output_bool: true,
- },
- },
- {
- definition: {
- id: "#main/output_int",
- type: "int",
- },
- output: {
- output_int: 1,
- },
- },
- {
- definition: {
- id: "#main/output_long",
- type: "long",
- },
- output: {
- output_long: 1,
- },
- },
- {
- definition: {
- id: "#main/output_float",
- type: "float",
- },
- output: {
- output_float: 100.5,
- },
- },
- {
- definition: {
- id: "#main/output_double",
- type: "double",
- },
- output: {
- output_double: 100.3,
- },
- },
- {
- definition: {
- id: "#main/output_string",
- type: "string",
- },
- output: {
- output_string: "Hello output",
- },
- },
- {
- definition: {
- id: "#main/output_file_array",
- type: {
- items: "File",
- type: "array",
+ output: {
- "output_dir_array": [
++ output_dir_array: [
+ {
- "basename": "outdir2",
- "class": "Directory",
- "location": "outdir2"
++ basename: "outdir2",
++ class: "Directory",
++ location: "outdir2",
+ },
+ {
- "basename": "outdir3",
- "class": "Directory",
- "location": "outdir3"
- }
- ]
- }
++ basename: "outdir3",
++ class: "Directory",
++ location: "outdir3",
++ },
++ ],
+ },
},
- output: {
- output_file_array: [
- {
- basename: "output2.tar",
- class: "File",
- location: "output2.tar",
- },
- {
- basename: "output3.tar",
- class: "File",
- location: "output3.tar",
+ {
+ definition: {
- "id": "#main/output_int_array",
- "type": {
- "items": "int",
- "type": "array"
- }
++ id: "#main/output_int_array",
++ type: {
++ items: "int",
++ type: "array",
+ },
- ],
- },
- },
- {
- definition: {
- id: "#main/output_dir_array",
- type: {
- items: "Directory",
- type: "array",
+ },
+ output: {
- "output_int_array": [
- 10,
- 11,
- 12
- ]
- }
++ output_int_array: [10, 11, 12],
+ },
},
- output: {
- output_dir_array: [
- {
- basename: "outdir2",
- class: "Directory",
- location: "outdir2",
- },
- {
- basename: "outdir3",
- class: "Directory",
- location: "outdir3",
+ {
+ definition: {
- "id": "#main/output_long_array",
- "type": {
- "items": "long",
- "type": "array"
- }
++ id: "#main/output_long_array",
++ type: {
++ items: "long",
++ type: "array",
+ },
- ],
- },
- },
- {
- definition: {
- id: "#main/output_int_array",
- type: {
- items: "int",
- type: "array",
},
- },
- output: {
- output_int_array: [10, 11, 12],
- },
- },
- {
- definition: {
- id: "#main/output_long_array",
- type: {
- items: "long",
- type: "array",
+ output: {
- "output_long_array": [
- 51,
- 52
- ]
- }
++ output_long_array: [51, 52],
+ },
},
- output: {
- output_long_array: [51, 52],
- },
- },
- {
- definition: {
- id: "#main/output_float_array",
- type: {
- items: "float",
- type: "array",
+ {
+ definition: {
- "id": "#main/output_float_array",
- "type": {
- "items": "float",
- "type": "array"
- }
++ id: "#main/output_float_array",
++ type: {
++ items: "float",
++ type: "array",
++ },
},
- },
- output: {
- output_float_array: [100.2, 100.4, 100.6],
- },
- },
- {
- definition: {
- id: "#main/output_double_array",
- type: {
- items: "double",
- type: "array",
+ output: {
- "output_float_array": [
- 100.2,
- 100.4,
- 100.6
- ]
- }
++ output_float_array: [100.2, 100.4, 100.6],
+ },
},
- output: {
- output_double_array: [100.1, 100.2, 100.3],
- },
- },
- {
- definition: {
- id: "#main/output_string_array",
- type: {
- items: "string",
- type: "array",
+ {
+ definition: {
- "id": "#main/output_double_array",
- "type": {
- "items": "double",
- "type": "array"
- }
++ id: "#main/output_double_array",
++ type: {
++ items: "double",
++ type: "array",
++ },
+ },
+ output: {
- "output_double_array": [
- 100.1,
- 100.2,
- 100.3
- ]
- }
++ output_double_array: [100.1, 100.2, 100.3],
+ },
},
- output: {
- output_string_array: ["Hello", "Output", "!"],
+ {
+ definition: {
- "id": "#main/output_string_array",
- "type": {
- "items": "string",
- "type": "array"
- }
++ id: "#main/output_string_array",
++ type: {
++ items: "string",
++ type: "array",
++ },
+ },
+ output: {
- "output_string_array": [
- "Hello",
- "Output",
- "!"
- ]
- }
- }
++ output_string_array: ["Hello", "Output", "!"],
++ },
+ },
- },
- ];
-
- const verifyIOParameter = (name, label, doc, val, collection, multipleRows) => {
- cy.get("table tr")
- .contains(name)
- .parents("tr")
- .within($mainRow => {
- label && cy.contains(label);
-
- if (multipleRows) {
- cy.get($mainRow).nextUntil('[data-cy="process-io-param"]').as("secondaryRows");
- if (val) {
- if (Array.isArray(val)) {
- val.forEach(v => cy.get("@secondaryRows").contains(v));
- } else {
- cy.get("@secondaryRows").contains(val);
+ ];
+
+ const verifyIOParameter = (name, label, doc, val, collection, multipleRows) => {
- cy.get('table tr').contains(name).parents('tr').within(($mainRow) => {
- label && cy.contains(label);
-
- if (multipleRows) {
- cy.get($mainRow).nextUntil('[data-cy="process-io-param"]').as('secondaryRows');
- if (val) {
- if (Array.isArray(val)) {
- val.forEach(v => cy.get('@secondaryRows').contains(v));
- } else {
- cy.get('@secondaryRows').contains(val);
++ cy.get("table tr")
++ .contains(name)
++ .parents("tr")
++ .within($mainRow => {
++ label && cy.contains(label);
++
++ if (multipleRows) {
++ cy.get($mainRow).nextUntil('[data-cy="process-io-param"]').as("secondaryRows");
++ if (val) {
++ if (Array.isArray(val)) {
++ val.forEach(v => cy.get("@secondaryRows").contains(v));
++ } else {
++ cy.get("@secondaryRows").contains(val);
++ }
}
-- }
-- if (collection) {
- cy.get("@secondaryRows").contains(collection);
- cy.get('@secondaryRows').contains(collection);
-- }
-- } else {
-- if (val) {
-- if (Array.isArray(val)) {
-- val.forEach(v => cy.contains(v));
-- } else {
-- cy.contains(val);
++ if (collection) {
++ cy.get("@secondaryRows").contains(collection);
++ }
++ } else {
++ if (val) {
++ if (Array.isArray(val)) {
++ val.forEach(v => cy.contains(v));
++ } else {
++ cy.contains(val);
++ }
++ }
++ if (collection) {
++ cy.contains(collection);
}
}
-- if (collection) {
-- cy.contains(collection);
-- }
-- }
- });
- };
--
- const verifyIOParameterImage = (name, url) => {
- cy.get("table tr")
- .contains(name)
- .parents("tr")
- .within(() => {
- cy.get('[alt="Inline Preview"]')
- .should("be.visible")
- .and($img => {
- expect($img[0].naturalWidth).to.be.greaterThan(0);
- expect($img[0].src).contains(url);
- });
-
- });
++ });
+ };
+
+ const verifyIOParameterImage = (name, url) => {
- cy.get('table tr').contains(name).parents('tr').within(() => {
- cy.get('[alt="Inline Preview"]')
- .should('be.visible')
- .and(($img) => {
- expect($img[0].naturalWidth).to.be.greaterThan(0);
- expect($img[0].src).contains(url);
- })
- });
++ cy.get("table tr")
++ .contains(name)
++ .parents("tr")
++ .within(() => {
++ cy.get('[alt="Inline Preview"]')
++ .should("be.visible")
++ .and($img => {
++ expect($img[0].naturalWidth).to.be.greaterThan(0);
++ expect($img[0].src).contains(url);
++ });
++ });
+ };
+
- it('displays IO parameters with keep links and previews', function() {
++ it("displays IO parameters with keep links and previews", function () {
+ // Create output collection for real files
+ cy.createCollection(adminUser.token, {
+ name: `Test collection ${Math.floor(Math.random() * 999999)}`,
+ owner_uuid: activeUser.user.uuid,
- }).then((testOutputCollection) => {
- cy.loginAs(activeUser);
++ }).then(testOutputCollection => {
++ cy.loginAs(activeUser);
+
- cy.goToPath(`/collections/${testOutputCollection.uuid}`);
++ cy.goToPath(`/collections/${testOutputCollection.uuid}`);
+
- cy.get('[data-cy=upload-button]').click();
++ cy.get("[data-cy=upload-button]").click();
+
- cy.fixture('files/cat.png', 'base64').then(content => {
- cy.get('[data-cy=drag-and-drop]').upload(content, 'cat.png');
- cy.get('[data-cy=form-submit-btn]').click();
- cy.waitForDom().get('[data-cy=form-submit-btn]').should('not.exist');
- // Confirm final collection state.
- cy.get('[data-cy=collection-files-panel]')
- .contains('cat.png').should('exist');
- });
++ cy.fixture("files/cat.png", "base64").then(content => {
++ cy.get("[data-cy=drag-and-drop]").upload(content, "cat.png");
++ cy.get("[data-cy=form-submit-btn]").click();
++ cy.waitForDom().get("[data-cy=form-submit-btn]").should("not.exist");
++ // Confirm final collection state.
++ cy.get("[data-cy=collection-files-panel]").contains("cat.png").should("exist");
++ });
+
- cy.getCollection(activeUser.token, testOutputCollection.uuid).as('testOutputCollection');
- });
++ cy.getCollection(activeUser.token, testOutputCollection.uuid).as("testOutputCollection");
+ });
- };
- it("displays IO parameters with keep links and previews", function () {
- // Create output collection for real files
- cy.createCollection(adminUser.token, {
- name: `Test collection ${Math.floor(Math.random() * 999999)}`,
- owner_uuid: activeUser.user.uuid,
- }).then(testOutputCollection => {
- cy.loginAs(activeUser);
+ // Get updated collection pdh
- cy.getAll('@testOutputCollection').then(([testOutputCollection]) => {
++ cy.getAll("@testOutputCollection").then(([testOutputCollection]) => {
+ // Add output uuid and inputs to container request
- cy.intercept({method: 'GET', url: '**/arvados/v1/container_requests/*'}, (req) => {
- req.reply((res) => {
++ cy.intercept({ method: "GET", url: "**/arvados/v1/container_requests/*" }, req => {
++ req.reply(res => {
+ res.body.output_uuid = testOutputCollection.uuid;
+ res.body.mounts["/var/lib/cwl/cwl.input.json"] = {
- content: testInputs.map((param) => (param.input)).reduce((acc, val) => (Object.assign(acc, val)), {})
++ content: testInputs.map(param => param.input).reduce((acc, val) => Object.assign(acc, val), {}),
+ };
+ res.body.mounts["/var/lib/cwl/workflow.json"] = {
+ content: {
- $graph: [{
- id: "#main",
- inputs: testInputs.map((input) => (input.definition)),
- outputs: testOutputs.map((output) => (output.definition))
- }]
- }
++ $graph: [
++ {
++ id: "#main",
++ inputs: testInputs.map(input => input.definition),
++ outputs: testOutputs.map(output => output.definition),
++ },
++ ],
++ },
+ };
+ });
+ });
- cy.goToPath(`/collections/${testOutputCollection.uuid}`);
+ // Stub fake output collection
- cy.intercept({method: 'GET', url: `**/arvados/v1/collections/${testOutputCollection.uuid}*`}, {
- statusCode: 200,
- body: {
- uuid: testOutputCollection.uuid,
- portable_data_hash: testOutputCollection.portable_data_hash,
++ cy.intercept(
++ { method: "GET", url: `**/arvados/v1/collections/${testOutputCollection.uuid}*` },
++ {
++ statusCode: 200,
++ body: {
++ uuid: testOutputCollection.uuid,
++ portable_data_hash: testOutputCollection.portable_data_hash,
++ },
+ }
- });
++ );
- cy.get("[data-cy=upload-button]").click();
+ // Stub fake output json
- cy.intercept({method: 'GET', url: '**/c%3Dzzzzz-4zz18-zzzzzzzzzzzzzzz/cwl.output.json'}, {
- statusCode: 200,
- body: testOutputs.map((param) => (param.output)).reduce((acc, val) => (Object.assign(acc, val)), {})
- });
++ cy.intercept(
++ { method: "GET", url: "**/c%3Dzzzzz-4zz18-zzzzzzzzzzzzzzz/cwl.output.json" },
++ {
++ statusCode: 200,
++ body: testOutputs.map(param => param.output).reduce((acc, val) => Object.assign(acc, val), {}),
++ }
++ );
- cy.fixture("files/cat.png", "base64").then(content => {
- cy.get("[data-cy=drag-and-drop]").upload(content, "cat.png");
- cy.get("[data-cy=form-submit-btn]").click();
- cy.waitForDom().get("[data-cy=form-submit-btn]").should("not.exist");
- // Confirm final collection state.
- cy.get("[data-cy=collection-files-panel]").contains("cat.png").should("exist");
+ // Stub webdav response, points to output json
- cy.intercept({method: 'PROPFIND', url: '*'}, {
- fixture: 'webdav-propfind-outputs.xml',
- });
++ cy.intercept(
++ { method: "PROPFIND", url: "*" },
++ {
++ fixture: "webdav-propfind-outputs.xml",
++ }
++ );
});
- cy.getCollection(activeUser.token, testOutputCollection.uuid).as("testOutputCollection");
- createContainerRequest(
- activeUser,
- 'test_container_request',
- 'arvados/jobs',
- ['echo', 'hello world'],
- false, 'Committed')
- .as('containerRequest');
++ createContainerRequest(activeUser, "test_container_request", "arvados/jobs", ["echo", "hello world"], false, "Committed").as(
++ "containerRequest"
++ );
+
- cy.getAll('@containerRequest', '@testOutputCollection').then(function([containerRequest, testOutputCollection]) {
++ cy.getAll("@containerRequest", "@testOutputCollection").then(function ([containerRequest, testOutputCollection]) {
+ cy.goToPath(`/processes/${containerRequest.uuid}`);
- cy.get('[data-cy=process-io-card] h6').contains('Inputs')
- .parents('[data-cy=process-io-card]').within(() => {
- verifyIOParameter('input_file', null, "Label Description", 'input1.tar', '00000000000000000000000000000000+01');
- verifyIOParameter('input_file', null, "Label Description", 'input1-2.txt', undefined, true);
- verifyIOParameter('input_file', null, "Label Description", 'input1-3.txt', undefined, true);
- verifyIOParameter('input_file', null, "Label Description", 'input1-4.txt', undefined, true);
- verifyIOParameter('input_dir', null, "Doc Description", '/', '11111111111111111111111111111111+01');
- verifyIOParameter('input_bool', null, "Doc desc 1, Doc desc 2", 'true');
- verifyIOParameter('input_int', null, null, '1');
- verifyIOParameter('input_long', null, null, '1');
- verifyIOParameter('input_float', null, null, '1.5');
- verifyIOParameter('input_double', null, null, '1.3');
- verifyIOParameter('input_string', null, null, 'Hello World');
- verifyIOParameter('input_file_array', null, null, 'input2.tar', '00000000000000000000000000000000+02');
- verifyIOParameter('input_file_array', null, null, 'input3.tar', undefined, true);
- verifyIOParameter('input_file_array', null, null, 'input3-2.txt', undefined, true);
- verifyIOParameter('input_file_array', null, null, 'Cannot display value', undefined, true);
- verifyIOParameter('input_dir_array', null, null, '/', '11111111111111111111111111111111+02');
- verifyIOParameter('input_dir_array', null, null, '/', '11111111111111111111111111111111+03', true);
- verifyIOParameter('input_dir_array', null, null, 'Cannot display value', undefined, true);
- verifyIOParameter('input_int_array', null, null, ["1", "3", "5", "Cannot display value"]);
- verifyIOParameter('input_long_array', null, null, ["10", "20", "Cannot display value"]);
- verifyIOParameter('input_float_array', null, null, ["10.2", "10.4", "10.6", "Cannot display value"]);
- verifyIOParameter('input_double_array', null, null, ["20.1", "20.2", "20.3", "Cannot display value"]);
- verifyIOParameter('input_string_array', null, null, ["Hello", "World", "!", "Cannot display value"]);
- verifyIOParameter('input_bool_include', null, null, "Cannot display value");
- verifyIOParameter('input_int_include', null, null, "Cannot display value");
- verifyIOParameter('input_float_include', null, null, "Cannot display value");
- verifyIOParameter('input_string_include', null, null, "Cannot display value");
- verifyIOParameter('input_file_include', null, null, "Cannot display value");
- verifyIOParameter('input_directory_include', null, null, "Cannot display value");
- verifyIOParameter('input_file_url', null, null, "http://example.com/index.html");
++ cy.get("[data-cy=process-io-card] h6")
++ .contains("Inputs")
++ .parents("[data-cy=process-io-card]")
++ .within(() => {
++ verifyIOParameter("input_file", null, "Label Description", "input1.tar", "00000000000000000000000000000000+01");
++ verifyIOParameter("input_file", null, "Label Description", "input1-2.txt", undefined, true);
++ verifyIOParameter("input_file", null, "Label Description", "input1-3.txt", undefined, true);
++ verifyIOParameter("input_file", null, "Label Description", "input1-4.txt", undefined, true);
++ verifyIOParameter("input_dir", null, "Doc Description", "/", "11111111111111111111111111111111+01");
++ verifyIOParameter("input_bool", null, "Doc desc 1, Doc desc 2", "true");
++ verifyIOParameter("input_int", null, null, "1");
++ verifyIOParameter("input_long", null, null, "1");
++ verifyIOParameter("input_float", null, null, "1.5");
++ verifyIOParameter("input_double", null, null, "1.3");
++ verifyIOParameter("input_string", null, null, "Hello World");
++ verifyIOParameter("input_file_array", null, null, "input2.tar", "00000000000000000000000000000000+02");
++ verifyIOParameter("input_file_array", null, null, "input3.tar", undefined, true);
++ verifyIOParameter("input_file_array", null, null, "input3-2.txt", undefined, true);
++ verifyIOParameter("input_file_array", null, null, "Cannot display value", undefined, true);
++ verifyIOParameter("input_dir_array", null, null, "/", "11111111111111111111111111111111+02");
++ verifyIOParameter("input_dir_array", null, null, "/", "11111111111111111111111111111111+03", true);
++ verifyIOParameter("input_dir_array", null, null, "Cannot display value", undefined, true);
++ verifyIOParameter("input_int_array", null, null, ["1", "3", "5", "Cannot display value"]);
++ verifyIOParameter("input_long_array", null, null, ["10", "20", "Cannot display value"]);
++ verifyIOParameter("input_float_array", null, null, ["10.2", "10.4", "10.6", "Cannot display value"]);
++ verifyIOParameter("input_double_array", null, null, ["20.1", "20.2", "20.3", "Cannot display value"]);
++ verifyIOParameter("input_string_array", null, null, ["Hello", "World", "!", "Cannot display value"]);
++ verifyIOParameter("input_bool_include", null, null, "Cannot display value");
++ verifyIOParameter("input_int_include", null, null, "Cannot display value");
++ verifyIOParameter("input_float_include", null, null, "Cannot display value");
++ verifyIOParameter("input_string_include", null, null, "Cannot display value");
++ verifyIOParameter("input_file_include", null, null, "Cannot display value");
++ verifyIOParameter("input_directory_include", null, null, "Cannot display value");
++ verifyIOParameter("input_file_url", null, null, "http://example.com/index.html");
+ });
- cy.get('[data-cy=process-io-card] h6').contains('Outputs')
- .parents('[data-cy=process-io-card]').within((ctx) => {
++ cy.get("[data-cy=process-io-card] h6")
++ .contains("Outputs")
++ .parents("[data-cy=process-io-card]")
++ .within(ctx => {
+ cy.get(ctx).scrollIntoView();
- cy.get('[data-cy="io-preview-image-toggle"]').click({waitForAnimations: false});
++ cy.get('[data-cy="io-preview-image-toggle"]').click({ waitForAnimations: false });
+ const outPdh = testOutputCollection.portable_data_hash;
+
- verifyIOParameter('output_file', null, "Label Description", 'cat.png', `${outPdh}`);
- verifyIOParameterImage('output_file', `/c=${outPdh}/cat.png`);
- verifyIOParameter('output_file_with_secondary', null, "Doc Description", 'main.dat', `${outPdh}`);
- verifyIOParameter('output_file_with_secondary', null, "Doc Description", 'secondary.dat', undefined, true);
- verifyIOParameter('output_file_with_secondary', null, "Doc Description", 'secondary2.dat', undefined, true);
- verifyIOParameter('output_dir', null, "Doc desc 1, Doc desc 2", 'outdir1', `${outPdh}`);
- verifyIOParameter('output_bool', null, null, 'true');
- verifyIOParameter('output_int', null, null, '1');
- verifyIOParameter('output_long', null, null, '1');
- verifyIOParameter('output_float', null, null, '100.5');
- verifyIOParameter('output_double', null, null, '100.3');
- verifyIOParameter('output_string', null, null, 'Hello output');
- verifyIOParameter('output_file_array', null, null, 'output2.tar', `${outPdh}`);
- verifyIOParameter('output_file_array', null, null, 'output3.tar', undefined, true);
- verifyIOParameter('output_dir_array', null, null, 'outdir2', `${outPdh}`);
- verifyIOParameter('output_dir_array', null, null, 'outdir3', undefined, true);
- verifyIOParameter('output_int_array', null, null, ["10", "11", "12"]);
- verifyIOParameter('output_long_array', null, null, ["51", "52"]);
- verifyIOParameter('output_float_array', null, null, ["100.2", "100.4", "100.6"]);
- verifyIOParameter('output_double_array', null, null, ["100.1", "100.2", "100.3"]);
- verifyIOParameter('output_string_array', null, null, ["Hello", "Output", "!"]);
++ verifyIOParameter("output_file", null, "Label Description", "cat.png", `${outPdh}`);
++ verifyIOParameterImage("output_file", `/c=${outPdh}/cat.png`);
++ verifyIOParameter("output_file_with_secondary", null, "Doc Description", "main.dat", `${outPdh}`);
++ verifyIOParameter("output_file_with_secondary", null, "Doc Description", "secondary.dat", undefined, true);
++ verifyIOParameter("output_file_with_secondary", null, "Doc Description", "secondary2.dat", undefined, true);
++ verifyIOParameter("output_dir", null, "Doc desc 1, Doc desc 2", "outdir1", `${outPdh}`);
++ verifyIOParameter("output_bool", null, null, "true");
++ verifyIOParameter("output_int", null, null, "1");
++ verifyIOParameter("output_long", null, null, "1");
++ verifyIOParameter("output_float", null, null, "100.5");
++ verifyIOParameter("output_double", null, null, "100.3");
++ verifyIOParameter("output_string", null, null, "Hello output");
++ verifyIOParameter("output_file_array", null, null, "output2.tar", `${outPdh}`);
++ verifyIOParameter("output_file_array", null, null, "output3.tar", undefined, true);
++ verifyIOParameter("output_dir_array", null, null, "outdir2", `${outPdh}`);
++ verifyIOParameter("output_dir_array", null, null, "outdir3", undefined, true);
++ verifyIOParameter("output_int_array", null, null, ["10", "11", "12"]);
++ verifyIOParameter("output_long_array", null, null, ["51", "52"]);
++ verifyIOParameter("output_float_array", null, null, ["100.2", "100.4", "100.6"]);
++ verifyIOParameter("output_double_array", null, null, ["100.1", "100.2", "100.3"]);
++ verifyIOParameter("output_string_array", null, null, ["Hello", "Output", "!"]);
+ });
+ });
});
- // Get updated collection pdh
- cy.getAll("@testOutputCollection").then(([testOutputCollection]) => {
- it('displays IO parameters with no value', function() {
-
- const fakeOutputUUID = 'zzzzz-4zz18-abcdefghijklmno';
- const fakeOutputPDH = '11111111111111111111111111111111+99/';
++ it("displays IO parameters with no value", function () {
++ const fakeOutputUUID = "zzzzz-4zz18-abcdefghijklmno";
++ const fakeOutputPDH = "11111111111111111111111111111111+99/";
+
+ cy.loginAs(activeUser);
+
// Add output uuid and inputs to container request
- cy.intercept({method: 'GET', url: '**/arvados/v1/container_requests/*'}, (req) => {
- req.reply((res) => {
+ cy.intercept({ method: "GET", url: "**/arvados/v1/container_requests/*" }, req => {
+ req.reply(res => {
- res.body.output_uuid = testOutputCollection.uuid;
+ res.body.output_uuid = fakeOutputUUID;
res.body.mounts["/var/lib/cwl/cwl.input.json"] = {
- content: testInputs.map(param => param.input).reduce((acc, val) => Object.assign(acc, val), {}),
- content: {}
++ content: {},
};
res.body.mounts["/var/lib/cwl/workflow.json"] = {
content: {
});
// Stub fake output collection
- cy.intercept({method: 'GET', url: `**/arvados/v1/collections/${fakeOutputUUID}*`}, {
- statusCode: 200,
- body: {
- uuid: fakeOutputUUID,
- portable_data_hash: fakeOutputPDH,
+ cy.intercept(
- { method: "GET", url: `**/arvados/v1/collections/${testOutputCollection.uuid}*` },
++ { method: "GET", url: `**/arvados/v1/collections/${fakeOutputUUID}*` },
+ {
+ statusCode: 200,
+ body: {
- uuid: testOutputCollection.uuid,
- portable_data_hash: testOutputCollection.portable_data_hash,
++ uuid: fakeOutputUUID,
++ portable_data_hash: fakeOutputPDH,
+ },
}
- });
+ );
// Stub fake output json
- cy.intercept({method: 'GET', url: `**/c%3D${fakeOutputUUID}/cwl.output.json`}, {
- statusCode: 200,
- body: {}
- });
+ cy.intercept(
- { method: "GET", url: "**/c%3Dzzzzz-4zz18-zzzzzzzzzzzzzzz/cwl.output.json" },
++ { method: "GET", url: `**/c%3D${fakeOutputUUID}/cwl.output.json` },
+ {
+ statusCode: 200,
- body: testOutputs.map(param => param.output).reduce((acc, val) => Object.assign(acc, val), {}),
- }
- );
-
- // Stub webdav response, points to output json
- cy.intercept(
- { method: "PROPFIND", url: "*" },
- {
- fixture: "webdav-propfind-outputs.xml",
++ body: {},
+ }
+ );
- });
-
- createContainerRequest(activeUser, "test_container_request", "arvados/jobs", ["echo", "hello world"], false, "Committed").as(
- "containerRequest"
- );
- cy.getAll("@containerRequest", "@testOutputCollection").then(function ([containerRequest, testOutputCollection]) {
- cy.goToPath(`/processes/${containerRequest.uuid}`);
- cy.get("[data-cy=process-io-card] h6")
- .contains("Inputs")
- .parents("[data-cy=process-io-card]")
- .within(() => {
- verifyIOParameter("input_file", null, "Label Description", "input1.tar", "00000000000000000000000000000000+01");
- verifyIOParameter("input_file", null, "Label Description", "input1-2.txt", undefined, true);
- verifyIOParameter("input_file", null, "Label Description", "input1-3.txt", undefined, true);
- verifyIOParameter("input_file", null, "Label Description", "input1-4.txt", undefined, true);
- verifyIOParameter("input_dir", null, "Doc Description", "/", "11111111111111111111111111111111+01");
- verifyIOParameter("input_bool", null, "Doc desc 1, Doc desc 2", "true");
- verifyIOParameter("input_int", null, null, "1");
- verifyIOParameter("input_long", null, null, "1");
- verifyIOParameter("input_float", null, null, "1.5");
- verifyIOParameter("input_double", null, null, "1.3");
- verifyIOParameter("input_string", null, null, "Hello World");
- verifyIOParameter("input_file_array", null, null, "input2.tar", "00000000000000000000000000000000+02");
- verifyIOParameter("input_file_array", null, null, "input3.tar", undefined, true);
- verifyIOParameter("input_file_array", null, null, "input3-2.txt", undefined, true);
- verifyIOParameter("input_file_array", null, null, "Cannot display value", undefined, true);
- verifyIOParameter("input_dir_array", null, null, "/", "11111111111111111111111111111111+02");
- verifyIOParameter("input_dir_array", null, null, "/", "11111111111111111111111111111111+03", true);
- verifyIOParameter("input_dir_array", null, null, "Cannot display value", undefined, true);
- verifyIOParameter("input_int_array", null, null, ["1", "3", "5", "Cannot display value"]);
- verifyIOParameter("input_long_array", null, null, ["10", "20", "Cannot display value"]);
- verifyIOParameter("input_float_array", null, null, ["10.2", "10.4", "10.6", "Cannot display value"]);
- verifyIOParameter("input_double_array", null, null, ["20.1", "20.2", "20.3", "Cannot display value"]);
- verifyIOParameter("input_string_array", null, null, ["Hello", "World", "!", "Cannot display value"]);
- verifyIOParameter("input_bool_include", null, null, "Cannot display value");
- verifyIOParameter("input_int_include", null, null, "Cannot display value");
- verifyIOParameter("input_float_include", null, null, "Cannot display value");
- verifyIOParameter("input_string_include", null, null, "Cannot display value");
- verifyIOParameter("input_file_include", null, null, "Cannot display value");
- verifyIOParameter("input_directory_include", null, null, "Cannot display value");
- verifyIOParameter("input_file_url", null, null, "http://example.com/index.html");
- });
- cy.get("[data-cy=process-io-card] h6")
- .contains("Outputs")
- .parents("[data-cy=process-io-card]")
- .within(ctx => {
- cy.get(ctx).scrollIntoView();
- cy.get('[data-cy="io-preview-image-toggle"]').click({ waitForAnimations: false });
- const outPdh = testOutputCollection.portable_data_hash;
- verifyIOParameter("output_file", null, "Label Description", "cat.png", `${outPdh}`);
- verifyIOParameterImage("output_file", `/c=${outPdh}/cat.png`);
- verifyIOParameter("output_file_with_secondary", null, "Doc Description", "main.dat", `${outPdh}`);
- verifyIOParameter("output_file_with_secondary", null, "Doc Description", "secondary.dat", undefined, true);
- verifyIOParameter("output_file_with_secondary", null, "Doc Description", "secondary2.dat", undefined, true);
- verifyIOParameter("output_dir", null, "Doc desc 1, Doc desc 2", "outdir1", `${outPdh}`);
- verifyIOParameter("output_bool", null, null, "true");
- verifyIOParameter("output_int", null, null, "1");
- verifyIOParameter("output_long", null, null, "1");
- verifyIOParameter("output_float", null, null, "100.5");
- verifyIOParameter("output_double", null, null, "100.3");
- verifyIOParameter("output_string", null, null, "Hello output");
- verifyIOParameter("output_file_array", null, null, "output2.tar", `${outPdh}`);
- verifyIOParameter("output_file_array", null, null, "output3.tar", undefined, true);
- verifyIOParameter("output_dir_array", null, null, "outdir2", `${outPdh}`);
- verifyIOParameter("output_dir_array", null, null, "outdir3", undefined, true);
- verifyIOParameter("output_int_array", null, null, ["10", "11", "12"]);
- verifyIOParameter("output_long_array", null, null, ["51", "52"]);
- verifyIOParameter("output_float_array", null, null, ["100.2", "100.4", "100.6"]);
- verifyIOParameter("output_double_array", null, null, ["100.1", "100.2", "100.3"]);
- verifyIOParameter("output_string_array", null, null, ["Hello", "Output", "!"]);
- });
- });
- });
-
- it("displays IO parameters with no value", function () {
- const fakeOutputUUID = "zzzzz-4zz18-abcdefghijklmno";
- const fakeOutputPDH = "11111111111111111111111111111111+99/";
-
- cy.loginAs(activeUser);
-
- // Add output uuid and inputs to container request
- cy.intercept({ method: "GET", url: "**/arvados/v1/container_requests/*" }, req => {
- req.reply(res => {
- res.body.output_uuid = fakeOutputUUID;
- res.body.mounts["/var/lib/cwl/cwl.input.json"] = {
- content: {},
- };
- res.body.mounts["/var/lib/cwl/workflow.json"] = {
- content: {
- $graph: [
- {
- id: "#main",
- inputs: testInputs.map(input => input.definition),
- outputs: testOutputs.map(output => output.definition),
- },
- ],
- },
- };
- cy.readFile('cypress/fixtures/webdav-propfind-outputs.xml').then((data) => {
++ cy.readFile("cypress/fixtures/webdav-propfind-outputs.xml").then(data => {
+ // Stub webdav response, points to output json
- cy.intercept({method: 'PROPFIND', url: '*'}, {
- statusCode: 200,
- body: data.replace(/zzzzz-4zz18-zzzzzzzzzzzzzzz/g, fakeOutputUUID)
- });
++ cy.intercept(
++ { method: "PROPFIND", url: "*" },
++ {
++ statusCode: 200,
++ body: data.replace(/zzzzz-4zz18-zzzzzzzzzzzzzzz/g, fakeOutputUUID),
++ }
++ );
});
- });
-
- // Stub fake output collection
- cy.intercept(
- { method: "GET", url: `**/arvados/v1/collections/${fakeOutputUUID}*` },
- {
- statusCode: 200,
- body: {
- uuid: fakeOutputUUID,
- portable_data_hash: fakeOutputPDH,
- },
- }
- );
- // Stub fake output json
- cy.intercept(
- { method: "GET", url: `**/c%3D${fakeOutputUUID}/cwl.output.json` },
- {
- statusCode: 200,
- body: {},
- }
- );
-
- cy.readFile("cypress/fixtures/webdav-propfind-outputs.xml").then(data => {
- // Stub webdav response, points to output json
- cy.intercept(
- { method: "PROPFIND", url: "*" },
- {
- statusCode: 200,
- body: data.replace(/zzzzz-4zz18-zzzzzzzzzzzzzzz/g, fakeOutputUUID),
- }
- createContainerRequest(
- activeUser,
- 'test_container_request',
- 'arvados/jobs',
- ['echo', 'hello world'],
- false, 'Committed')
- .as('containerRequest');
++ createContainerRequest(activeUser, "test_container_request", "arvados/jobs", ["echo", "hello world"], false, "Committed").as(
++ "containerRequest"
+ );
- });
- createContainerRequest(activeUser, "test_container_request", "arvados/jobs", ["echo", "hello world"], false, "Committed").as(
- "containerRequest"
- );
-
- cy.getAll("@containerRequest").then(function ([containerRequest]) {
- cy.goToPath(`/processes/${containerRequest.uuid}`);
- cy.get("[data-cy=process-io-card] h6")
- .contains("Inputs")
- .parents("[data-cy=process-io-card]")
- .within(() => {
- cy.wait(2000);
- cy.waitForDom();
- cy.get("tbody tr").each(item => {
- cy.wrap(item).contains("No value");
- cy.getAll('@containerRequest').then(function([containerRequest]) {
++ cy.getAll("@containerRequest").then(function ([containerRequest]) {
+ cy.goToPath(`/processes/${containerRequest.uuid}`);
- cy.get('[data-cy=process-io-card] h6').contains('Inputs')
- .parents('[data-cy=process-io-card]').within(() => {
++ cy.get("[data-cy=process-io-card] h6")
++ .contains("Inputs")
++ .parents("[data-cy=process-io-card]")
++ .within(() => {
+ cy.wait(2000);
+ cy.waitForDom();
- cy.get('tbody tr').each((item) => {
- cy.wrap(item).contains('No value');
++ cy.get("tbody tr").each(item => {
++ cy.wrap(item).contains("No value");
+ });
});
- });
- cy.get("[data-cy=process-io-card] h6")
- .contains("Outputs")
- .parents("[data-cy=process-io-card]")
- .within(() => {
- cy.get("tbody tr").each(item => {
- cy.wrap(item).contains("No value");
- cy.get('[data-cy=process-io-card] h6').contains('Outputs')
- .parents('[data-cy=process-io-card]').within(() => {
- cy.get('tbody tr').each((item) => {
- cy.wrap(item).contains('No value');
++ cy.get("[data-cy=process-io-card] h6")
++ .contains("Outputs")
++ .parents("[data-cy=process-io-card]")
++ .within(() => {
++ cy.get("tbody tr").each(item => {
++ cy.wrap(item).contains("No value");
+ });
});
- });
- });
- });
-
- it("allows copying processes", function () {
- const crName = "first_container_request";
- const copiedCrName = "copied_container_request";
- createContainerRequest(activeUser, crName, "arvados/jobs", ["echo", "hello world"], false, "Committed").then(function (containerRequest) {
- cy.loginAs(activeUser);
- cy.goToPath(`/processes/${containerRequest.uuid}`);
- cy.get("[data-cy=process-details]").should("contain", crName);
-
- cy.get("[data-cy=process-details]").find('button[title="More options"]').click();
- cy.get("ul[data-cy=context-menu]").contains("Copy and re-run process").click();
- });
-
- cy.get("[data-cy=form-dialog]").within(() => {
- cy.get("input[name=name]").clear().type(copiedCrName);
- cy.get("[data-cy=projects-tree-home-tree-picker]").click();
- cy.get("[data-cy=form-submit-btn]").click();
- });
-
- cy.get("[data-cy=process-details]").should("contain", copiedCrName);
- cy.get("[data-cy=process-details]").find("button").contains("Run");
- });
-
- const getFakeContainer = fakeContainerUuid => ({
- href: `/containers/${fakeContainerUuid}`,
- kind: "arvados#container",
- etag: "ecfosljpnxfari9a8m7e4yv06",
- uuid: fakeContainerUuid,
- owner_uuid: "zzzzz-tpzed-000000000000000",
- created_at: "2023-02-13T15:55:47.308915000Z",
- modified_by_client_uuid: "zzzzz-ozdt8-q6dzdi1lcc03155",
- modified_by_user_uuid: "zzzzz-tpzed-000000000000000",
- modified_at: "2023-02-15T19:12:45.987086000Z",
- command: [
- "arvados-cwl-runner",
- "--api=containers",
- "--local",
- "--project-uuid=zzzzz-j7d0g-yr18k784zplfeza",
- "/var/lib/cwl/workflow.json#main",
- "/var/lib/cwl/cwl.input.json",
- ],
- container_image: "4ad7d11381df349e464694762db14e04+303",
- cwd: "/var/spool/cwl",
- environment: {},
- exit_code: null,
- finished_at: null,
- locked_by_uuid: null,
- log: null,
- output: null,
- output_path: "/var/spool/cwl",
- progress: null,
- runtime_constraints: {
- API: true,
- cuda: {
- device_count: 0,
- driver_version: "",
- hardware_capability: "",
- },
- keep_cache_disk: 2147483648,
- keep_cache_ram: 0,
- ram: 1342177280,
- vcpus: 1,
- },
- runtime_status: {},
- started_at: null,
- auth_uuid: null,
- scheduling_parameters: {
- max_run_time: 0,
- partitions: [],
- preemptible: false,
- },
- runtime_user_uuid: "zzzzz-tpzed-vllbpebicy84rd5",
- runtime_auth_scopes: ["all"],
- lock_count: 2,
- gateway_address: null,
- interactive_session_started: false,
- output_storage_classes: ["default"],
- output_properties: {},
- cost: 0.0,
- subrequests_cost: 0.0,
- });
-
- it("shows cancel button when appropriate", function () {
- // Ignore collection requests
- cy.intercept(
- { method: "GET", url: `**/arvados/v1/collections/*` },
- {
- statusCode: 200,
- body: {},
- }
- );
-
- // Uncommitted container
- const crUncommitted = `Test process ${Math.floor(Math.random() * 999999)}`;
- createContainerRequest(activeUser, crUncommitted, "arvados/jobs", ["echo", "hello world"], false, "Uncommitted").then(function (
- containerRequest
- ) {
- // Navigate to process and verify run / cancel button
- cy.goToPath(`/processes/${containerRequest.uuid}`);
- cy.waitForDom();
- cy.get("[data-cy=process-details]").should("contain", crUncommitted);
- cy.get("[data-cy=process-run-button]").should("exist");
- cy.get("[data-cy=process-cancel-button]").should("not.exist");
- });
-
- // Queued container
- const crQueued = `Test process ${Math.floor(Math.random() * 999999)}`;
- const fakeCrUuid = "zzzzz-dz642-000000000000001";
- createContainerRequest(activeUser, crQueued, "arvados/jobs", ["echo", "hello world"], false, "Committed").then(function (containerRequest) {
- // Fake container uuid
- cy.intercept({ method: "GET", url: `**/arvados/v1/container_requests/${containerRequest.uuid}` }, req => {
- req.reply(res => {
- res.body.output_uuid = fakeCrUuid;
- res.body.priority = 500;
- res.body.state = "Committed";
- });
});
-
- // Fake container
- const container = getFakeContainer(fakeCrUuid);
- cy.intercept(
- { method: "GET", url: `**/arvados/v1/container/${fakeCrUuid}` },
- {
- statusCode: 200,
- body: { ...container, state: "Queued", priority: 500 },
- }
- );
-
- // Navigate to process and verify cancel button
- cy.goToPath(`/processes/${containerRequest.uuid}`);
- cy.waitForDom();
- cy.get("[data-cy=process-details]").should("contain", crQueued);
- cy.get("[data-cy=process-cancel-button]").contains("Cancel");
- });
-
- // Locked container
- const crLocked = `Test process ${Math.floor(Math.random() * 999999)}`;
- const fakeCrLockedUuid = "zzzzz-dz642-000000000000002";
- createContainerRequest(activeUser, crLocked, "arvados/jobs", ["echo", "hello world"], false, "Committed").then(function (containerRequest) {
- // Fake container uuid
- cy.intercept({ method: "GET", url: `**/arvados/v1/container_requests/${containerRequest.uuid}` }, req => {
- req.reply(res => {
- res.body.output_uuid = fakeCrLockedUuid;
- res.body.priority = 500;
- res.body.state = "Committed";
- });
- });
-
- // Fake container
- const container = getFakeContainer(fakeCrLockedUuid);
- cy.intercept(
- { method: "GET", url: `**/arvados/v1/container/${fakeCrLockedUuid}` },
- {
- statusCode: 200,
- body: { ...container, state: "Locked", priority: 500 },
- }
- );
-
- // Navigate to process and verify cancel button
- cy.goToPath(`/processes/${containerRequest.uuid}`);
- cy.waitForDom();
- cy.get("[data-cy=process-details]").should("contain", crLocked);
- cy.get("[data-cy=process-cancel-button]").contains("Cancel");
- });
-
- // On Hold container
- const crOnHold = `Test process ${Math.floor(Math.random() * 999999)}`;
- const fakeCrOnHoldUuid = "zzzzz-dz642-000000000000003";
- createContainerRequest(activeUser, crOnHold, "arvados/jobs", ["echo", "hello world"], false, "Committed").then(function (containerRequest) {
- // Fake container uuid
- cy.intercept({ method: "GET", url: `**/arvados/v1/container_requests/${containerRequest.uuid}` }, req => {
- req.reply(res => {
- res.body.output_uuid = fakeCrOnHoldUuid;
- res.body.priority = 0;
- res.body.state = "Committed";
- });
- });
-
- // Fake container
- const container = getFakeContainer(fakeCrOnHoldUuid);
- cy.intercept(
- { method: "GET", url: `**/arvados/v1/container/${fakeCrOnHoldUuid}` },
- {
- statusCode: 200,
- body: { ...container, state: "Queued", priority: 0 },
- }
- );
-
- // Navigate to process and verify cancel button
- cy.goToPath(`/processes/${containerRequest.uuid}`);
- cy.waitForDom();
- cy.get("[data-cy=process-details]").should("contain", crOnHold);
- cy.get("[data-cy=process-run-button]").should("exist");
- cy.get("[data-cy=process-cancel-button]").should("not.exist");
});
});
-
});
cy.loginAs(activeUser);
// Create project
- cy.get('[data-cy=side-panel-button]').click();
- cy.get('[data-cy=side-panel-new-project]').click();
- cy.get('[data-cy=form-dialog]')
- .should('contain', 'New Project')
+ cy.get("[data-cy=side-panel-button]").click();
+ cy.get("[data-cy=side-panel-new-project]").click();
+ cy.get("[data-cy=form-dialog]")
+ .should("contain", "New Project")
.within(() => {
- cy.get('[data-cy=name-field]').within(() => {
- cy.get('input').type(projName);
+ cy.get("[data-cy=name-field]").within(() => {
+ cy.get("input").type(projName);
});
});
- cy.get('[data-cy=form-submit-btn]').click();
- cy.get('[data-cy=form-dialog]').should('not.exist');
+ cy.get("[data-cy=form-submit-btn]").click();
++ cy.get("[data-cy=form-dialog]").should("not.exist");
const editProjectDescription = (name, type) => {
- cy.get('[data-cy=side-panel-tree]').contains('Home Projects').click();
- cy.get('[data-cy=project-panel] tbody tr').contains(name).rightclick();
- cy.get('[data-cy=context-menu]').contains('Edit').click();
- cy.get('[data-cy=form-dialog]').within(() => {
- cy.get('div[contenteditable=true]')
- .click()
- .type(type);
- cy.get('[data-cy=form-submit-btn]').click();
+ cy.get("[data-cy=side-panel-tree]").contains("Home Projects").click();
+ cy.get("[data-cy=project-panel] tbody tr").contains(name).rightclick({ force: true });
+ cy.get("[data-cy=context-menu]").contains("Edit").click();
+ cy.get("[data-cy=form-dialog]").within(() => {
+ cy.get("div[contenteditable=true]").click().type(type);
+ cy.get("[data-cy=form-submit-btn]").click();
});
};
const projectName = `Test project (${Math.floor(999999 * Math.random())})`;
cy.loginAs(activeUser);
- cy.get('[data-cy=side-panel-button]').click();
- cy.get('[data-cy=side-panel-new-project]').click();
- cy.get('[data-cy=form-dialog]')
- .should('contain', 'New Project')
+ cy.get("[data-cy=side-panel-button]").click();
+ cy.get("[data-cy=side-panel-new-project]").click();
+ cy.get("[data-cy=form-dialog]")
+ .should("contain", "New Project")
.within(() => {
- cy.get('[data-cy=name-field]').within(() => {
- cy.get('input').type(projectName);
+ cy.get("[data-cy=name-field]").within(() => {
+ cy.get("input").type(projectName);
});
- cy.get('[data-cy=form-submit-btn]').click();
+ cy.get("[data-cy=form-submit-btn]").click();
});
- cy.get('[data-cy=form-dialog]').should("not.exist");
- cy.get('[data-cy=snackbar]').contains('created');
- cy.get('[data-cy=snackbar]').should("not.exist");
- cy.get('[data-cy=side-panel-tree]').contains('Projects').click();
+ cy.get("[data-cy=form-dialog]").should("not.exist");
++ cy.get("[data-cy=snackbar]").contains("created");
++ cy.get("[data-cy=snackbar]").should("not.exist");
+ cy.get("[data-cy=side-panel-tree]").contains("Projects").click();
+ cy.waitForDom();
- cy.get('[data-cy=project-panel]').contains(projectName).should('be.visible').rightclick();
- cy.get('[data-cy=context-menu]').contains('Copy to clipboard').click();
- cy.window().then((win) => (
- win.navigator.clipboard.readText().then((text) => {
- expect(text).to.match(/https\:\/\/127\.0\.0\.1\:[0-9]+\/projects\/[a-z0-9]{5}-[a-z0-9]{5}-[a-z0-9]{15}/,);
+ cy.get("[data-cy=project-panel]").contains(projectName).should("be.visible").rightclick();
+ cy.get("[data-cy=context-menu]").contains("Copy to clipboard").click();
+ cy.window().then(win =>
+ win.navigator.clipboard.readText().then(text => {
- expect(text).to.match(/https\:\/\/localhost\:[0-9]+\/projects\/[a-z0-9]{5}-[a-z0-9]{5}-[a-z0-9]{15}/);
++ expect(text).to.match(/https\:\/\/127\.0\.0\.1\:[0-9]+\/projects\/[a-z0-9]{5}-[a-z0-9]{5}-[a-z0-9]{15}/);
})
- ));
-
+ );
});
- it('sorts displayed items correctly', () => {
+ it("sorts displayed items correctly", () => {
cy.loginAs(activeUser);
cy.get('[data-cy=project-panel] button[title="Select columns"]').click();
});
});
- it('shows search context menu', function() {
+ it("shows search context menu", function () {
- const colName = `Collection ${Math.floor(Math.random() * Math.floor(999999))}`;
- const federatedColName = `Collection ${Math.floor(Math.random() * Math.floor(999999))}`;
+ const colName = `Home Collection ${Math.floor(Math.random() * Math.floor(999999))}`;
+ const federatedColName = `Federated Collection ${Math.floor(Math.random() * Math.floor(999999))}`;
const federatedColUuid = "xxxxx-4zz18-000000000000000";
// Intercept config to insert remote cluster
- cy.intercept({ method: "GET", hostname: "localhost", url: "**/arvados/v1/config?nocache=*" }, req => {
- cy.intercept({method: 'GET', hostname: '127.0.0.1', url: '**/arvados/v1/config?nocache=*'}, (req) => {
- req.reply((res) => {
++ cy.intercept({ method: "GET", hostname: "127.0.0.1", url: "**/arvados/v1/config?nocache=*" }, req => {
+ req.reply(res => {
res.body.RemoteClusters = {
"*": res.body.RemoteClusters["*"],
- "xxxxx": {
- "ActivateUsers": true,
- "Host": "xxxxx.fakecluster.tld",
- "Insecure": false,
- "Proxy": true,
- "Scheme": ""
- }
+ xxxxx: {
+ ActivateUsers: true,
+ Host: "xxxxx.fakecluster.tld",
+ Insecure: false,
+ Proxy: true,
+ Scheme: "",
+ },
};
});
});
});
// Check copy to clipboard
- cy.get('[data-cy=search-results]').contains(colName).rightclick();
- cy.get('[data-cy=context-menu]').within((ctx) => {
+ cy.get("[data-cy=search-results]").contains(colName).rightclick();
+ cy.get("[data-cy=context-menu]").within(ctx => {
// Check that there are 4 items in the menu
- cy.get(ctx).children().should('have.length', 4);
- cy.contains('API Details');
- cy.contains('Copy to clipboard');
- cy.contains('Open in new tab');
- cy.contains('View details');
+ cy.get(ctx).children().should("have.length", 4);
+ cy.contains("API Details");
+ cy.contains("Copy to clipboard");
+ cy.contains("Open in new tab");
+ cy.contains("View details");
- cy.contains('Copy to clipboard').click();
+ cy.contains("Copy to clipboard").click();
+ cy.waitForDom();
- cy.window().then((win) => (
- win.navigator.clipboard.readText().then((text) => {
+ cy.window().then(win =>
+ win.navigator.clipboard.readText().then(text => {
expect(text).to.match(new RegExp(`/collections/${testCollection.uuid}$`));
})
- ));
+ );
});
// Check open in new tab
- cy.get('[data-cy=search-results]').contains(colName).rightclick();
- cy.get('[data-cy=context-menu]').within(() => {
- cy.contains('Open in new tab').click();
+ cy.get("[data-cy=search-results]").contains(colName).rightclick();
+ cy.get("[data-cy=context-menu]").within(() => {
+ cy.contains("Open in new tab").click();
+ cy.waitForDom();
- cy.get('@Open').should('have.been.calledOnceWith', `${window.location.origin}/collections/${testCollection.uuid}`)
+ cy.get("@Open").should("have.been.calledOnceWith", `${window.location.origin}/collections/${testCollection.uuid}`);
});
// Check federated result copy to clipboard
- cy.get('[data-cy=search-results]').contains(federatedColName).rightclick();
- cy.get('[data-cy=context-menu]').within(() => {
- cy.contains('Copy to clipboard').click();
+ cy.get("[data-cy=search-results]").contains(federatedColName).rightclick();
+ cy.get("[data-cy=context-menu]").within(() => {
+ cy.contains("Copy to clipboard").click();
+ cy.waitForDom();
- cy.window().then((win) => (
- win.navigator.clipboard.readText().then((text) => {
+ cy.window().then(win =>
+ win.navigator.clipboard.readText().then(text => {
expect(text).to.equal(`https://wb2.xxxxx.fakecluster.tld/collections/${federatedColUuid}`);
})
- ));
+ );
});
// Check open in new tab
- cy.get('[data-cy=search-results]').contains(federatedColName).rightclick();
- cy.get('[data-cy=context-menu]').within(() => {
- cy.contains('Open in new tab').click();
+ cy.get("[data-cy=search-results]").contains(federatedColName).rightclick();
+ cy.get("[data-cy=context-menu]").within(() => {
+ cy.contains("Open in new tab").click();
+ cy.waitForDom();
- cy.get('@Open').should('have.been.calledWith', `https://wb2.xxxxx.fakecluster.tld/collections/${federatedColUuid}`)
+ cy.get("@Open").should("have.been.calledWith", `https://wb2.xxxxx.fakecluster.tld/collections/${federatedColUuid}`);
});
-
});
});
});
.within(() => {
cy.get('button[title="Add Login Permission"]').click();
});
- cy.get('[data-cy=form-dialog]')
- .should('contain', 'Add login permission')
+ cy.get("[data-cy=form-dialog]")
+ .should("contain", "Add login permission")
.within(() => {
- cy.get('label')
- .contains('Search for user')
- .parent()
- .within(() => {
- cy.get('input').type('VMAdmin');
- })
+ cy.get("label")
+ .contains("Search for user")
+ .parent()
+ .within(() => {
+ cy.get("input").type("VMAdmin");
+ });
});
- cy.get("[role=tooltip]").click();
- cy.waitForDom().get('[role=tooltip]').click();
- cy.get('[data-cy=form-dialog]').as('add-login-dialog')
- .should('contain', 'Add login permission')
++ cy.waitForDom().get("[role=tooltip]").click();
+ cy.get("[data-cy=form-dialog]")
+ .as("add-login-dialog")
+ .should("contain", "Add login permission")
.within(() => {
- cy.get('label')
- .contains('Add groups')
- .parent()
- .within(() => {
- cy.get('input').type('docker ');
- // Veryfy submit enabled (form has changed)
- cy.get('@add-login-dialog').within(() => {
- cy.get('[data-cy=form-submit-btn]').should('be.enabled');
- });
- cy.get('input').type('sudo');
- // Veryfy submit disabled (partial input in chips)
- cy.get('@add-login-dialog').within(() => {
- cy.get('[data-cy=form-submit-btn]').should('be.disabled');
+ cy.get("label")
+ .contains("Add groups")
+ .parent()
+ .within(() => {
+ cy.get("input").type("docker ");
+ // Veryfy submit enabled (form has changed)
+ cy.get("@add-login-dialog").within(() => {
+ cy.get("[data-cy=form-submit-btn]").should("be.enabled");
+ });
+ cy.get("input").type("sudo");
+ // Veryfy submit disabled (partial input in chips)
+ cy.get("@add-login-dialog").within(() => {
+ cy.get("[data-cy=form-submit-btn]").should("be.disabled");
+ });
+ cy.get("input").type("{enter}");
});
- cy.get('input').type('{enter}');
- })
});
- cy.get('[data-cy=form-dialog]').within(() => {
- cy.get('[data-cy=form-submit-btn]').click();
+ cy.get("[data-cy=form-dialog]").within(() => {
+ cy.get("[data-cy=form-submit-btn]").click();
});
- cy.get('[data-cy=vm-admin-table]')
+ cy.get("[data-cy=vm-admin-table]")
.contains(vmHost)
- .parents('tr')
+ .parents("tr")
.within(() => {
- cy.get('td').contains('admin');
- });
+ cy.get("td").contains("admin");
+ });
// Add login permission to activeUser
- cy.get('[data-cy=vm-admin-table]')
+ cy.get("[data-cy=vm-admin-table]")
.contains(vmHost)
- .parents('tr')
+ .parents("tr")
.within(() => {
cy.get('button[title="Add Login Permission"]').click();
});
// Remove login permissions
cy.loginAs(adminUser);
cy.get('header button[title="Admin Panel"]').click();
- cy.get('#admin-menu').contains('Virtual Machines').click();
+ cy.get("#admin-menu").contains("Virtual Machines").click();
- cy.get('[data-cy=vm-admin-table]')
- .contains('user'); // Wait for page to finish
+ cy.get("[data-cy=vm-admin-table]").contains("user"); // Wait for page to finish
- cy.get('[data-cy=vm-admin-table]')
+ cy.get("[data-cy=vm-admin-table]")
.contains(vmHost)
- .parents('tr')
- .as('vmRow')
- .contains('user')
- .parents('[role=button]')
- .find('svg')
- .as('removeButton');
- cy.get('@removeButton').click();
- cy.get('[data-cy=confirmation-dialog-ok-btn]').click();
-
- cy.get('@vmRow')
- .within(() => {
- cy.get('div[role=button]').should('not.contain', 'user');
- cy.get('div[role=button]').should('have.length', 1)
- });
+ .parents("tr")
+ .as("vmRow")
+ .contains("user")
+ .parents("[role=button]")
+ .find("svg")
+ .as("removeButton");
+ cy.get("@removeButton").click();
+ cy.get("[data-cy=confirmation-dialog-ok-btn]").click();
+
+ cy.get("@vmRow").within(() => {
+ cy.get("div[role=button]").should("not.contain", "user");
+ cy.get("div[role=button]").should("have.length", 1);
+ });
- cy.get('@vmRow')
- .find('div[role=button]')
- .contains('admin')
- .parents('[role=button]')
- .find('svg')
- .as('removeButton');
- cy.get('@removeButton').click();
- cy.get('[data-cy=confirmation-dialog-ok-btn]').click();
+ cy.get("@vmRow").find("div[role=button]").contains("admin").parents("[role=button]").find("svg").as("removeButton");
+ cy.get("@removeButton").click();
+ cy.get("[data-cy=confirmation-dialog-ok-btn]").click();
- cy.get("[data-cy=vm-admin-table]")
- cy.waitForDom().get('[data-cy=vm-admin-table]')
++ cy.waitForDom()
++ .get("[data-cy=vm-admin-table]")
.contains(vmHost)
- .parents('tr')
+ .parents("tr")
.within(() => {
- cy.get('div[role=button]').should('not.exist');
+ cy.get("div[role=button]").should("not.exist");
});
// Check admin's vm page for login
// -- This will overwrite an existing command --
// Cypress.Commands.overwrite("visit", (originalFn, url, options) => { ... })
-const controllerURL = Cypress.env('controller_url');
-const systemToken = Cypress.env('system_token');
+ import { extractFilesData } from "services/collection-service/collection-service-files-response";
+
+const controllerURL = Cypress.env("controller_url");
+const systemToken = Cypress.env("system_token");
let createdResources = [];
- // Clean up on a 'before' hook to allow post-mortem analysis on individual tests.
- beforeEach(function () {
-const containerLogFolderPrefix = 'log for container ';
++const containerLogFolderPrefix = "log for container ";
+
+ // Clean up anything that was created. You can temporarily add
+ // 'return' to the top if you need the resources to hang around to
+ // debug a specific test.
+ afterEach(function () {
if (createdResources.length === 0) {
return;
}
- cy.log(`Cleaning ${createdResources.length} previously created resource(s)`);
+ cy.log(`Cleaning ${createdResources.length} previously created resource(s).`);
- createdResources.forEach(function({suffix, uuid}) {
+ createdResources.forEach(function ({ suffix, uuid }) {
// Don't fail when a resource isn't already there, some objects may have
// been removed, directly or indirectly, from the test that created them.
cy.deleteResource(systemToken, suffix, uuid, false);
qs: auth ? qs : Object.assign({ api_token: token }, qs),
auth: auth ? { bearer: `${token}` } : undefined,
followRedirect: followRedirect,
- failOnStatusCode: failOnStatusCode
+ failOnStatusCode: failOnStatusCode,
});
- });
-});
+ }
+);
-Cypress.Commands.add(
- "getUser", (username, first_name = '', last_name = '', is_admin = false, is_active = true) => {
- // Create user if not already created
- return cy.doRequest('POST', '/auth/controller/callback', {
- auth_info: JSON.stringify({
- email: `${username}@example.local`,
- username: username,
- first_name: first_name,
- last_name: last_name,
- alternate_emails: []
- }),
- return_to: ',https://example.local'
- }, null, systemToken, true, false) // Don't follow redirects so we can catch the token
- .its('headers.location').as('location')
- // Get its token and set the account up as admin and/or active
- .then(function () {
- this.userToken = this.location.split("=")[1]
- assert.isString(this.userToken)
- return cy.doRequest('GET', '/arvados/v1/users', null, {
- filters: `[["username", "=", "${username}"]]`
- })
- .its('body.items.0').as('aUser')
+Cypress.Commands.add("getUser", (username, first_name = "", last_name = "", is_admin = false, is_active = true) => {
+ // Create user if not already created
+ return (
+ cy
+ .doRequest(
+ "POST",
+ "/auth/controller/callback",
+ {
+ auth_info: JSON.stringify({
+ email: `${username}@example.local`,
+ username: username,
+ first_name: first_name,
+ last_name: last_name,
+ alternate_emails: [],
+ }),
+ return_to: ",https://example.local",
+ },
+ null,
+ systemToken,
+ true,
+ false
+ ) // Don't follow redirects so we can catch the token
+ .its("headers.location")
+ .as("location")
+ // Get its token and set the account up as admin and/or active
.then(function () {
- cy.doRequest('PUT', `/arvados/v1/users/${this.aUser.uuid}`, {
- user: {
- is_admin: is_admin,
- is_active: is_active
- }
- })
- .its('body').as('theUser')
- .then(function () {
- cy.doRequest('GET', '/arvados/v1/api_clients', null, {
- filters: `[["is_trusted", "=", false]]`,
- order: `["created_at desc"]`
+ this.userToken = this.location.split("=")[1];
+ assert.isString(this.userToken);
+ return cy
+ .doRequest("GET", "/arvados/v1/users", null, {
+ filters: `[["username", "=", "${username}"]]`,
})
- .its('body.items').as('apiClients')
+ .its("body.items.0")
+ .as("aUser")
.then(function () {
- if (this.apiClients.length > 0) {
- cy.doRequest('PUT', `/arvados/v1/api_clients/${this.apiClients[0].uuid}`, {
- api_client: {
- is_trusted: true
- }
- })
- .its('body').as('updatedApiClient')
- .then(function() {
- assert(this.updatedApiClient.is_trusted);
- })
- }
- })
- .then(function () {
- return { user: this.theUser, token: this.userToken };
- })
- })
+ cy.doRequest("PUT", `/arvados/v1/users/${this.aUser.uuid}`, {
+ user: {
+ is_admin: is_admin,
+ is_active: is_active,
+ },
+ })
+ .its("body")
+ .as("theUser")
+ .then(function () {
+ cy.doRequest("GET", "/arvados/v1/api_clients", null, {
+ filters: `[["is_trusted", "=", false]]`,
+ order: `["created_at desc"]`,
+ })
+ .its("body.items")
+ .as("apiClients")
+ .then(function () {
+ if (this.apiClients.length > 0) {
+ cy.doRequest("PUT", `/arvados/v1/api_clients/${this.apiClients[0].uuid}`, {
+ api_client: {
+ is_trusted: true,
+ },
+ })
+ .its("body")
+ .as("updatedApiClient")
+ .then(function () {
+ assert(this.updatedApiClient.is_trusted);
+ });
+ }
+ })
+ .then(function () {
+ return { user: this.theUser, token: this.userToken };
+ });
+ });
+ });
})
- })
- }
-)
+ );
+});
-Cypress.Commands.add(
- "createLink", (token, data) => {
- return cy.createResource(token, 'links', {
- link: JSON.stringify(data)
- })
- }
-)
+Cypress.Commands.add("createLink", (token, data) => {
+ return cy.createResource(token, "links", {
+ link: JSON.stringify(data),
+ });
+});
- Cypress.Commands.add("createGroup", (token, data) => {
- return cy.createResource(token, "groups", {
- group: JSON.stringify(data),
- ensure_unique_name: true,
- });
- });
-
- Cypress.Commands.add("trashGroup", (token, uuid) => {
- return cy.deleteResource(token, "groups", uuid);
- });
-
- Cypress.Commands.add("createWorkflow", (token, data) => {
- return cy.createResource(token, "workflows", {
- workflow: JSON.stringify(data),
- ensure_unique_name: true,
- });
- });
-
- Cypress.Commands.add("getCollection", (token, uuid) => {
- return cy.getResource(token, "collections", uuid);
- });
-
- Cypress.Commands.add("createCollection", (token, data) => {
- return cy.createResource(token, "collections", {
- collection: JSON.stringify(data),
- ensure_unique_name: true,
- });
- });
-
- Cypress.Commands.add("updateCollection", (token, uuid, data) => {
- return cy.updateResource(token, "collections", uuid, {
- collection: JSON.stringify(data),
- });
- });
-
- Cypress.Commands.add("getContainer", (token, uuid) => {
- return cy.getResource(token, "containers", uuid);
- });
-
- Cypress.Commands.add("updateContainer", (token, uuid, data) => {
- return cy.updateResource(token, "containers", uuid, {
- container: JSON.stringify(data),
- });
- });
-
- Cypress.Commands.add("createContainerRequest", (token, data) => {
- return cy.createResource(token, "container_requests", {
- container_request: JSON.stringify(data),
- ensure_unique_name: true,
- });
- });
-
- Cypress.Commands.add("updateContainerRequest", (token, uuid, data) => {
- return cy.updateResource(token, "container_requests", uuid, {
- container_request: JSON.stringify(data),
- });
- });
-
- Cypress.Commands.add("createLog", (token, data) => {
- return cy.createResource(token, "logs", {
- log: JSON.stringify(data),
- });
- });
+ Cypress.Commands.add(
- "createGroup", (token, data) => {
- return cy.createResource(token, 'groups', {
- group: JSON.stringify(data),
- ensure_unique_name: true
- })
++ "doWebDAVRequest",
++ (method = "GET", path = "", data = null, qs = null, token = systemToken, auth = false, followRedirect = true, failOnStatusCode = true) => {
++ return cy.doRequest("GET", "/arvados/v1/config", null, null).then(({ body: config }) => {
++ return cy.request({
++ method: method,
++ url: `${config.Services.WebDAVDownload.ExternalURL.replace(/\/+$/, "")}/${path.replace(/^\/+/, "")}`,
++ body: data,
++ qs: auth ? qs : Object.assign({ api_token: token }, qs),
++ auth: auth ? { bearer: `${token}` } : undefined,
++ followRedirect: followRedirect,
++ failOnStatusCode: failOnStatusCode,
++ });
++ });
+ }
-)
++);
- Cypress.Commands.add("logsForContainer", (token, uuid, logType, logTextArray = []) => {
- let logs = [];
- for (const logText of logTextArray) {
- logs.push(
- cy
- .createLog(token, {
- object_uuid: uuid,
- event_type: logType,
- properties: {
- text: logText,
- },
-Cypress.Commands.add(
- "trashGroup", (token, uuid) => {
- return cy.deleteResource(token, 'groups', uuid);
- }
-)
++Cypress.Commands.add("getUser", (username, first_name = "", last_name = "", is_admin = false, is_active = true) => {
++ // Create user if not already created
++ return (
++ cy
++ .doRequest(
++ "POST",
++ "/auth/controller/callback",
++ {
++ auth_info: JSON.stringify({
++ email: `${username}@example.local`,
++ username: username,
++ first_name: first_name,
++ last_name: last_name,
++ alternate_emails: [],
++ }),
++ return_to: ",https://example.local",
++ },
++ null,
++ systemToken,
++ true,
++ false
++ ) // Don't follow redirects so we can catch the token
++ .its("headers.location")
++ .as("location")
++ // Get its token and set the account up as admin and/or active
++ .then(
++ function () {
++ this.userToken = this.location.split("=")[1];
++ assert.isString(this.userToken);
++ return cy
++ .doRequest("GET", "/arvados/v1/users", null, {
++ filters: `[["username", "=", "${username}"]]`,
++ })
++ .its("body.items.0")
++ .as("aUser")
++ .then(function () {
++ cy.doRequest("PUT", `/arvados/v1/users/${this.aUser.uuid}`, {
++ user: {
++ is_admin: is_admin,
++ is_active: is_active,
++ },
++ }).as("lastLogRecord");
++ });
++ },
++ cy.getAll("@lastLogRecord").then(function () {
++ return logs;
+ })
- .as("lastLogRecord")
- );
- }
- cy.getAll("@lastLogRecord").then(function () {
- return logs;
- });
++ )
++ );
+});
+Cypress.Commands.add("createVirtualMachine", (token, data) => {
+ return cy.createResource(token, "virtual_machines", {
+ virtual_machine: JSON.stringify(data),
+ ensure_unique_name: true,
+ });
+});
-Cypress.Commands.add(
- "createWorkflow", (token, data) => {
- return cy.createResource(token, 'workflows', {
- workflow: JSON.stringify(data),
- ensure_unique_name: true
- })
- }
-)
+Cypress.Commands.add("getResource", (token, suffix, uuid) => {
+ return cy
+ .doRequest("GET", `/arvados/v1/${suffix}/${uuid}`, null, {}, token)
+ .its("body")
+ .then(function (resource) {
+ return resource;
+ });
+});
-Cypress.Commands.add(
- "createCollection", (token, data) => {
- return cy.createResource(token, 'collections', {
- collection: JSON.stringify(data),
- ensure_unique_name: true
- })
- }
-)
+Cypress.Commands.add("createResource", (token, suffix, data) => {
+ return cy
+ .doRequest("POST", "/arvados/v1/" + suffix, data, null, token, true)
+ .its("body")
+ .then(function (resource) {
+ createdResources.push({ suffix, uuid: resource.uuid });
+ return resource;
+ });
+});
-Cypress.Commands.add(
- "getCollection", (token, uuid) => {
- return cy.getResource(token, 'collections', uuid)
- }
-)
+Cypress.Commands.add("deleteResource", (token, suffix, uuid, failOnStatusCode = true) => {
+ return cy
+ .doRequest("DELETE", "/arvados/v1/" + suffix + "/" + uuid, null, null, token, false, true, failOnStatusCode)
+ .its("body")
+ .then(function (resource) {
+ return resource;
+ });
+});
-Cypress.Commands.add(
- "updateCollection", (token, uuid, data) => {
- return cy.updateResource(token, 'collections', uuid, {
- collection: JSON.stringify(data)
- })
- }
-)
+Cypress.Commands.add("updateResource", (token, suffix, uuid, data) => {
+ return cy
+ .doRequest("PATCH", "/arvados/v1/" + suffix + "/" + uuid, data, null, token, true)
+ .its("body")
+ .then(function (resource) {
+ return resource;
+ });
+});
- Cypress.Commands.add("loginAs", user => {
- cy.clearCookies();
- cy.clearLocalStorage();
- cy.visit(`/token/?api_token=${user.token}`);
- cy.url({ timeout: 10000 }).should("contain", "/projects/");
- cy.get("div#root").should("contain", "Arvados Workbench (zzzzz)");
- cy.get("div#root").should("not.contain", "Your account is inactive");
-Cypress.Commands.add(
- "collectionReplaceFiles", (token, uuid, data) => {
- return cy.updateResource(token, 'collections', uuid, {
- collection: {
- preserve_version: true,
- },
- replace_files: JSON.stringify(data)
- })
- }
-)
++Cypress.Commands.add("createCollection", (token, data) => {
++ return cy.createResource(token, "collections", {
++ collection: JSON.stringify(data),
++ ensure_unique_name: true,
++ });
++});
+
-Cypress.Commands.add(
- "getContainer", (token, uuid) => {
- return cy.getResource(token, 'containers', uuid)
- }
-)
++Cypress.Commands.add("getCollection", (token, uuid) => {
++ return cy.getResource(token, "collections", uuid);
++});
+
-Cypress.Commands.add(
- "updateContainer", (token, uuid, data) => {
- return cy.updateResource(token, 'containers', uuid, {
- container: JSON.stringify(data)
- })
- }
-)
++Cypress.Commands.add("updateCollection", (token, uuid, data) => {
++ return cy.updateResource(token, "collections", uuid, {
++ collection: JSON.stringify(data),
++ });
++});
+
-Cypress.Commands.add(
- "getContainerRequest", (token, uuid) => {
- return cy.getResource(token, 'container_requests', uuid)
- }
-)
++Cypress.Commands.add("collectionReplaceFiles", (token, uuid, data) => {
++ return cy.updateResource(token, "collections", uuid, {
++ collection: {
++ preserve_version: true,
++ },
++ replace_files: JSON.stringify(data),
++ });
+});
- Cypress.Commands.add("testEditProjectOrCollection", (container, oldName, newName, newDescription, isProject = true) => {
- cy.get(container).contains(oldName).rightclick();
- cy.get("[data-cy=context-menu]")
- .contains(isProject ? "Edit project" : "Edit collection")
- .click();
- cy.get("[data-cy=form-dialog]").within(() => {
- cy.get("input[name=name]").clear().type(newName);
- cy.get(isProject ? "div[contenteditable=true]" : "input[name=description]")
- .clear()
- .type(newDescription);
- cy.get("[data-cy=form-submit-btn]").click();
-Cypress.Commands.add(
- 'createContainerRequest', (token, data) => {
- return cy.createResource(token, 'container_requests', {
- container_request: JSON.stringify(data),
- ensure_unique_name: true
- })
- }
-)
++Cypress.Commands.add("getContainer", (token, uuid) => {
++ return cy.getResource(token, "containers", uuid);
++});
+
-Cypress.Commands.add(
- "updateContainerRequest", (token, uuid, data) => {
- return cy.updateResource(token, 'container_requests', uuid, {
- container_request: JSON.stringify(data)
- })
- }
-)
++Cypress.Commands.add("updateContainer", (token, uuid, data) => {
++ return cy.updateResource(token, "containers", uuid, {
++ container: JSON.stringify(data),
+ });
++});
+
- cy.get(container).contains(newName).rightclick();
- cy.get("[data-cy=context-menu]")
- .contains(isProject ? "Edit project" : "Edit collection")
- .click();
- cy.get("[data-cy=form-dialog]").within(() => {
- cy.get("input[name=name]").should("have.value", newName);
++Cypress.Commands.add("getContainerRequest", (token, uuid) => {
++ return cy.getResource(token, "container_requests", uuid);
++});
+
- if (isProject) {
- cy.get("span[data-text=true]").contains(newDescription);
- } else {
- cy.get("input[name=description]").should("have.value", newDescription);
- }
++Cypress.Commands.add("createContainerRequest", (token, data) => {
++ return cy.createResource(token, "container_requests", {
++ container_request: JSON.stringify(data),
++ ensure_unique_name: true,
++ });
++});
+
- cy.get("[data-cy=form-cancel-btn]").click();
++Cypress.Commands.add("updateContainerRequest", (token, uuid, data) => {
++ return cy.updateResource(token, "container_requests", uuid, {
++ container_request: JSON.stringify(data),
+ });
+});
-Cypress.Commands.add(
- "appendLog", (token, crUuid, fileName, lines = []) => (
- cy.getContainerRequest(token, crUuid).then((containerRequest) => {
- if (containerRequest.log_uuid) {
- cy.listContainerRequestLogs(token, crUuid).then((logFiles) => {
- const filePath = `${containerRequest.log_uuid}/${containerLogFolderPrefix}${containerRequest.container_uuid}/${fileName}`;
- if (logFiles.find((file) => (file.name === fileName))) {
- // File exists, fetch and append
- return cy.doWebDAVRequest(
- "GET",
- `c=${filePath}`,
- null,
- null,
- token
- )
- .then(({ body: contents }) => cy.doWebDAVRequest(
- "PUT",
- `c=${filePath}`,
- contents.split("\n").concat(lines).join("\n"),
- null,
- token
- ));
- } else {
- // File not exists, put new file
- cy.doWebDAVRequest(
- "PUT",
- `c=${filePath}`,
- lines.join("\n"),
- null,
- token
- )
- }
- });
- } else {
- // Create log collection
- return cy.createCollection(token, {
+ /**
+ * Requires an admin token for log_uuid modification to succeed
+ */
- manifest_text: ""
- }).then((collection) => {
++Cypress.Commands.add("appendLog", (token, crUuid, fileName, lines = []) =>
++ cy.getContainerRequest(token, crUuid).then(containerRequest => {
++ if (containerRequest.log_uuid) {
++ cy.listContainerRequestLogs(token, crUuid).then(logFiles => {
++ const filePath = `${containerRequest.log_uuid}/${containerLogFolderPrefix}${containerRequest.container_uuid}/${fileName}`;
++ if (logFiles.find(file => file.name === fileName)) {
++ // File exists, fetch and append
++ return cy
++ .doWebDAVRequest("GET", `c=${filePath}`, null, null, token)
++ .then(({ body: contents }) =>
++ cy.doWebDAVRequest("PUT", `c=${filePath}`, contents.split("\n").concat(lines).join("\n"), null, token)
++ );
++ } else {
++ // File not exists, put new file
++ cy.doWebDAVRequest("PUT", `c=${filePath}`, lines.join("\n"), null, token);
++ }
++ });
++ } else {
++ // Create log collection
++ return cy
++ .createCollection(token, {
+ name: `Test log collection ${Math.floor(Math.random() * 999999)}`,
+ owner_uuid: containerRequest.owner_uuid,
- }).then(() => (
++ manifest_text: "",
++ })
++ .then(collection => {
+ // Update CR log_uuid to fake log collection
+ cy.updateContainerRequest(token, containerRequest.uuid, {
+ log_uuid: collection.uuid,
- cy.collectionReplaceFiles(token, collection.uuid, {
- [`/${containerLogFolderPrefix}${containerRequest.container_uuid}`]: "d41d8cd98f00b204e9800998ecf8427e+0"
- }).then(() => (
- // Put new log file with contents into fake log collection
- cy.doWebDAVRequest(
- 'PUT',
- `c=${collection.uuid}/${containerLogFolderPrefix}${containerRequest.container_uuid}/${fileName}`,
- lines.join('\n'),
- null,
- token
++ }).then(() =>
+ // Create empty directory for container uuid
- ))
- ));
++ cy
++ .collectionReplaceFiles(token, collection.uuid, {
++ [`/${containerLogFolderPrefix}${containerRequest.container_uuid}`]: "d41d8cd98f00b204e9800998ecf8427e+0",
++ })
++ .then(() =>
++ // Put new log file with contents into fake log collection
++ cy.doWebDAVRequest(
++ "PUT",
++ `c=${collection.uuid}/${containerLogFolderPrefix}${containerRequest.container_uuid}/${fileName}`,
++ lines.join("\n"),
++ null,
++ token
++ )
+ )
- }
- })
- )
-)
-
-Cypress.Commands.add(
- "listContainerRequestLogs", (token, crUuid) => (
- cy.getContainerRequest(token, crUuid).then((containerRequest) => (
- cy.doWebDAVRequest('PROPFIND', `c=${containerRequest.log_uuid}/${containerLogFolderPrefix}${containerRequest.container_uuid}`, null, null, token)
- .then(({body: data}) => {
- return extractFilesData(new DOMParser().parseFromString(data, "text/xml"));
- })
- ))
- )
++ );
+ });
-Cypress.Commands.add(
- "createVirtualMachine", (token, data) => {
- return cy.createResource(token, 'virtual_machines', {
- virtual_machine: JSON.stringify(data),
- ensure_unique_name: true
- })
- }
-)
-
-Cypress.Commands.add(
- "getResource", (token, suffix, uuid) => {
- return cy.doRequest('GET', `/arvados/v1/${suffix}/${uuid}`, null, {}, token)
- .its('body')
- .then(function (resource) {
- return resource;
- })
- }
-)
-
-Cypress.Commands.add(
- "createResource", (token, suffix, data) => {
- return cy.doRequest('POST', '/arvados/v1/' + suffix, data, null, token, true)
- .its('body')
- .then(function (resource) {
- createdResources.push({suffix, uuid: resource.uuid});
- return resource;
++ }
++ })
+ );
+
- }
-)
-
-Cypress.Commands.add(
- "deleteResource", (token, suffix, uuid, failOnStatusCode = true) => {
- return cy.doRequest('DELETE', '/arvados/v1/' + suffix + '/' + uuid, null, null, token, false, true, failOnStatusCode)
- .its('body')
- .then(function (resource) {
- return resource;
- })
- }
-)
-
-Cypress.Commands.add(
- "updateResource", (token, suffix, uuid, data) => {
- return cy.doRequest('PATCH', '/arvados/v1/' + suffix + '/' + uuid, data, null, token, true)
- .its('body')
- .then(function (resource) {
- return resource;
- })
- }
-)
-
-Cypress.Commands.add(
- "loginAs", (user) => {
- cy.clearCookies()
- cy.clearLocalStorage()
- cy.visit(`/token/?api_token=${user.token}`);
- cy.url({timeout: 10000}).should('contain', '/projects/');
- cy.get('div#root').should('contain', 'Arvados Workbench (zzzzz)');
- cy.get('div#root').should('not.contain', 'Your account is inactive');
- }
-)
-
-Cypress.Commands.add(
- "testEditProjectOrCollection", (container, oldName, newName, newDescription, isProject = true) => {
- cy.get(container).contains(oldName).rightclick();
- cy.get('[data-cy=context-menu]').contains(isProject ? 'Edit project' : 'Edit collection').click();
- cy.get('[data-cy=form-dialog]').within(() => {
- cy.get('input[name=name]').clear().type(newName);
- cy.get(isProject ? 'div[contenteditable=true]' : 'input[name=description]').clear().type(newDescription);
- cy.get('[data-cy=form-submit-btn]').click();
- });
-
- cy.get(container).contains(newName).rightclick();
- cy.get('[data-cy=context-menu]').contains(isProject ? 'Edit project' : 'Edit collection').click();
- cy.get('[data-cy=form-dialog]').within(() => {
- cy.get('input[name=name]').should('have.value', newName);
-
- if (isProject) {
- cy.get('span[data-text=true]').contains(newDescription);
- } else {
- cy.get('input[name=description]').should('have.value', newDescription);
- }
++Cypress.Commands.add("listContainerRequestLogs", (token, crUuid) =>
++ cy.getContainerRequest(token, crUuid).then(containerRequest =>
++ cy
++ .doWebDAVRequest(
++ "PROPFIND",
++ `c=${containerRequest.log_uuid}/${containerLogFolderPrefix}${containerRequest.container_uuid}`,
++ null,
++ null,
++ token
++ )
++ .then(({ body: data }) => {
++ return extractFilesData(new DOMParser().parseFromString(data, "text/xml"));
+ })
- cy.get('[data-cy=form-cancel-btn]').click();
- });
- }
-)
++ )
++);
+
-Cypress.Commands.add(
- "doSearch", (searchTerm) => {
- cy.get('[data-cy=searchbar-input-field]').type(`{selectall}${searchTerm}{enter}`);
- }
-)
++cy.get("[data-cy=form-cancel-btn]").click();
+
+Cypress.Commands.add("doSearch", searchTerm => {
+ cy.get("[data-cy=searchbar-input-field]").type(`{selectall}${searchTerm}{enter}`);
+ cy.get("[data-cy=searchbar-parent-form]").submit();
+});
-Cypress.Commands.add(
- "goToPath", (path) => {
- return cy.window().its('appHistory').invoke('push', path);
- }
-)
+Cypress.Commands.add("goToPath", path => {
+ return cy.window().its("appHistory").invoke("push", path);
+});
-Cypress.Commands.add('getAll', (...elements) => {
- const promise = cy.wrap([], { log: false })
+Cypress.Commands.add("getAll", (...elements) => {
+ const promise = cy.wrap([], { log: false });
for (let element of elements) {
- promise.then(arr => cy.get(element).then(got => cy.wrap([...arr, got])))
+ promise.then(arr => cy.get(element).then(got => cy.wrap([...arr, got])));
}
- return promise
-})
+ return promise;
+});
-Cypress.Commands.add('shareWith', (srcUserToken, targetUserUUID, itemUUID, permission = 'can_write') => {
+Cypress.Commands.add("shareWith", (srcUserToken, targetUserUUID, itemUUID, permission = "can_write") => {
cy.createLink(srcUserToken, {
name: permission,
- link_class: 'permission',
+ link_class: "permission",
head_uuid: itemUUID,
- tail_uuid: targetUserUUID
+ tail_uuid: targetUserUUID,
});
-})
+});
-Cypress.Commands.add('addToFavorites', (userToken, userUUID, itemUUID) => {
+Cypress.Commands.add("addToFavorites", (userToken, userUUID, itemUUID) => {
cy.createLink(userToken, {
head_uuid: itemUUID,
- link_class: 'star',
- name: '',
+ link_class: "star",
+ name: "",
owner_uuid: userUUID,
tail_uuid: userUUID,
});
//
// SPDX-License-Identifier: AGPL-3.0
-import React from 'react';
-import classNames from 'classnames';
-import { connect } from 'react-redux';
+import React from "react";
+import classNames from "classnames";
+import { connect } from "react-redux";
import { FixedSizeList } from "react-window";
import AutoSizer from "react-virtualized-auto-sizer";
-import servicesProvider from 'common/service-provider';
-import { DownloadIcon, MoreHorizontalIcon, MoreVerticalIcon } from 'components/icon/icon';
-import { SearchInput } from 'components/search-input/search-input';
+import servicesProvider from "common/service-provider";
- import { CustomizeTableIcon, DownloadIcon, MoreOptionsIcon } from "components/icon/icon";
++import { DownloadIcon, MoreHorizontalIcon, MoreVerticalIcon } from "components/icon/icon";
+import { SearchInput } from "components/search-input/search-input";
import {
ListItemIcon,
StyleRulesCallback,
Checkbox,
CircularProgress,
Button,
-} from '@material-ui/core';
-import { FileTreeData } from '../file-tree/file-tree-data';
-import { TreeItem, TreeItemStatus } from '../tree/tree';
-import { RootState } from 'store/store';
-import { WebDAV, WebDAVRequestConfig } from 'common/webdav';
-import { AuthState } from 'store/auth/auth-reducer';
-import { extractFilesData } from 'services/collection-service/collection-service-files-response';
-import {
- DefaultIcon,
- DirectoryIcon,
- FileIcon,
- BackIcon,
- SidePanelRightArrowIcon
-} from 'components/icon/icon';
-import { setCollectionFiles } from 'store/collection-panel/collection-panel-files/collection-panel-files-actions';
-import { sortBy } from 'lodash';
-import { formatFileSize } from 'common/formatters';
-import { getInlineFileUrl, sanitizeToken } from 'views-components/context-menu/actions/helpers';
-import { extractUuidKind, ResourceKind } from 'models/resource';
+} from "@material-ui/core";
+import { FileTreeData } from "../file-tree/file-tree-data";
+import { TreeItem, TreeItemStatus } from "../tree/tree";
+import { RootState } from "store/store";
+import { WebDAV, WebDAVRequestConfig } from "common/webdav";
+import { AuthState } from "store/auth/auth-reducer";
+import { extractFilesData } from "services/collection-service/collection-service-files-response";
+import { DefaultIcon, DirectoryIcon, FileIcon, BackIcon, SidePanelRightArrowIcon } from "components/icon/icon";
+import { setCollectionFiles } from "store/collection-panel/collection-panel-files/collection-panel-files-actions";
+import { sortBy } from "lodash";
+import { formatFileSize } from "common/formatters";
+import { getInlineFileUrl, sanitizeToken } from "views-components/context-menu/actions/helpers";
++import { extractUuidKind, ResourceKind } from "models/resource";
export interface CollectionPanelFilesProps {
isWritable: boolean;
const pathPromise = {};
-export const CollectionPanelFiles = withStyles(styles)(connect((state: RootState) => ({
- auth: state.auth,
- collectionPanel: state.collectionPanel,
- collectionPanelFiles: state.collectionPanelFiles,
-}))((props: CollectionPanelFilesProps & WithStyles<CssRules> & { auth: AuthState }) => {
- const { classes, onItemMenuOpen, onUploadDataClick, isWritable, dispatch, collectionPanelFiles, collectionPanel } = props;
- const { apiToken, config } = props.auth;
-
- const webdavClient = new WebDAV({
- baseURL: config.keepWebServiceUrl,
- headers: {
- Authorization: `Bearer ${apiToken}`
- },
- });
+export const CollectionPanelFiles = withStyles(styles)(
+ connect((state: RootState) => ({
+ auth: state.auth,
+ collectionPanel: state.collectionPanel,
+ collectionPanelFiles: state.collectionPanelFiles,
+ }))((props: CollectionPanelFilesProps & WithStyles<CssRules> & { auth: AuthState }) => {
+ const { classes, onItemMenuOpen, onUploadDataClick, isWritable, dispatch, collectionPanelFiles, collectionPanel } = props;
+ const { apiToken, config } = props.auth;
+
+ const webdavClient = new WebDAV({
+ baseURL: config.keepWebServiceUrl,
+ headers: {
+ Authorization: `Bearer ${apiToken}`,
+ },
+ });
+
+ const webDAVRequestConfig: WebDAVRequestConfig = {
+ headers: {
+ Depth: "1",
+ },
+ };
- const webDAVRequestConfig: WebDAVRequestConfig = {
- headers: {
- Depth: '1',
- },
- };
-
- const parentRef = React.useRef(null);
- const [path, setPath] = React.useState<string[]>([]);
- const [pathData, setPathData] = React.useState({});
- const [isLoading, setIsLoading] = React.useState(false);
- const [leftSearch, setLeftSearch] = React.useState('');
- const [rightSearch, setRightSearch] = React.useState('');
-
- const leftKey = (path.length > 1 ? path.slice(0, path.length - 1) : path).join('/');
- const rightKey = path.join('/');
-
- const leftData = pathData[leftKey] || [];
- const rightData = pathData[rightKey];
-
- React.useEffect(() => {
- if (props.currentItemUuid && extractUuidKind(props.currentItemUuid) === ResourceKind.COLLECTION) {
- setPathData({});
- setPath([props.currentItemUuid]);
- }
- }, [props.currentItemUuid]);
-
- const fetchData = (keys, ignoreCache = false) => {
- const keyArray = Array.isArray(keys) ? keys : [keys];
-
- Promise.all(keyArray.filter(key => !!key)
- .map((key) => {
- const dataExists = !!pathData[key];
- const runningRequest = pathPromise[key];
-
- if (ignoreCache || (!dataExists && !runningRequest)) {
- if (!isLoading) {
- setIsLoading(true);
- }
+ const parentRef = React.useRef(null);
+ const [path, setPath] = React.useState<string[]>([]);
+ const [pathData, setPathData] = React.useState({});
+ const [isLoading, setIsLoading] = React.useState(false);
+ const [leftSearch, setLeftSearch] = React.useState("");
+ const [rightSearch, setRightSearch] = React.useState("");
- pathPromise[key] = true;
+ const leftKey = (path.length > 1 ? path.slice(0, path.length - 1) : path).join("/");
+ const rightKey = path.join("/");
- return webdavClient.propfind(`c=${key}`, webDAVRequestConfig);
- }
+ const leftData = pathData[leftKey] || [];
+ const rightData = pathData[rightKey];
- return Promise.resolve(null);
- })
- .filter((promise) => !!promise)
- )
- .then((requests) => {
- const newState = requests.map((request, index) => {
- if (request && request.responseXML != null) {
- const key = keyArray[index];
- const result: any = extractFilesData(request.responseXML);
- const sortedResult = sortBy(result, (n) => n.name).sort((n1, n2) => {
- if (n1.type === 'directory' && n2.type !== 'directory') {
- return -1;
+ React.useEffect(() => {
- if (props.currentItemUuid) {
++ if (props.currentItemUuid && extractUuidKind(props.currentItemUuid) === ResourceKind.COLLECTION) {
+ setPathData({});
+ setPath([props.currentItemUuid]);
+ }
+ }, [props.currentItemUuid]);
+
+ const fetchData = (keys, ignoreCache = false) => {
+ const keyArray = Array.isArray(keys) ? keys : [keys];
+
+ Promise.all(
+ keyArray
+ .filter(key => !!key)
+ .map(key => {
+ const dataExists = !!pathData[key];
+ const runningRequest = pathPromise[key];
+
+ if (ignoreCache || (!dataExists && !runningRequest)) {
+ if (!isLoading) {
+ setIsLoading(true);
}
- if (n1.type !== 'directory' && n2.type === 'directory') {
- return 1;
+
+ pathPromise[key] = true;
+
+ return webdavClient.propfind(`c=${key}`, webDAVRequestConfig);
+ }
+
+ return Promise.resolve(null);
+ })
+ .filter(promise => !!promise)
+ )
+ .then(requests => {
+ const newState = requests
+ .map((request, index) => {
+ if (request && request.responseXML != null) {
+ const key = keyArray[index];
+ const result: any = extractFilesData(request.responseXML);
+ const sortedResult = sortBy(result, n => n.name).sort((n1, n2) => {
+ if (n1.type === "directory" && n2.type !== "directory") {
+ return -1;
+ }
+ if (n1.type !== "directory" && n2.type === "directory") {
+ return 1;
+ }
+ return 0;
+ });
+
+ return { [key]: sortedResult };
}
- return 0;
- });
+ return {};
+ })
+ .reduce((prev, next) => {
+ return { ...next, ...prev };
+ }, {});
+ setPathData(state => ({ ...state, ...newState }));
+ })
+ .finally(() => {
+ setIsLoading(false);
+ keyArray.forEach(key => delete pathPromise[key]);
+ });
+ };
- return { [key]: sortedResult };
- }
- return {};
- }).reduce((prev, next) => {
- return { ...next, ...prev };
- }, {});
- setPathData((state) => ({ ...state, ...newState }));
- })
- .finally(() => {
- setIsLoading(false);
- keyArray.forEach(key => delete pathPromise[key]);
- });
- };
-
- React.useEffect(() => {
- if (rightKey) {
- fetchData(rightKey);
- setLeftSearch('');
- setRightSearch('');
- }
- }, [rightKey, rightData]); // eslint-disable-line react-hooks/exhaustive-deps
-
- const currentPDH = (collectionPanel.item || {}).portableDataHash;
- React.useEffect(() => {
- if (currentPDH) {
- fetchData([leftKey, rightKey], true);
- }
- }, [currentPDH]); // eslint-disable-line react-hooks/exhaustive-deps
-
- React.useEffect(() => {
- if (rightData) {
- const filtered = rightData.filter(({ name }) => name.indexOf(rightSearch) > -1);
- setCollectionFiles(filtered, false)(dispatch);
- }
- }, [rightData, dispatch, rightSearch]);
-
- const handleRightClick = React.useCallback(
- (event) => {
- event.preventDefault();
- let elem = event.target;
-
- while (elem && elem.dataset && !elem.dataset.item) {
- elem = elem.parentNode;
+ React.useEffect(() => {
+ if (rightKey) {
+ fetchData(rightKey);
+ setLeftSearch("");
+ setRightSearch("");
}
- }, [rightKey]); // eslint-disable-line react-hooks/exhaustive-deps
++ }, [rightKey, rightData]); // eslint-disable-line react-hooks/exhaustive-deps
- if (!elem || !elem.dataset) {
- return;
+ const currentPDH = (collectionPanel.item || {}).portableDataHash;
+ React.useEffect(() => {
+ if (currentPDH) {
+ fetchData([leftKey, rightKey], true);
}
+ }, [currentPDH]); // eslint-disable-line react-hooks/exhaustive-deps
- const { id } = elem.dataset;
+ React.useEffect(() => {
+ if (rightData) {
+ const filtered = rightData.filter(({ name }) => name.indexOf(rightSearch) > -1);
+ setCollectionFiles(filtered, false)(dispatch);
+ }
+ }, [rightData, dispatch, rightSearch]);
- const item: any = {
- id,
- data: rightData.find((elem) => elem.id === id),
- };
+ const handleRightClick = React.useCallback(
+ event => {
+ event.preventDefault();
+ let elem = event.target;
- if (id) {
- onItemMenuOpen(event, item, isWritable);
- }
- },
- [onItemMenuOpen, isWritable, rightData]);
+ while (elem && elem.dataset && !elem.dataset.item) {
+ elem = elem.parentNode;
+ }
- React.useEffect(() => {
- let node = null;
+ if (!elem || !elem.dataset) {
+ return;
+ }
- if (parentRef?.current) {
- node = parentRef.current;
- (node as any).addEventListener('contextmenu', handleRightClick);
- }
+ const { id } = elem.dataset;
- return () => {
- if (node) {
- (node as any).removeEventListener('contextmenu', handleRightClick);
- }
- };
- }, [parentRef, handleRightClick]);
+ const item: any = {
+ id,
+ data: rightData.find(elem => elem.id === id),
+ };
- const handleClick = React.useCallback(
- (event: any) => {
- let isCheckbox = false;
- let isMoreButton = false;
- let elem = event.target;
+ if (id) {
+ onItemMenuOpen(event, item, isWritable);
+ }
+ },
+ [onItemMenuOpen, isWritable, rightData]
+ );
- if (elem.type === 'checkbox') {
- isCheckbox = true;
- }
- // The "More options" button click event could be triggered on its
- // internal graphic element.
- else if ((elem.dataset && elem.dataset.id === 'moreOptions') || (elem.parentNode && elem.parentNode.dataset && elem.parentNode.dataset.id === 'moreOptions')) {
- isMoreButton = true;
- }
+ React.useEffect(() => {
+ let node = null;
- while (elem && elem.dataset && !elem.dataset.item) {
- elem = elem.parentNode;
+ if (parentRef?.current) {
+ node = parentRef.current;
+ (node as any).addEventListener("contextmenu", handleRightClick);
}
- if (elem && elem.dataset && !isCheckbox && !isMoreButton) {
- const { parentPath, subfolderPath, breadcrumbPath, type } = elem.dataset;
-
- if (breadcrumbPath) {
- const index = path.indexOf(breadcrumbPath);
- setPath((state) => ([...state.slice(0, index + 1)]));
+ return () => {
+ if (node) {
+ (node as any).removeEventListener("contextmenu", handleRightClick);
}
+ };
+ }, [parentRef, handleRightClick]);
- if (parentPath && type === 'directory') {
- if (path.length > 1) {
- path.pop()
- }
+ const handleClick = React.useCallback(
+ (event: any) => {
+ let isCheckbox = false;
+ let isMoreButton = false;
+ let elem = event.target;
- setPath((state) => ([...state, parentPath]));
+ if (elem.type === "checkbox") {
+ isCheckbox = true;
}
-
- if (subfolderPath && type === 'directory') {
- setPath((state) => ([...state, subfolderPath]));
+ // The "More options" button click event could be triggered on its
+ // internal graphic element.
+ else if (
+ (elem.dataset && elem.dataset.id === "moreOptions") ||
+ (elem.parentNode && elem.parentNode.dataset && elem.parentNode.dataset.id === "moreOptions")
+ ) {
+ isMoreButton = true;
}
- if (elem.dataset.id && type === 'file') {
- const item = rightData.find(({ id }) => id === elem.dataset.id) || leftData.find(({ id }) => id === elem.dataset.id);
- const enhancedItem = servicesProvider.getServices().collectionService.extendFileURL(item);
- const fileUrl = sanitizeToken(getInlineFileUrl(enhancedItem.url, config.keepWebServiceUrl, config.keepWebInlineServiceUrl), true);
- window.open(fileUrl, '_blank');
+ while (elem && elem.dataset && !elem.dataset.item) {
+ elem = elem.parentNode;
}
- }
- if (isCheckbox) {
- const { id } = elem.dataset;
- const item = collectionPanelFiles[id];
- props.onSelectionToggle(event, item);
- }
- if (isMoreButton) {
- const { id } = elem.dataset;
- const item: any = {
- id,
- data: rightData.find((elem) => elem.id === id),
- };
- onItemMenuOpen(event, item, isWritable);
- }
- },
- [path, setPath, collectionPanelFiles] // eslint-disable-line react-hooks/exhaustive-deps
- );
-
- const getItemIcon = React.useCallback(
- (type: string, activeClass: string | null) => {
- let Icon = DefaultIcon;
-
- switch (type) {
- case 'directory':
- Icon = DirectoryIcon;
- break;
- case 'file':
- Icon = FileIcon;
- break;
- }
+ if (elem && elem.dataset && !isCheckbox && !isMoreButton) {
+ const { parentPath, subfolderPath, breadcrumbPath, type } = elem.dataset;
- return (
- <ListItemIcon className={classNames(classes.listItemIcon, activeClass)}>
- <Icon />
- </ListItemIcon>
- )
- },
- [classes]
- );
+ if (breadcrumbPath) {
+ const index = path.indexOf(breadcrumbPath);
+ setPath(state => [...state.slice(0, index + 1)]);
+ }
- const getActiveClass = React.useCallback(
- (name) => {
- return path[path.length - 1] === name ? classes.rowActive : null;
- },
- [path, classes]
- );
+ if (parentPath && type === "directory") {
+ if (path.length > 1) {
+ path.pop();
+ }
- const onOptionsMenuOpen = React.useCallback(
- (ev, isWritable) => {
- props.onOptionsMenuOpen(ev, isWritable);
- },
- [props.onOptionsMenuOpen] // eslint-disable-line react-hooks/exhaustive-deps
- );
-
- return <div data-cy="collection-files-panel" onClick={handleClick} ref={parentRef}>
- <div className={classes.pathPanel}>
- <div className={classes.pathPanelPathWrapper}>
- {path.map((p: string, index: number) =>
- <span key={`${index}-${p}`} data-item="true"
- className={classes.pathPanelItem} data-breadcrumb-path={p}>
- <span className={classes.rowActive}>{index === 0 ? 'Home' : p}</span> <b>/</b>
- </span>)
+ setPath(state => [...state, parentPath]);
+ }
+
+ if (subfolderPath && type === "directory") {
+ setPath(state => [...state, subfolderPath]);
+ }
+
+ if (elem.dataset.id && type === "file") {
+ const item = rightData.find(({ id }) => id === elem.dataset.id) || leftData.find(({ id }) => id === elem.dataset.id);
+ const enhancedItem = servicesProvider.getServices().collectionService.extendFileURL(item);
+ const fileUrl = sanitizeToken(
+ getInlineFileUrl(enhancedItem.url, config.keepWebServiceUrl, config.keepWebInlineServiceUrl),
+ true
+ );
+ window.open(fileUrl, "_blank");
+ }
}
- </div>
- <Tooltip className={classes.pathPanelMenu} title="More options" disableFocusListener>
- <IconButton data-cy='collection-files-panel-options-btn'
- onClick={(ev) => {
- onOptionsMenuOpen(ev, isWritable);
- }}>
- <MoreVerticalIcon />
- </IconButton>
- </Tooltip>
- </div>
- <div className={classes.wrapper}>
- <div className={classNames(classes.leftPanel, path.length > 1 ? classes.leftPanelVisible : classes.leftPanelHidden)} data-cy="collection-files-left-panel">
- <Tooltip title="Go back" className={path.length > 1 ? classes.backButton : classes.backButtonHidden}>
- <IconButton onClick={() => setPath((state) => ([...state.slice(0, state.length - 1)]))}>
- <BackIcon />
- </IconButton>
- </Tooltip>
- <div className={path.length > 1 ? classes.searchWrapper : classes.searchWrapperHidden}>
- <SearchInput selfClearProp={leftKey} label="Search" value={leftSearch} onSearch={setLeftSearch} />
- </div>
- <div className={classes.dataWrapper}>{leftData
- ? <AutoSizer defaultWidth={0}>{({ height, width }) => {
- const filtered = leftData.filter(({ name }) => name.indexOf(leftSearch) > -1);
- return !!filtered.length
- ? <FixedSizeList height={height} itemCount={filtered.length}
- itemSize={35} width={width}>{({ index, style }) => {
- const { id, type, name } = filtered[index];
- return <div data-id={id} style={style} data-item="true"
- data-type={type} data-parent-path={name}
- className={classNames(classes.row, getActiveClass(name))}
- key={id}>
- {getItemIcon(type, getActiveClass(name))}
- <div className={classes.rowName}>
- {name}
- </div>
- {getActiveClass(name)
- ? <SidePanelRightArrowIcon
- style={{ display: 'inline', marginTop: '5px', marginLeft: '5px' }} />
- : null
- }
- </div>;
- }}</FixedSizeList>
- : <div className={classes.rowEmpty}>No directories available</div>
- }}
- </AutoSizer>
- : <div data-cy="collection-loader" className={classes.row}><CircularProgress className={classes.loader} size={30} /></div>}
- </div>
- </div>
- <div className={classes.rightPanel} data-cy="collection-files-right-panel">
- <div className={classes.searchWrapper}>
- <SearchInput selfClearProp={rightKey} label="Search" value={rightSearch} onSearch={setRightSearch} />
+
+ if (isCheckbox) {
+ const { id } = elem.dataset;
+ const item = collectionPanelFiles[id];
+ props.onSelectionToggle(event, item);
+ }
+ if (isMoreButton) {
+ const { id } = elem.dataset;
+ const item: any = {
+ id,
+ data: rightData.find(elem => elem.id === id),
+ };
+ onItemMenuOpen(event, item, isWritable);
+ }
+ },
+ [path, setPath, collectionPanelFiles] // eslint-disable-line react-hooks/exhaustive-deps
+ );
+
+ const getItemIcon = React.useCallback(
+ (type: string, activeClass: string | null) => {
+ let Icon = DefaultIcon;
+
+ switch (type) {
+ case "directory":
+ Icon = DirectoryIcon;
+ break;
+ case "file":
+ Icon = FileIcon;
+ break;
+ }
+
+ return (
+ <ListItemIcon className={classNames(classes.listItemIcon, activeClass)}>
+ <Icon />
+ </ListItemIcon>
+ );
+ },
+ [classes]
+ );
+
+ const getActiveClass = React.useCallback(
+ name => {
+ return path[path.length - 1] === name ? classes.rowActive : null;
+ },
+ [path, classes]
+ );
+
+ const onOptionsMenuOpen = React.useCallback(
+ (ev, isWritable) => {
+ props.onOptionsMenuOpen(ev, isWritable);
+ },
+ [props.onOptionsMenuOpen] // eslint-disable-line react-hooks/exhaustive-deps
+ );
+
+ return (
+ <div
+ data-cy="collection-files-panel"
+ onClick={handleClick}
- ref={parentRef}>
++ ref={parentRef}
++ >
+ <div className={classes.pathPanel}>
+ <div className={classes.pathPanelPathWrapper}>
+ {path.map((p: string, index: number) => (
+ <span
+ key={`${index}-${p}`}
+ data-item="true"
+ className={classes.pathPanelItem}
- data-breadcrumb-path={p}>
++ data-breadcrumb-path={p}
++ >
+ <span className={classes.rowActive}>{index === 0 ? "Home" : p}</span> <b>/</b>
+ </span>
+ ))}
+ </div>
+ <Tooltip
+ className={classes.pathPanelMenu}
+ title="More options"
- disableFocusListener>
++ disableFocusListener
++ >
+ <IconButton
+ data-cy="collection-files-panel-options-btn"
+ onClick={ev => {
+ onOptionsMenuOpen(ev, isWritable);
- }}>
- <CustomizeTableIcon />
++ }}
++ >
++ <MoreVerticalIcon />
+ </IconButton>
+ </Tooltip>
</div>
- {isWritable &&
- <Button className={classes.uploadButton} data-cy='upload-button'
- onClick={() => {
- onUploadDataClick(rightKey === leftKey ? undefined : rightKey);
- }}
- variant='contained' color='primary' size='small'>
- <DownloadIcon className={classes.uploadIcon} />
- Upload data
- </Button>}
- <div className={classes.dataWrapper}>{rightData && !isLoading
- ? <AutoSizer defaultHeight={500}>{({ height, width }) => {
- const filtered = rightData.filter(({ name }) => name.indexOf(rightSearch) > -1);
- return !!filtered.length
- ? <FixedSizeList height={height} itemCount={filtered.length}
- itemSize={35} width={width}>{({ index, style }) => {
- const { id, type, name, size } = filtered[index];
-
- return <div style={style} data-id={id} data-item="true"
- data-type={type} data-subfolder-path={name}
- className={classes.row} key={id}>
- <Checkbox color="primary"
- className={classes.rowSelection}
- checked={collectionPanelFiles[id] ? collectionPanelFiles[id].value.selected : false}
- />
- {getItemIcon(type, null)}
- <div className={classes.rowName}>
- {name}
- </div>
- <span className={classes.rowName} style={{
- marginLeft: 'auto', marginRight: '1rem'
- }}>
- {formatFileSize(size)}
- </span>
- <Tooltip title="More options" disableFocusListener>
- <IconButton data-id='moreOptions'
- data-cy='file-item-options-btn'
- className={classes.moreOptionsButton}>
- <MoreHorizontalIcon
- data-id='moreOptions'
- className={classes.moreOptions} />
- </IconButton>
- </Tooltip>
- </div>
- }}</FixedSizeList>
- : <div className={classes.rowEmpty}>This collection is empty</div>
- }}</AutoSizer>
- : <div className={classes.row}>
- <CircularProgress className={classes.loader} size={30} />
- </div>}
+ <div className={classes.wrapper}>
+ <div
+ className={classNames(classes.leftPanel, path.length > 1 ? classes.leftPanelVisible : classes.leftPanelHidden)}
- data-cy="collection-files-left-panel">
++ data-cy="collection-files-left-panel"
++ >
+ <Tooltip
+ title="Go back"
- className={path.length > 1 ? classes.backButton : classes.backButtonHidden}>
++ className={path.length > 1 ? classes.backButton : classes.backButtonHidden}
++ >
+ <IconButton onClick={() => setPath(state => [...state.slice(0, state.length - 1)])}>
+ <BackIcon />
+ </IconButton>
+ </Tooltip>
+ <div className={path.length > 1 ? classes.searchWrapper : classes.searchWrapperHidden}>
+ <SearchInput
+ selfClearProp={leftKey}
+ label="Search"
+ value={leftSearch}
+ onSearch={setLeftSearch}
+ />
+ </div>
+ <div className={classes.dataWrapper}>
+ {leftData ? (
+ <AutoSizer defaultWidth={0}>
+ {({ height, width }) => {
+ const filtered = leftData.filter(({ name }) => name.indexOf(leftSearch) > -1);
+ return !!filtered.length ? (
+ <FixedSizeList
+ height={height}
+ itemCount={filtered.length}
+ itemSize={35}
- width={width}>
++ width={width}
++ >
+ {({ index, style }) => {
+ const { id, type, name } = filtered[index];
+ return (
+ <div
+ data-id={id}
+ style={style}
+ data-item="true"
+ data-type={type}
+ data-parent-path={name}
+ className={classNames(classes.row, getActiveClass(name))}
- key={id}>
++ key={id}
++ >
+ {getItemIcon(type, getActiveClass(name))}
+ <div className={classes.rowName}>{name}</div>
+ {getActiveClass(name) ? (
+ <SidePanelRightArrowIcon
+ style={{ display: "inline", marginTop: "5px", marginLeft: "5px" }}
+ />
+ ) : null}
+ </div>
+ );
+ }}
+ </FixedSizeList>
+ ) : (
+ <div className={classes.rowEmpty}>No directories available</div>
+ );
+ }}
+ </AutoSizer>
+ ) : (
+ <div
+ data-cy="collection-loader"
- className={classes.row}>
++ className={classes.row}
++ >
+ <CircularProgress
+ className={classes.loader}
+ size={30}
+ />
+ </div>
+ )}
+ </div>
+ </div>
+ <div
+ className={classes.rightPanel}
- data-cy="collection-files-right-panel">
++ data-cy="collection-files-right-panel"
++ >
+ <div className={classes.searchWrapper}>
+ <SearchInput
+ selfClearProp={rightKey}
+ label="Search"
+ value={rightSearch}
+ onSearch={setRightSearch}
+ />
+ </div>
+ {isWritable && (
+ <Button
+ className={classes.uploadButton}
+ data-cy="upload-button"
+ onClick={() => {
+ onUploadDataClick(rightKey === leftKey ? undefined : rightKey);
+ }}
+ variant="contained"
+ color="primary"
- size="small">
++ size="small"
++ >
+ <DownloadIcon className={classes.uploadIcon} />
+ Upload data
+ </Button>
+ )}
+ <div className={classes.dataWrapper}>
+ {rightData && !isLoading ? (
+ <AutoSizer defaultHeight={500}>
+ {({ height, width }) => {
+ const filtered = rightData.filter(({ name }) => name.indexOf(rightSearch) > -1);
+ return !!filtered.length ? (
+ <FixedSizeList
+ height={height}
+ itemCount={filtered.length}
+ itemSize={35}
- width={width}>
++ width={width}
++ >
+ {({ index, style }) => {
+ const { id, type, name, size } = filtered[index];
+
+ return (
+ <div
+ style={style}
+ data-id={id}
+ data-item="true"
+ data-type={type}
+ data-subfolder-path={name}
+ className={classes.row}
- key={id}>
++ key={id}
++ >
+ <Checkbox
+ color="primary"
+ className={classes.rowSelection}
+ checked={collectionPanelFiles[id] ? collectionPanelFiles[id].value.selected : false}
+ />
+
+ {getItemIcon(type, null)}
+ <div className={classes.rowName}>{name}</div>
+ <span
+ className={classes.rowName}
+ style={{
+ marginLeft: "auto",
+ marginRight: "1rem",
- }}>
++ }}
++ >
+ {formatFileSize(size)}
+ </span>
+ <Tooltip
+ title="More options"
- disableFocusListener>
++ disableFocusListener
++ >
+ <IconButton
+ data-id="moreOptions"
+ data-cy="file-item-options-btn"
- className={classes.moreOptionsButton}>
- <MoreOptionsIcon
++ className={classes.moreOptionsButton}
++ >
++ <MoreHorizontalIcon
+ data-id="moreOptions"
+ className={classes.moreOptions}
+ />
+ </IconButton>
+ </Tooltip>
+ </div>
+ );
+ }}
+ </FixedSizeList>
+ ) : (
+ <div className={classes.rowEmpty}>This collection is empty</div>
+ );
+ }}
+ </AutoSizer>
+ ) : (
+ <div className={classes.row}>
+ <CircularProgress
+ className={classes.loader}
+ size={30}
+ />
+ </div>
+ )}
+ </div>
+ </div>
</div>
</div>
- </div>
- </div>
-}));
+ );
+ })
+);
import { ColumnSelector } from "components/column-selector/column-selector";
import { DataTable, DataColumns, DataTableFetchMode } from "components/data-table/data-table";
import { DataColumn } from "components/data-table/data-column";
-import { SearchInput } from 'components/search-input/search-input';
+import { SearchInput } from "components/search-input/search-input";
import { ArvadosTheme } from "common/custom-theme";
-import { createTree } from 'models/tree';
-import { DataTableFilters } from 'components/data-table-filters/data-table-filters-tree';
-import {
- CloseIcon,
- IconType,
- MaximizeIcon,
- UnMaximizeIcon,
- MoreVerticalIcon
-} from 'components/icon/icon';
-import { PaperProps } from '@material-ui/core/Paper';
-import { MPVPanelProps } from 'components/multi-panel-view/multi-panel-view';
++import { MultiselectToolbar } from "components/multiselect-toolbar/MultiselectToolbar";
++import { TCheckedList } from "components/data-table/data-table";
+import { createTree } from "models/tree";
+import { DataTableFilters } from "components/data-table-filters/data-table-filters-tree";
- import { CloseIcon, IconType, MaximizeIcon, UnMaximizeIcon, MoreOptionsIcon } from "components/icon/icon";
++import { CloseIcon, IconType, MaximizeIcon, UnMaximizeIcon, MoreVerticalIcon } from "components/icon/icon";
+import { PaperProps } from "@material-ui/core/Paper";
+import { MPVPanelProps } from "components/multi-panel-view/multi-panel-view";
- import { MultiselectToolbar } from "components/multiselect-toolbar/MultiselectToolbar";
- import { TCheckedList } from "components/data-table/data-table";
-type CssRules = 'searchBox' | 'headerMenu' | "toolbar" | "footer" | "root" | 'moreOptionsButton' | 'title' | 'dataTable' | 'container';
+type CssRules = "searchBox" | "headerMenu" | "toolbar" | "footer" | "root" | "moreOptionsButton" | "title" | "dataTable" | "container";
const styles: StyleRulesCallback<CssRules> = (theme: ArvadosTheme) => ({
searchBox: {
render() {
const {
- columns, onContextMenu, onFiltersChange, onSortToggle, extractKey,
- rowsPerPage, rowsPerPageOptions, onColumnToggle, searchLabel, searchValue, onSearch,
- items, itemsAvailable, onRowClick, onRowDoubleClick, classes,
- defaultViewIcon, defaultViewMessages, hideColumnSelector, actions, paperProps, hideSearchInput,
- paperKey, fetchMode, currentItemUuid, title,
- doHidePanel, doMaximizePanel, doUnMaximizePanel, panelName, panelMaximized, elementPath
+ columns,
+ onContextMenu,
+ onFiltersChange,
+ onSortToggle,
+ extractKey,
+ rowsPerPage,
+ rowsPerPageOptions,
+ onColumnToggle,
+ searchLabel,
+ searchValue,
+ onSearch,
+ items,
+ itemsAvailable,
+ onRowClick,
+ onRowDoubleClick,
+ classes,
+ defaultViewIcon,
+ defaultViewMessages,
+ hideColumnSelector,
+ actions,
+ paperProps,
+ hideSearchInput,
+ paperKey,
+ fetchMode,
+ currentItemUuid,
+ title,
+ doHidePanel,
+ doMaximizePanel,
+ doUnMaximizePanel,
+ panelName,
+ panelMaximized,
+ elementPath,
+ toggleMSToolbar,
+ setCheckedListOnStore,
+ checkedList,
} = this.props;
- return <Paper className={classes.root} {...paperProps} key={paperKey} data-cy={this.props["data-cy"]}>
- <Grid container direction="column" wrap="nowrap" className={classes.container}>
- <div>
- {title && <Grid item xs className={classes.title}>{title}</Grid>}
- {
- (!hideColumnSelector || !hideSearchInput || !!actions) &&
- <Grid className={classes.headerMenu} item xs>
- <Toolbar className={classes.toolbar}>
- {!hideSearchInput && <div className={classes.searchBox}>
- {!hideSearchInput && <SearchInput
- label={searchLabel}
- value={searchValue}
- selfClearProp={''}
- onSearch={onSearch} />}
- </div>}
- {actions}
- {!hideColumnSelector && <ColumnSelector
- columns={columns}
- onColumnToggle={onColumnToggle} />}
- { doUnMaximizePanel && panelMaximized &&
- <Tooltip title={`Unmaximize ${panelName || 'panel'}`} disableFocusListener>
- <IconButton onClick={doUnMaximizePanel}><UnMaximizeIcon /></IconButton>
- </Tooltip> }
- { doMaximizePanel && !panelMaximized &&
- <Tooltip title={`Maximize ${panelName || 'panel'}`} disableFocusListener>
- <IconButton onClick={doMaximizePanel}><MaximizeIcon /></IconButton>
- </Tooltip> }
- { doHidePanel &&
- <Tooltip title={`Close ${panelName || 'panel'}`} disableFocusListener>
- <IconButton disabled={panelMaximized} onClick={doHidePanel}><CloseIcon /></IconButton>
- </Tooltip> }
- </Toolbar>
- </Grid>
- }
- </div>
- <Grid item xs="auto" className={classes.dataTable}><DataTable
- columns={this.props.contextMenuColumn ? [...columns, this.contextMenuColumn] : columns}
- items={items}
- onRowClick={(_, item: T) => onRowClick(item)}
- onContextMenu={onContextMenu}
- onRowDoubleClick={(_, item: T) => onRowDoubleClick(item)}
- onFiltersChange={onFiltersChange}
- onSortToggle={onSortToggle}
- extractKey={extractKey}
- working={this.state.showLoading}
- defaultViewIcon={defaultViewIcon}
- defaultViewMessages={defaultViewMessages}
- currentItemUuid={currentItemUuid}
- currentRoute={paperKey} /></Grid>
- <Grid item xs><Toolbar className={classes.footer}>
- {
- elementPath &&
- <Grid container>
- <span data-cy="element-path">
- {elementPath}
- </span>
+ return (
+ <Paper
+ className={classes.root}
+ {...paperProps}
+ key={paperKey}
- data-cy={this.props["data-cy"]}>
++ data-cy={this.props["data-cy"]}
++ >
+ <Grid
+ container
+ direction="column"
+ wrap="nowrap"
- className={classes.container}>
++ className={classes.container}
++ >
+ <div>
+ {title && (
+ <Grid
+ item
+ xs
- className={classes.title}>
++ className={classes.title}
++ >
+ {title}
+ </Grid>
+ )}
+ {(!hideColumnSelector || !hideSearchInput || !!actions) && (
+ <Grid
+ className={classes.headerMenu}
+ item
- xs>
++ xs
++ >
+ <Toolbar className={classes.toolbar}>
+ {!hideSearchInput && (
+ <div className={classes.searchBox}>
+ {!hideSearchInput && (
+ <SearchInput
+ label={searchLabel}
+ value={searchValue}
+ selfClearProp={""}
+ onSearch={onSearch}
+ />
+ )}
+ </div>
+ )}
+ {actions}
+ {!hideColumnSelector && (
+ <ColumnSelector
+ columns={columns}
+ onColumnToggle={onColumnToggle}
+ />
+ )}
+ {doUnMaximizePanel && panelMaximized && (
+ <Tooltip
+ title={`Unmaximize ${panelName || "panel"}`}
- disableFocusListener>
++ disableFocusListener
++ >
+ <IconButton onClick={doUnMaximizePanel}>
+ <UnMaximizeIcon />
+ </IconButton>
+ </Tooltip>
+ )}
+ {doMaximizePanel && !panelMaximized && (
+ <Tooltip
+ title={`Maximize ${panelName || "panel"}`}
- disableFocusListener>
++ disableFocusListener
++ >
+ <IconButton onClick={doMaximizePanel}>
+ <MaximizeIcon />
+ </IconButton>
+ </Tooltip>
+ )}
+ {doHidePanel && (
+ <Tooltip
+ title={`Close ${panelName || "panel"}`}
- disableFocusListener>
++ disableFocusListener
++ >
+ <IconButton
+ disabled={panelMaximized}
- onClick={doHidePanel}>
++ onClick={doHidePanel}
++ >
+ <CloseIcon />
+ </IconButton>
+ </Tooltip>
+ )}
+ </Toolbar>
+ <MultiselectToolbar />
+ </Grid>
+ )}
+ </div>
+ <Grid
+ item
+ xs="auto"
- className={classes.dataTable}>
++ className={classes.dataTable}
++ >
+ <DataTable
+ columns={this.props.contextMenuColumn ? [...columns, this.contextMenuColumn] : columns}
+ items={items}
+ onRowClick={(_, item: T) => onRowClick(item)}
+ onContextMenu={onContextMenu}
+ onRowDoubleClick={(_, item: T) => onRowDoubleClick(item)}
+ onFiltersChange={onFiltersChange}
+ onSortToggle={onSortToggle}
+ extractKey={extractKey}
+ working={this.state.showLoading}
+ defaultViewIcon={defaultViewIcon}
+ defaultViewMessages={defaultViewMessages}
+ currentItemUuid={currentItemUuid}
+ currentRoute={paperKey}
+ toggleMSToolbar={toggleMSToolbar}
+ setCheckedListOnStore={setCheckedListOnStore}
+ checkedList={checkedList}
+ />
+ </Grid>
+ <Grid
+ item
- xs>
++ xs
++ >
+ <Toolbar className={classes.footer}>
+ {elementPath && (
+ <Grid container>
+ <span data-cy="element-path">{elementPath}</span>
+ </Grid>
+ )}
+ <Grid
+ container={!elementPath}
- justify="flex-end">
++ justify="flex-end"
++ >
+ {fetchMode === DataTableFetchMode.PAGINATED ? (
+ <TablePagination
+ count={itemsAvailable}
+ rowsPerPage={rowsPerPage}
+ rowsPerPageOptions={rowsPerPageOptions}
+ page={this.props.page}
+ onChangePage={this.changePage}
+ onChangeRowsPerPage={this.changeRowsPerPage}
+ // Disable next button on empty lists since that's not default behavior
+ nextIconButtonProps={itemsAvailable > 0 ? {} : { disabled: true }}
+ component="div"
+ />
+ ) : (
+ <Button
+ variant="text"
+ size="medium"
- onClick={this.loadMore}>
++ onClick={this.loadMore}
++ >
+ Load more
+ </Button>
+ )}
+ </Grid>
+ </Toolbar>
</Grid>
- }
- <Grid container={!elementPath} justify="flex-end">
- {fetchMode === DataTableFetchMode.PAGINATED ? <TablePagination
- count={itemsAvailable}
- rowsPerPage={rowsPerPage}
- rowsPerPageOptions={rowsPerPageOptions}
- page={this.props.page}
- onChangePage={this.changePage}
- onChangeRowsPerPage={this.changeRowsPerPage}
- // Disable next button on empty lists since that's not default behavior
- nextIconButtonProps={(itemsAvailable > 0) ? {} : {disabled: true}}
- component="div" /> : <Button
- variant="text"
- size="medium"
- onClick={this.loadMore}
- >Load more</Button>}
</Grid>
- </Toolbar></Grid>
- </Grid>
- </Paper>;
+ </Paper>
+ );
}
changePage = (event: React.MouseEvent<HTMLButtonElement>, page: number) => {
loadMore = () => {
this.props.onLoadMore(this.props.page + 1);
- }
+ };
- renderContextMenuTrigger = (item: T) =>
- <Grid container justify="center">
- <Tooltip title="More options" disableFocusListener>
- <IconButton className={this.props.classes.moreOptionsButton} onClick={event => this.props.onContextMenu(event, item)}>
+ renderContextMenuTrigger = (item: T) => (
+ <Grid
+ container
- justify="center">
++ justify="center"
++ >
+ <Tooltip
+ title="More options"
- disableFocusListener>
++ disableFocusListener
++ >
+ <IconButton
+ className={this.props.classes.moreOptionsButton}
- onClick={event => this.props.onContextMenu(event, item)}>
- <MoreOptionsIcon />
++ onClick={event => this.props.onContextMenu(event, item)}
++ >
+ <MoreVerticalIcon />
</IconButton>
</Tooltip>
</Grid>
import { AddSession } from "views-components/add-session/add-session";
import { initAuth, logout } from "store/auth/auth-action";
import { createServices } from "services/services";
-import { MuiThemeProvider } from '@material-ui/core/styles';
-import { CustomTheme } from 'common/custom-theme';
-import { fetchConfig } from 'common/config';
-import servicesProvider from 'common/service-provider';
-import { addMenuActionSet, ContextMenuKind } from 'views-components/context-menu/context-menu';
+import { MuiThemeProvider } from "@material-ui/core/styles";
+import { CustomTheme } from "common/custom-theme";
+import { fetchConfig } from "common/config";
+import servicesProvider from "common/service-provider";
+import { addMenuActionSet, ContextMenuKind } from "views-components/context-menu/context-menu";
import { rootProjectActionSet } from "views-components/context-menu/action-sets/root-project-action-set";
-import { filterGroupActionSet, frozenActionSet, projectActionSet, readOnlyProjectActionSet } from "views-components/context-menu/action-sets/project-action-set";
-import { resourceActionSet } from 'views-components/context-menu/action-sets/resource-action-set';
+import {
+ filterGroupActionSet,
+ frozenActionSet,
+ projectActionSet,
+ readOnlyProjectActionSet,
+} from "views-components/context-menu/action-sets/project-action-set";
+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 { collectionFilesActionSet, collectionFilesMultipleActionSet, readOnlyCollectionFilesActionSet, readOnlyCollectionFilesMultipleActionSet } from 'views-components/context-menu/action-sets/collection-files-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 { loadWorkbench } from 'store/workbench/workbench-actions';
-import { Routes } from 'routes/routes';
++import {
++ collectionFilesActionSet,
++ collectionFilesMultipleActionSet,
++ readOnlyCollectionFilesActionSet,
++ readOnlyCollectionFilesMultipleActionSet,
++} from "views-components/context-menu/action-sets/collection-files-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 { loadWorkbench } from "store/workbench/workbench-actions";
+import { Routes } from "routes/routes";
import { trashActionSet } from "views-components/context-menu/action-sets/trash-action-set";
-import { ServiceRepository } from 'services/services';
-import { initWebSocket } from 'websocket/websocket';
-import { Config } from 'common/config';
-import { addRouteChangeHandlers } from './routes/route-change-handlers';
-import { setTokenDialogApiHost } from 'store/token-dialog/token-dialog-actions';
+import { ServiceRepository } from "services/services";
+import { initWebSocket } from "websocket/websocket";
+import { Config } from "common/config";
+import { addRouteChangeHandlers } from "./routes/route-change-handlers";
+import { setTokenDialogApiHost } from "store/token-dialog/token-dialog-actions";
import {
processResourceActionSet,
processResourceAdminActionSet,
- readOnlyProcessResourceActionSet
-} from 'views-components/context-menu/action-sets/process-resource-action-set';
-import { progressIndicatorActions } from 'store/progress-indicator/progress-indicator-actions';
-import { trashedCollectionActionSet } from 'views-components/context-menu/action-sets/trashed-collection-action-set';
-import { setBuildInfo } from 'store/app-info/app-info-actions';
-import { getBuildInfo } from 'common/app-info';
-import { DragDropContextProvider } from 'react-dnd';
-import HTML5Backend from 'react-dnd-html5-backend';
-import { initAdvancedFormProjectsTree } from 'store/search-bar/search-bar-actions';
-import { repositoryActionSet } from 'views-components/context-menu/action-sets/repository-action-set';
-import { sshKeyActionSet } from 'views-components/context-menu/action-sets/ssh-key-action-set';
-import { keepServiceActionSet } from 'views-components/context-menu/action-sets/keep-service-action-set';
-import { loadVocabulary } from 'store/vocabulary/vocabulary-actions';
-import { virtualMachineActionSet } from 'views-components/context-menu/action-sets/virtual-machine-action-set';
-import { userActionSet } from 'views-components/context-menu/action-sets/user-action-set';
-import { apiClientAuthorizationActionSet } from 'views-components/context-menu/action-sets/api-client-authorization-action-set';
-import { groupActionSet } from 'views-components/context-menu/action-sets/group-action-set';
-import { groupMemberActionSet } from 'views-components/context-menu/action-sets/group-member-action-set';
-import { linkActionSet } from 'views-components/context-menu/action-sets/link-action-set';
-import { loadFileViewersConfig } from 'store/file-viewers/file-viewers-actions';
-import { filterGroupAdminActionSet, frozenAdminActionSet, projectAdminActionSet } from 'views-components/context-menu/action-sets/project-admin-action-set';
-import { permissionEditActionSet } from 'views-components/context-menu/action-sets/permission-edit-action-set';
-import { workflowActionSet, readOnlyWorkflowActionSet } from 'views-components/context-menu/action-sets/workflow-action-set';
+ readOnlyProcessResourceActionSet,
+} from "views-components/context-menu/action-sets/process-resource-action-set";
+import { progressIndicatorActions } from "store/progress-indicator/progress-indicator-actions";
+import { trashedCollectionActionSet } from "views-components/context-menu/action-sets/trashed-collection-action-set";
+import { setBuildInfo } from "store/app-info/app-info-actions";
+import { getBuildInfo } from "common/app-info";
+import { DragDropContextProvider } from "react-dnd";
+import HTML5Backend from "react-dnd-html5-backend";
+import { initAdvancedFormProjectsTree } from "store/search-bar/search-bar-actions";
+import { repositoryActionSet } from "views-components/context-menu/action-sets/repository-action-set";
+import { sshKeyActionSet } from "views-components/context-menu/action-sets/ssh-key-action-set";
+import { keepServiceActionSet } from "views-components/context-menu/action-sets/keep-service-action-set";
+import { loadVocabulary } from "store/vocabulary/vocabulary-actions";
+import { virtualMachineActionSet } from "views-components/context-menu/action-sets/virtual-machine-action-set";
+import { userActionSet } from "views-components/context-menu/action-sets/user-action-set";
+import { apiClientAuthorizationActionSet } from "views-components/context-menu/action-sets/api-client-authorization-action-set";
+import { groupActionSet } from "views-components/context-menu/action-sets/group-action-set";
+import { groupMemberActionSet } from "views-components/context-menu/action-sets/group-member-action-set";
+import { linkActionSet } from "views-components/context-menu/action-sets/link-action-set";
+import { loadFileViewersConfig } from "store/file-viewers/file-viewers-actions";
+import {
+ filterGroupAdminActionSet,
+ frozenAdminActionSet,
+ projectAdminActionSet,
+} from "views-components/context-menu/action-sets/project-admin-action-set";
+import { permissionEditActionSet } from "views-components/context-menu/action-sets/permission-edit-action-set";
- import { workflowActionSet } from "views-components/context-menu/action-sets/workflow-action-set";
++import { workflowActionSet, readOnlyWorkflowActionSet } from "views-components/context-menu/action-sets/workflow-action-set";
import { snackbarActions, SnackbarKind } from "store/snackbar/snackbar-actions";
-import { openNotFoundDialog } from './store/not-found-panel/not-found-panel-action';
-import { storeRedirects } from './common/redirect-to';
-import { searchResultsActionSet } from 'views-components/context-menu/action-sets/search-results-action-set';
+import { openNotFoundDialog } from "./store/not-found-panel/not-found-panel-action";
+import { storeRedirects } from "./common/redirect-to";
+import { searchResultsActionSet } from "views-components/context-menu/action-sets/search-results-action-set";
console.log(`Starting arvados [${getBuildInfo()}]`);
import { ApiActions } from "services/api/api-actions";
import { Session } from "models/session";
import { CommonService } from "services/common-service/common-service";
+ import { snakeCase } from "lodash";
+ import { CommonResourceServiceError } from "services/common-service/common-resource-service";
export type UploadProgress = (fileId: number, loaded: number, total: number, currentTime: number) => void;
-type CollectionPartialUpdateOrCreate = Partial<CollectionResource> & Pick<CollectionResource, "uuid"> |
- Partial<CollectionResource> & Pick<CollectionResource, "ownerUuid">;
++type CollectionPartialUpdateOrCreate =
++ | (Partial<CollectionResource> & Pick<CollectionResource, "uuid">)
++ | (Partial<CollectionResource> & Pick<CollectionResource, "ownerUuid">);
-export const emptyCollectionPdh = 'd41d8cd98f00b204e9800998ecf8427e+0';
-export const SOURCE_DESTINATION_EQUAL_ERROR_MESSAGE = 'Source and destination cannot be the same';
+export const emptyCollectionPdh = "d41d8cd98f00b204e9800998ecf8427e+0";
++export const SOURCE_DESTINATION_EQUAL_ERROR_MESSAGE = "Source and destination cannot be the same";
export class CollectionService extends TrashableResourceService<CollectionResource> {
- constructor(serverApi: AxiosInstance, private webdavClient: WebDAV, private authService: AuthService, actions: ApiActions) {
+ constructor(serverApi: AxiosInstance, private keepWebdavClient: WebDAV, private authService: AuthService, actions: ApiActions) {
super(serverApi, "collections", actions, [
- 'fileCount',
- 'fileSizeTotal',
- 'replicationConfirmed',
- 'replicationConfirmedAt',
- 'storageClassesConfirmed',
- 'storageClassesConfirmedAt',
- 'unsignedManifestText',
- 'version',
+ "fileCount",
+ "fileSizeTotal",
+ "replicationConfirmed",
+ "replicationConfirmedAt",
+ "storageClassesConfirmed",
+ "storageClassesConfirmedAt",
+ "unsignedManifestText",
+ "version",
]);
}
const payload = {
collection: {
preserve_version: true,
+ ...CommonService.mapKeys(snakeCase)(data),
+ // Don't send uuid in payload when creating
+ uuid: undefined,
},
- replace_files: fileMap
+ replace_files: fileMap,
};
-
- return CommonService.defaultResponse(
- this.serverApi.put<CollectionResource>(`/${this.resourceType}/${collectionUuid}`, payload),
- this.actions,
- true, // mapKeys
- showErrors
- );
+ if (data.uuid) {
+ return CommonService.defaultResponse(
- this.serverApi
- .put<CollectionResource>(`/${this.resourceType}/${data.uuid}`, payload),
++ this.serverApi.put<CollectionResource>(`/${this.resourceType}/${data.uuid}`, payload),
+ this.actions,
+ true, // mapKeys
+ showErrors
+ );
+ } else {
+ return CommonService.defaultResponse(
- this.serverApi
- .post<CollectionResource>(`/${this.resourceType}`, payload),
++ this.serverApi.post<CollectionResource>(`/${this.resourceType}`, payload),
+ this.actions,
+ true, // mapKeys
+ showErrors
+ );
+ }
}
- async uploadFiles(collectionUuid: string, files: File[], onProgress?: UploadProgress, targetLocation: string = '') {
- if (collectionUuid === "" || files.length === 0) { return; }
+ async uploadFiles(collectionUuid: string, files: File[], onProgress?: UploadProgress, targetLocation: string = "") {
+ if (collectionUuid === "" || files.length === 0) {
+ return;
+ }
// files have to be uploaded sequentially
for (let idx = 0; idx < files.length; idx++) {
await this.uploadFile(collectionUuid, files[idx], idx, onProgress, targetLocation);
}
async renameFile(collectionUuid: string, collectionPdh: string, oldPath: string, newPath: string) {
- return this.replaceFiles(collectionUuid, {
- return this.replaceFiles({uuid: collectionUuid}, {
-- [this.combineFilePath([newPath])]: `${collectionPdh}${this.combineFilePath([oldPath])}`,
- [this.combineFilePath([oldPath])]: "",
- [this.combineFilePath([oldPath])]: '',
-- });
++ return this.replaceFiles(
++ { uuid: collectionUuid },
++ {
++ [this.combineFilePath([newPath])]: `${collectionPdh}${this.combineFilePath([oldPath])}`,
++ [this.combineFilePath([oldPath])]: "",
++ }
++ );
}
extendFileURL = (file: CollectionDirectory | CollectionFile) => {
- const baseUrl = this.webdavClient.getBaseUrl().endsWith("/") ? this.webdavClient.getBaseUrl().slice(0, -1) : this.webdavClient.getBaseUrl();
- const baseUrl = this.keepWebdavClient.getBaseUrl().endsWith('/')
++ const baseUrl = this.keepWebdavClient.getBaseUrl().endsWith("/")
+ ? this.keepWebdavClient.getBaseUrl().slice(0, -1)
+ : this.keepWebdavClient.getBaseUrl();
const apiToken = this.authService.getApiToken();
- const encodedApiToken = apiToken ? encodeURI(apiToken) : '';
+ const encodedApiToken = apiToken ? encodeURI(apiToken) : "";
const userApiToken = `/t=${encodedApiToken}/`;
- const splittedPrevFileUrl = file.url.split('/');
- const url = `${baseUrl}/${splittedPrevFileUrl[1]}${userApiToken}${splittedPrevFileUrl.slice(2).join('/')}`;
+ const splittedPrevFileUrl = file.url.split("/");
+ const url = `${baseUrl}/${splittedPrevFileUrl[1]}${userApiToken}${splittedPrevFileUrl.slice(2).join("/")}`;
return {
...file,
- url
+ url,
};
- }
+ };
async getFileContents(file: CollectionFile) {
- return (await this.webdavClient.get(`c=${file.id}`)).response;
+ return (await this.keepWebdavClient.get(`c=${file.id}`)).response;
}
- private async uploadFile(collectionUuid: string, file: File, fileId: number, onProgress: UploadProgress = () => { return; }, targetLocation: string = '') {
- const fileURL = `c=${targetLocation !== '' ? targetLocation : collectionUuid}/${file.name}`.replace('//', '/');
+ private async uploadFile(
+ collectionUuid: string,
+ file: File,
+ fileId: number,
+ onProgress: UploadProgress = () => {
+ return;
+ },
+ targetLocation: string = ""
+ ) {
+ const fileURL = `c=${targetLocation !== "" ? targetLocation : collectionUuid}/${file.name}`.replace("//", "/");
const requestConfig = {
headers: {
- 'Content-Type': 'text/octet-stream'
+ "Content-Type": "text/octet-stream",
},
onUploadProgress: (e: ProgressEvent) => {
onProgress(fileId, e.loaded, e.total, Date.now());
const fileMap = optimizedFiles.reduce((obj, filePath) => {
return {
...obj,
- [this.combineFilePath([filePath])]: ''
- }
- }, {})
+ [this.combineFilePath([filePath])]: "",
+ };
+ }, {});
- return this.replaceFiles(collectionUuid, fileMap, showErrors);
- return this.replaceFiles({uuid: collectionUuid}, fileMap, showErrors);
++ return this.replaceFiles({ uuid: collectionUuid }, fileMap, showErrors);
}
- copyFiles(sourcePdh: string, files: string[], destinationCollectionUuid: string, destinationPath: string, showErrors?: boolean) {
- copyFiles(sourcePdh: string, files: string[], destinationCollection: CollectionPartialUpdateOrCreate, destinationPath: string, showErrors?: boolean) {
++ copyFiles(
++ sourcePdh: string,
++ files: string[],
++ destinationCollection: CollectionPartialUpdateOrCreate,
++ destinationPath: string,
++ showErrors?: boolean
++ ) {
const fileMap = files.reduce((obj, sourceFile) => {
- const sourceFileName = sourceFile.split("/").filter(Boolean).slice(-1).join("");
- const fileBasename = sourceFile.split('/').filter(Boolean).slice(-1).join("");
++ const fileBasename = sourceFile.split("/").filter(Boolean).slice(-1).join("");
return {
...obj,
- [this.combineFilePath([destinationPath, sourceFileName])]: `${sourcePdh}${this.combineFilePath([sourceFile])}`,
- [this.combineFilePath([destinationPath, fileBasename])]: `${sourcePdh}${this.combineFilePath([sourceFile])}`
++ [this.combineFilePath([destinationPath, fileBasename])]: `${sourcePdh}${this.combineFilePath([sourceFile])}`,
};
}, {});
- return this.replaceFiles(destinationCollectionUuid, fileMap, showErrors);
+ return this.replaceFiles(destinationCollection, fileMap, showErrors);
}
- moveFiles(sourceUuid: string, sourcePdh: string, files: string[], destinationCollection: CollectionPartialUpdateOrCreate, destinationPath: string, showErrors?: boolean) {
+ moveFiles(
+ sourceUuid: string,
+ sourcePdh: string,
+ files: string[],
- destinationCollectionUuid: string,
++ destinationCollection: CollectionPartialUpdateOrCreate,
+ destinationPath: string,
+ showErrors?: boolean
+ ) {
- if (sourceUuid === destinationCollectionUuid) {
+ if (sourceUuid === destinationCollection.uuid) {
+ let errors: CommonResourceServiceError[] = [];
const fileMap = files.reduce((obj, sourceFile) => {
- const sourceFileName = sourceFile.split("/").filter(Boolean).slice(-1).join("");
- return {
- ...obj,
- [this.combineFilePath([destinationPath, sourceFileName])]: `${sourcePdh}${this.combineFilePath([sourceFile])}`,
- [this.combineFilePath([sourceFile])]: "",
- };
- const fileBasename = sourceFile.split('/').filter(Boolean).slice(-1).join("");
++ const fileBasename = sourceFile.split("/").filter(Boolean).slice(-1).join("");
+ const fileDestinationPath = this.combineFilePath([destinationPath, fileBasename]);
+ const fileSourcePath = this.combineFilePath([sourceFile]);
+ const fileSourceUri = `${sourcePdh}${fileSourcePath}`;
+
-
+ if (fileDestinationPath !== fileSourcePath) {
+ return {
+ ...obj,
+ [fileDestinationPath]: fileSourceUri,
- [fileSourcePath]: '',
++ [fileSourcePath]: "",
+ };
+ } else {
+ errors.push(CommonResourceServiceError.SOURCE_DESTINATION_CANNOT_BE_SAME);
+ return obj;
+ }
}, {});
- return this.replaceFiles(sourceUuid, fileMap, showErrors);
+ if (errors.length === 0) {
- return this.replaceFiles({uuid: sourceUuid}, fileMap, showErrors)
++ return this.replaceFiles({ uuid: sourceUuid }, fileMap, showErrors);
+ } else {
- return Promise.reject({errors});
++ return Promise.reject({ errors });
+ }
} else {
- return this.copyFiles(sourcePdh, files, destinationCollectionUuid, destinationPath, showErrors).then(() => {
- return this.copyFiles(sourcePdh, files, destinationCollection, destinationPath, showErrors)
- .then(() => {
- return this.deleteFiles(sourceUuid, files, showErrors);
- });
++ return this.copyFiles(sourcePdh, files, destinationCollection, destinationPath, showErrors).then(() => {
+ return this.deleteFiles(sourceUuid, files, showErrors);
+ });
}
}
createDirectory(collectionUuid: string, path: string, showErrors?: boolean) {
- const fileMap = {[this.combineFilePath([path])]: emptyCollectionPdh};
+ const fileMap = { [this.combineFilePath([path])]: emptyCollectionPdh };
- return this.replaceFiles(collectionUuid, fileMap, showErrors);
- return this.replaceFiles({uuid: collectionUuid}, fileMap, showErrors);
++ return this.replaceFiles({ uuid: collectionUuid }, fileMap, showErrors);
}
-
}
//
// SPDX-License-Identifier: AGPL-3.0
-import { unionize, ofType, UnionOf } from 'common/unionize';
+import { unionize, ofType, UnionOf } from "common/unionize";
import { ContextMenuPosition } from "./context-menu-reducer";
-import { ContextMenuKind } from 'views-components/context-menu/context-menu';
-import { Dispatch } from 'redux';
-import { RootState } from 'store/store';
-import { getResource, getResourceWithEditableStatus } from '../resources/resources';
-import { UserResource } from 'models/user';
-import { isSidePanelTreeCategory } from 'store/side-panel-tree/side-panel-tree-actions';
-import { extractUuidKind, ResourceKind, EditableResource, Resource } from 'models/resource';
-import { Process } from 'store/processes/process';
-import { RepositoryResource } from 'models/repositories';
-import { SshKeyResource } from 'models/ssh-key';
-import { VirtualMachinesResource } from 'models/virtual-machines';
-import { KeepServiceResource } from 'models/keep-services';
-import { ProcessResource } from 'models/process';
-import { CollectionResource } from 'models/collection';
-import { GroupClass, GroupResource } from 'models/group';
-import { GroupContentsResource } from 'services/groups-service/groups-service';
-import { LinkResource } from 'models/link';
-import { resourceIsFrozen } from 'common/frozen-resources';
-import { ProjectResource } from 'models/project';
-import { filterCollectionFilesBySelection } from 'store/collection-panel/collection-panel-files/collection-panel-files-state';
+import { ContextMenuKind } from "views-components/context-menu/context-menu";
+import { Dispatch } from "redux";
+import { RootState } from "store/store";
+import { getResource, getResourceWithEditableStatus } from "../resources/resources";
+import { UserResource } from "models/user";
+import { isSidePanelTreeCategory } from "store/side-panel-tree/side-panel-tree-actions";
+import { extractUuidKind, ResourceKind, EditableResource, Resource } from "models/resource";
+import { Process } from "store/processes/process";
+import { RepositoryResource } from "models/repositories";
+import { SshKeyResource } from "models/ssh-key";
+import { VirtualMachinesResource } from "models/virtual-machines";
+import { KeepServiceResource } from "models/keep-services";
+import { ProcessResource } from "models/process";
+import { CollectionResource } from "models/collection";
+import { GroupClass, GroupResource } from "models/group";
+import { GroupContentsResource } from "services/groups-service/groups-service";
+import { LinkResource } from "models/link";
+import { resourceIsFrozen } from "common/frozen-resources";
+import { ProjectResource } from "models/project";
++import { filterCollectionFilesBySelection } from "store/collection-panel/collection-panel-files/collection-panel-files-state";
export const contextMenuActions = unionize({
- OPEN_CONTEXT_MENU: ofType<{ position: ContextMenuPosition, resource: ContextMenuResource }>(),
- CLOSE_CONTEXT_MENU: ofType<{}>()
+ OPEN_CONTEXT_MENU: ofType<{ position: ContextMenuPosition; resource: ContextMenuResource }>(),
+ CLOSE_CONTEXT_MENU: ofType<{}>(),
});
export type ContextMenuAction = UnionOf<typeof contextMenuActions>;
import { ContainerResource } from "models/container";
import { ContainerRequestResource, ContainerRequestState } from "models/container-request";
import { FilterBuilder } from "services/api/filter-builder";
+import { selectedToArray } from "components/multiselect-toolbar/MultiselectToolbar";
+import { Resource, ResourceKind } from "models/resource";
+import { ContextMenuResource } from "store/context-menu/context-menu-actions";
+ import { CommonResourceServiceError, getCommonResourceServiceError } from "services/common-service/common-resource-service";
-export const loadProcess = (containerRequestUuid: string) =>
+export const loadProcess =
+ (containerRequestUuid: string) =>
async (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository): Promise<Process | undefined> => {
let containerRequest: ContainerRequestResource | undefined = undefined;
try {
"scheduling_parameters",
"started_at",
"state",
+ "subrequests_cost",
"uuid",
-]
+];
-export const cancelRunningWorkflow = (uuid: string) =>
- async (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => {
- try {
- const process = await services.containerRequestService.update(uuid, { priority: 0 });
- dispatch<any>(updateResources([process]));
- if (process.containerUuid) {
- const container = await services.containerService.get(process.containerUuid, false);
- dispatch<any>(updateResources([container]));
- }
- return process;
- } catch (e) {
- throw new Error('Could not cancel the process.');
+export const cancelRunningWorkflow = (uuid: string) => async (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => {
+ try {
+ const process = await services.containerRequestService.update(uuid, { priority: 0 });
+ dispatch<any>(updateResources([process]));
+ if (process.containerUuid) {
+ const container = await services.containerService.get(process.containerUuid, false);
+ dispatch<any>(updateResources([container]));
}
- };
+ return process;
+ } catch (e) {
+ throw new Error("Could not cancel the process.");
+ }
+};
-export const resumeOnHoldWorkflow = (uuid: string) =>
- async (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => {
- try {
- const process = await services.containerRequestService.update(uuid, { priority: 500 });
- dispatch<any>(updateResources([process]));
- if (process.containerUuid) {
- const container = await services.containerService.get(process.containerUuid, false);
- dispatch<any>(updateResources([container]));
- }
- return process;
- } catch (e) {
- throw new Error('Could not resume the process.');
+export const resumeOnHoldWorkflow = (uuid: string) => async (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => {
+ try {
+ const process = await services.containerRequestService.update(uuid, { priority: 500 });
+ dispatch<any>(updateResources([process]));
+ if (process.containerUuid) {
+ const container = await services.containerService.get(process.containerUuid, false);
+ dispatch<any>(updateResources([container]));
}
- };
+ return process;
+ } catch (e) {
+ throw new Error("Could not resume the process.");
+ }
+};
-export const startWorkflow = (uuid: string) =>
- async (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => {
- try {
- const process = await services.containerRequestService.update(uuid, { state: ContainerRequestState.COMMITTED });
- if (process) {
- dispatch<any>(updateResources([process]));
- dispatch(snackbarActions.OPEN_SNACKBAR({ message: 'Process started', hideDuration: 2000, kind: SnackbarKind.SUCCESS }));
- } else {
- dispatch<any>(snackbarActions.OPEN_SNACKBAR({ message: `Failed to start process`, kind: SnackbarKind.ERROR }));
- }
- } catch (e) {
+export const startWorkflow = (uuid: string) => async (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => {
+ try {
+ const process = await services.containerRequestService.update(uuid, { state: ContainerRequestState.COMMITTED });
+ if (process) {
+ dispatch<any>(updateResources([process]));
+ dispatch(snackbarActions.OPEN_SNACKBAR({ message: "Process started", hideDuration: 2000, kind: SnackbarKind.SUCCESS }));
+ } else {
dispatch<any>(snackbarActions.OPEN_SNACKBAR({ message: `Failed to start process`, kind: SnackbarKind.ERROR }));
}
- };
+ } catch (e) {
+ dispatch<any>(snackbarActions.OPEN_SNACKBAR({ message: `Failed to start process`, kind: SnackbarKind.ERROR }));
+ }
+};
-export const reRunProcess = (processUuid: string, workflowUuid: string) =>
- (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => {
+export const reRunProcess =
+ (processUuid: string, workflowUuid: string) => (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => {
const process = getResource<any>(processUuid)(getState().resources);
const workflows = getState().runProcessPanel.searchWorkflows;
const workflow = workflows.find(workflow => workflow.uuid === workflowUuid);
};
export const getOutputParameters = (data: any): CommandOutputParameter[] => {
- if (!data || !data.mounts || !data.mounts[MOUNT_PATH_CWL_WORKFLOW]) { return []; }
+ if (!data || !data.mounts || !data.mounts[MOUNT_PATH_CWL_WORKFLOW]) {
+ return [];
+ }
const outputs = getWorkflowOutputs(data.mounts[MOUNT_PATH_CWL_WORKFLOW].content);
- return outputs ? outputs.map(
- (it: any) => (
- {
- type: it.type,
- id: it.id,
- label: it.label,
- doc: it.doc
- }
- )
- ) : [];
+ return outputs
+ ? outputs.map((it: any) => ({
+ type: it.type,
+ id: it.id,
+ label: it.label,
+ doc: it.doc,
+ }))
+ : [];
};
-export const openRemoveProcessDialog = (uuid: string) =>
- (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => {
- dispatch(dialogActions.OPEN_DIALOG({
- id: REMOVE_PROCESS_DIALOG,
- data: {
- title: 'Remove process permanently',
- text: 'Are you sure you want to remove this process?',
- confirmButtonLabel: 'Remove',
- uuid
- }
- }));
+export const openRemoveProcessDialog =
+ (resource: ContextMenuResource, numOfProcesses: Number) => (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => {
+ const confirmationText =
+ numOfProcesses === 1
+ ? "Are you sure you want to remove this process?"
+ : `Are you sure you want to remove these ${numOfProcesses} processes?`;
+ const titleText = numOfProcesses === 1 ? "Remove process permanently" : "Remove processes permanently";
+
+ dispatch(
+ dialogActions.OPEN_DIALOG({
+ id: REMOVE_PROCESS_DIALOG,
+ data: {
+ title: titleText,
+ text: confirmationText,
+ confirmButtonLabel: "Remove",
+ uuid: resource.uuid,
+ resource,
+ },
+ })
+ );
};
-export const REMOVE_PROCESS_DIALOG = 'removeProcessDialog';
+export const REMOVE_PROCESS_DIALOG = "removeProcessDialog";
-export const removeProcessPermanently = (uuid: string) =>
- async (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => {
+export const removeProcessPermanently = (uuid: string) => async (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => {
+ const resource = getState().dialog.removeProcessDialog.data.resource;
+ const checkedList = getState().multiselect.checkedList;
+
+ const uuidsToRemove: string[] = resource.isSingle ? [resource.uuid] : selectedToArray(checkedList);
+
+ //if no items in checkedlist, default to normal context menu behavior
+ if (!uuidsToRemove.length) uuidsToRemove.push(uuid);
+
+ const processesToRemove = uuidsToRemove
+ .map(uuid => getResource(uuid)(getState().resources) as Resource)
+ .filter(resource => resource.kind === ResourceKind.PROCESS);
+
+ for (const process of processesToRemove) {
- dispatch(snackbarActions.OPEN_SNACKBAR({ message: "Removing ...", kind: SnackbarKind.INFO }));
- await services.containerRequestService.delete(process.uuid);
- dispatch(projectPanelActions.REQUEST_ITEMS());
- dispatch(snackbarActions.OPEN_SNACKBAR({ message: "Removed.", hideDuration: 2000, kind: SnackbarKind.SUCCESS }));
+ try {
- dispatch(snackbarActions.OPEN_SNACKBAR({ message: 'Removing ...', kind: SnackbarKind.INFO }));
++ dispatch(snackbarActions.OPEN_SNACKBAR({ message: "Removing ...", kind: SnackbarKind.INFO }));
+ await services.containerRequestService.delete(uuid, false);
+ dispatch(projectPanelActions.REQUEST_ITEMS());
- dispatch(snackbarActions.OPEN_SNACKBAR({ message: 'Removed.', hideDuration: 2000, kind: SnackbarKind.SUCCESS }));
++ dispatch(snackbarActions.OPEN_SNACKBAR({ message: "Removed.", hideDuration: 2000, kind: SnackbarKind.SUCCESS }));
+ } catch (e) {
+ const error = getCommonResourceServiceError(e);
+ if (error === CommonResourceServiceError.PERMISSION_ERROR_FORBIDDEN) {
+ dispatch(snackbarActions.OPEN_SNACKBAR({ message: `Access denied`, hideDuration: 2000, kind: SnackbarKind.ERROR }));
+ } else {
+ dispatch(snackbarActions.OPEN_SNACKBAR({ message: `Deletion failed`, hideDuration: 2000, kind: SnackbarKind.ERROR }));
+ }
+ }
- };
+ }
+};
//
// SPDX-License-Identifier: AGPL-3.0
- import { createStore, applyMiddleware, compose, Middleware, combineReducers, Store, Action, Dispatch } from 'redux';
- import { routerMiddleware, routerReducer } from 'react-router-redux';
- import thunkMiddleware from 'redux-thunk';
- import { History } from 'history';
- import { handleRedirects } from '../common/redirect-to';
-
- import { authReducer } from './auth/auth-reducer';
- import { authMiddleware } from './auth/auth-middleware';
- import { dataExplorerReducer } from './data-explorer/data-explorer-reducer';
- import { detailsPanelReducer } from './details-panel/details-panel-reducer';
- import { contextMenuReducer } from './context-menu/context-menu-reducer';
- import { reducer as formReducer } from 'redux-form';
- import { favoritesReducer } from './favorites/favorites-reducer';
- import { snackbarReducer } from './snackbar/snackbar-reducer';
- import { collectionPanelFilesReducer } from './collection-panel/collection-panel-files/collection-panel-files-reducer';
- import { dataExplorerMiddleware } from './data-explorer/data-explorer-middleware';
- import { FAVORITE_PANEL_ID } from './favorite-panel/favorite-panel-action';
- import { PROJECT_PANEL_ID } from './project-panel/project-panel-action';
- import { ProjectPanelMiddlewareService } from './project-panel/project-panel-middleware-service';
- import { FavoritePanelMiddlewareService } from './favorite-panel/favorite-panel-middleware-service';
- import { AllProcessesPanelMiddlewareService } from './all-processes-panel/all-processes-panel-middleware-service';
- import { collectionPanelReducer } from './collection-panel/collection-panel-reducer';
- import { dialogReducer } from './dialog/dialog-reducer';
- import { ServiceRepository } from 'services/services';
- import { treePickerReducer, treePickerSearchReducer } from './tree-picker/tree-picker-reducer';
- import { treePickerSearchMiddleware } from './tree-picker/tree-picker-middleware';
- import { resourcesReducer } from 'store/resources/resources-reducer';
- import { propertiesReducer } from './properties/properties-reducer';
- import { fileUploaderReducer } from './file-uploader/file-uploader-reducer';
- import { TrashPanelMiddlewareService } from 'store/trash-panel/trash-panel-middleware-service';
- import { TRASH_PANEL_ID } from 'store/trash-panel/trash-panel-action';
- import { processLogsPanelReducer } from './process-logs-panel/process-logs-panel-reducer';
- import { processPanelReducer } from 'store/process-panel/process-panel-reducer';
- import { SHARED_WITH_ME_PANEL_ID } from 'store/shared-with-me-panel/shared-with-me-panel-actions';
- import { SharedWithMeMiddlewareService } from './shared-with-me-panel/shared-with-me-middleware-service';
- import { progressIndicatorReducer } from './progress-indicator/progress-indicator-reducer';
- import { runProcessPanelReducer } from 'store/run-process-panel/run-process-panel-reducer';
- import { WorkflowMiddlewareService } from './workflow-panel/workflow-middleware-service';
- import { WORKFLOW_PANEL_ID } from './workflow-panel/workflow-panel-actions';
- import { appInfoReducer } from 'store/app-info/app-info-reducer';
- import { searchBarReducer } from './search-bar/search-bar-reducer';
- import { SEARCH_RESULTS_PANEL_ID } from 'store/search-results-panel/search-results-panel-actions';
- import { SearchResultsMiddlewareService } from './search-results-panel/search-results-middleware-service';
- import { virtualMachinesReducer } from 'store/virtual-machines/virtual-machines-reducer';
- import { repositoriesReducer } from 'store/repositories/repositories-reducer';
- import { keepServicesReducer } from 'store/keep-services/keep-services-reducer';
- import { UserMiddlewareService } from 'store/users/user-panel-middleware-service';
- import { USERS_PANEL_ID } from 'store/users/users-actions';
- import { UserProfileGroupsMiddlewareService } from 'store/user-profile/user-profile-groups-middleware-service';
- import { USER_PROFILE_PANEL_ID } from 'store/user-profile/user-profile-actions';
- import { GroupsPanelMiddlewareService } from 'store/groups-panel/groups-panel-middleware-service';
- import { GROUPS_PANEL_ID } from 'store/groups-panel/groups-panel-actions';
- import { GroupDetailsPanelMembersMiddlewareService } from 'store/group-details-panel/group-details-panel-members-middleware-service';
- import { GroupDetailsPanelPermissionsMiddlewareService } from 'store/group-details-panel/group-details-panel-permissions-middleware-service';
- import { GROUP_DETAILS_MEMBERS_PANEL_ID, GROUP_DETAILS_PERMISSIONS_PANEL_ID } from 'store/group-details-panel/group-details-panel-actions';
- import { LINK_PANEL_ID } from 'store/link-panel/link-panel-actions';
- import { LinkMiddlewareService } from 'store/link-panel/link-panel-middleware-service';
- import { API_CLIENT_AUTHORIZATION_PANEL_ID } from 'store/api-client-authorizations/api-client-authorizations-actions';
- import { ApiClientAuthorizationMiddlewareService } from 'store/api-client-authorizations/api-client-authorizations-middleware-service';
- import { PublicFavoritesMiddlewareService } from 'store/public-favorites-panel/public-favorites-middleware-service';
- import { PUBLIC_FAVORITE_PANEL_ID } from 'store/public-favorites-panel/public-favorites-action';
- import { publicFavoritesReducer } from 'store/public-favorites/public-favorites-reducer';
- import { linkAccountPanelReducer } from './link-account-panel/link-account-panel-reducer';
- import { CollectionsWithSameContentAddressMiddlewareService } from 'store/collections-content-address-panel/collections-content-address-middleware-service';
- import { COLLECTIONS_CONTENT_ADDRESS_PANEL_ID } from 'store/collections-content-address-panel/collections-content-address-panel-actions';
- import { ownerNameReducer } from 'store/owner-name/owner-name-reducer';
- import { SubprocessMiddlewareService } from 'store/subprocess-panel/subprocess-panel-middleware-service';
- import { SUBPROCESS_PANEL_ID } from 'store/subprocess-panel/subprocess-panel-actions';
- import { ALL_PROCESSES_PANEL_ID } from './all-processes-panel/all-processes-panel-action';
- import { Config } from 'common/config';
- import { pluginConfig } from 'plugins';
- import { MiddlewareListReducer } from 'common/plugintypes';
- import { tooltipsMiddleware } from './tooltips/tooltips-middleware';
- import { sidePanelReducer } from './side-panel/side-panel-reducer';
- import { bannerReducer } from './banner/banner-reducer';
- import { multiselectReducer } from './multiselect/multiselect-reducer';
-import { createStore, applyMiddleware, Middleware, combineReducers, Store, Action, Dispatch } from 'redux';
++import { createStore, applyMiddleware, compose, Middleware, combineReducers, Store, Action, Dispatch } from "redux";
+ import { routerMiddleware, routerReducer } from "react-router-redux";
-import thunkMiddleware from 'redux-thunk';
++import thunkMiddleware from "redux-thunk";
+ import { History } from "history";
-import { handleRedirects } from '../common/redirect-to';
++import { handleRedirects } from "../common/redirect-to";
+
+ import { authReducer } from "./auth/auth-reducer";
+ import { authMiddleware } from "./auth/auth-middleware";
-import { dataExplorerReducer } from './data-explorer/data-explorer-reducer';
-import { detailsPanelReducer } from './details-panel/details-panel-reducer';
-import { contextMenuReducer } from './context-menu/context-menu-reducer';
-import { reducer as formReducer } from 'redux-form';
-import { favoritesReducer } from './favorites/favorites-reducer';
-import { snackbarReducer } from './snackbar/snackbar-reducer';
-import { collectionPanelFilesReducer } from './collection-panel/collection-panel-files/collection-panel-files-reducer';
++import { dataExplorerReducer } from "./data-explorer/data-explorer-reducer";
++import { detailsPanelReducer } from "./details-panel/details-panel-reducer";
++import { contextMenuReducer } from "./context-menu/context-menu-reducer";
++import { reducer as formReducer } from "redux-form";
++import { favoritesReducer } from "./favorites/favorites-reducer";
++import { snackbarReducer } from "./snackbar/snackbar-reducer";
++import { collectionPanelFilesReducer } from "./collection-panel/collection-panel-files/collection-panel-files-reducer";
+ import { dataExplorerMiddleware } from "./data-explorer/data-explorer-middleware";
+ import { FAVORITE_PANEL_ID } from "./favorite-panel/favorite-panel-action";
+ import { PROJECT_PANEL_ID } from "./project-panel/project-panel-action";
+ import { ProjectPanelMiddlewareService } from "./project-panel/project-panel-middleware-service";
+ import { FavoritePanelMiddlewareService } from "./favorite-panel/favorite-panel-middleware-service";
+ import { AllProcessesPanelMiddlewareService } from "./all-processes-panel/all-processes-panel-middleware-service";
-import { collectionPanelReducer } from './collection-panel/collection-panel-reducer';
-import { dialogReducer } from './dialog/dialog-reducer';
++import { collectionPanelReducer } from "./collection-panel/collection-panel-reducer";
++import { dialogReducer } from "./dialog/dialog-reducer";
+ import { ServiceRepository } from "services/services";
-import { treePickerReducer, treePickerSearchReducer } from './tree-picker/tree-picker-reducer';
-import { treePickerSearchMiddleware } from './tree-picker/tree-picker-middleware';
-import { resourcesReducer } from 'store/resources/resources-reducer';
-import { propertiesReducer } from './properties/properties-reducer';
-import { fileUploaderReducer } from './file-uploader/file-uploader-reducer';
++import { treePickerReducer, treePickerSearchReducer } from "./tree-picker/tree-picker-reducer";
++import { treePickerSearchMiddleware } from "./tree-picker/tree-picker-middleware";
++import { resourcesReducer } from "store/resources/resources-reducer";
++import { propertiesReducer } from "./properties/properties-reducer";
++import { fileUploaderReducer } from "./file-uploader/file-uploader-reducer";
+ import { TrashPanelMiddlewareService } from "store/trash-panel/trash-panel-middleware-service";
+ import { TRASH_PANEL_ID } from "store/trash-panel/trash-panel-action";
-import { processLogsPanelReducer } from './process-logs-panel/process-logs-panel-reducer';
-import { processPanelReducer } from 'store/process-panel/process-panel-reducer';
-import { SHARED_WITH_ME_PANEL_ID } from 'store/shared-with-me-panel/shared-with-me-panel-actions';
-import { SharedWithMeMiddlewareService } from './shared-with-me-panel/shared-with-me-middleware-service';
-import { progressIndicatorReducer } from './progress-indicator/progress-indicator-reducer';
-import { runProcessPanelReducer } from 'store/run-process-panel/run-process-panel-reducer';
-import { WorkflowMiddlewareService } from './workflow-panel/workflow-middleware-service';
-import { WORKFLOW_PANEL_ID } from './workflow-panel/workflow-panel-actions';
-import { appInfoReducer } from 'store/app-info/app-info-reducer';
-import { searchBarReducer } from './search-bar/search-bar-reducer';
-import { SEARCH_RESULTS_PANEL_ID } from 'store/search-results-panel/search-results-panel-actions';
-import { SearchResultsMiddlewareService } from './search-results-panel/search-results-middleware-service';
++import { processLogsPanelReducer } from "./process-logs-panel/process-logs-panel-reducer";
++import { processPanelReducer } from "store/process-panel/process-panel-reducer";
++import { SHARED_WITH_ME_PANEL_ID } from "store/shared-with-me-panel/shared-with-me-panel-actions";
++import { SharedWithMeMiddlewareService } from "./shared-with-me-panel/shared-with-me-middleware-service";
++import { progressIndicatorReducer } from "./progress-indicator/progress-indicator-reducer";
++import { runProcessPanelReducer } from "store/run-process-panel/run-process-panel-reducer";
++import { WorkflowMiddlewareService } from "./workflow-panel/workflow-middleware-service";
++import { WORKFLOW_PANEL_ID } from "./workflow-panel/workflow-panel-actions";
++import { appInfoReducer } from "store/app-info/app-info-reducer";
++import { searchBarReducer } from "./search-bar/search-bar-reducer";
++import { SEARCH_RESULTS_PANEL_ID } from "store/search-results-panel/search-results-panel-actions";
++import { SearchResultsMiddlewareService } from "./search-results-panel/search-results-middleware-service";
+ import { virtualMachinesReducer } from "store/virtual-machines/virtual-machines-reducer";
-import { repositoriesReducer } from 'store/repositories/repositories-reducer';
-import { keepServicesReducer } from 'store/keep-services/keep-services-reducer';
-import { UserMiddlewareService } from 'store/users/user-panel-middleware-service';
-import { USERS_PANEL_ID } from 'store/users/users-actions';
-import { UserProfileGroupsMiddlewareService } from 'store/user-profile/user-profile-groups-middleware-service';
-import { USER_PROFILE_PANEL_ID } from 'store/user-profile/user-profile-actions'
-import { GroupsPanelMiddlewareService } from 'store/groups-panel/groups-panel-middleware-service';
-import { GROUPS_PANEL_ID } from 'store/groups-panel/groups-panel-actions';
-import { GroupDetailsPanelMembersMiddlewareService } from 'store/group-details-panel/group-details-panel-members-middleware-service';
-import { GroupDetailsPanelPermissionsMiddlewareService } from 'store/group-details-panel/group-details-panel-permissions-middleware-service';
-import { GROUP_DETAILS_MEMBERS_PANEL_ID, GROUP_DETAILS_PERMISSIONS_PANEL_ID } from 'store/group-details-panel/group-details-panel-actions';
-import { LINK_PANEL_ID } from 'store/link-panel/link-panel-actions';
-import { LinkMiddlewareService } from 'store/link-panel/link-panel-middleware-service';
-import { API_CLIENT_AUTHORIZATION_PANEL_ID } from 'store/api-client-authorizations/api-client-authorizations-actions';
-import { ApiClientAuthorizationMiddlewareService } from 'store/api-client-authorizations/api-client-authorizations-middleware-service';
-import { PublicFavoritesMiddlewareService } from 'store/public-favorites-panel/public-favorites-middleware-service';
-import { PUBLIC_FAVORITE_PANEL_ID } from 'store/public-favorites-panel/public-favorites-action';
-import { publicFavoritesReducer } from 'store/public-favorites/public-favorites-reducer';
-import { linkAccountPanelReducer } from './link-account-panel/link-account-panel-reducer';
-import { CollectionsWithSameContentAddressMiddlewareService } from 'store/collections-content-address-panel/collections-content-address-middleware-service';
-import { COLLECTIONS_CONTENT_ADDRESS_PANEL_ID } from 'store/collections-content-address-panel/collections-content-address-panel-actions';
-import { ownerNameReducer } from 'store/owner-name/owner-name-reducer';
-import { SubprocessMiddlewareService } from 'store/subprocess-panel/subprocess-panel-middleware-service';
-import { SUBPROCESS_PANEL_ID } from 'store/subprocess-panel/subprocess-panel-actions';
-import { ALL_PROCESSES_PANEL_ID } from './all-processes-panel/all-processes-panel-action';
-import { Config } from 'common/config';
-import { pluginConfig } from 'plugins';
-import { MiddlewareListReducer } from 'common/plugintypes';
-import { tooltipsMiddleware } from './tooltips/tooltips-middleware';
-import { sidePanelReducer } from './side-panel/side-panel-reducer'
-import { bannerReducer } from './banner/banner-reducer';
-import { composeWithDevTools } from 'redux-devtools-extension';
++import { repositoriesReducer } from "store/repositories/repositories-reducer";
++import { keepServicesReducer } from "store/keep-services/keep-services-reducer";
++import { UserMiddlewareService } from "store/users/user-panel-middleware-service";
++import { USERS_PANEL_ID } from "store/users/users-actions";
++import { UserProfileGroupsMiddlewareService } from "store/user-profile/user-profile-groups-middleware-service";
++import { USER_PROFILE_PANEL_ID } from "store/user-profile/user-profile-actions";
++import { GroupsPanelMiddlewareService } from "store/groups-panel/groups-panel-middleware-service";
++import { GROUPS_PANEL_ID } from "store/groups-panel/groups-panel-actions";
++import { GroupDetailsPanelMembersMiddlewareService } from "store/group-details-panel/group-details-panel-members-middleware-service";
++import { GroupDetailsPanelPermissionsMiddlewareService } from "store/group-details-panel/group-details-panel-permissions-middleware-service";
++import { GROUP_DETAILS_MEMBERS_PANEL_ID, GROUP_DETAILS_PERMISSIONS_PANEL_ID } from "store/group-details-panel/group-details-panel-actions";
++import { LINK_PANEL_ID } from "store/link-panel/link-panel-actions";
++import { LinkMiddlewareService } from "store/link-panel/link-panel-middleware-service";
++import { API_CLIENT_AUTHORIZATION_PANEL_ID } from "store/api-client-authorizations/api-client-authorizations-actions";
++import { ApiClientAuthorizationMiddlewareService } from "store/api-client-authorizations/api-client-authorizations-middleware-service";
++import { PublicFavoritesMiddlewareService } from "store/public-favorites-panel/public-favorites-middleware-service";
++import { PUBLIC_FAVORITE_PANEL_ID } from "store/public-favorites-panel/public-favorites-action";
++import { publicFavoritesReducer } from "store/public-favorites/public-favorites-reducer";
++import { linkAccountPanelReducer } from "./link-account-panel/link-account-panel-reducer";
++import { CollectionsWithSameContentAddressMiddlewareService } from "store/collections-content-address-panel/collections-content-address-middleware-service";
++import { COLLECTIONS_CONTENT_ADDRESS_PANEL_ID } from "store/collections-content-address-panel/collections-content-address-panel-actions";
++import { ownerNameReducer } from "store/owner-name/owner-name-reducer";
++import { SubprocessMiddlewareService } from "store/subprocess-panel/subprocess-panel-middleware-service";
++import { SUBPROCESS_PANEL_ID } from "store/subprocess-panel/subprocess-panel-actions";
++import { ALL_PROCESSES_PANEL_ID } from "./all-processes-panel/all-processes-panel-action";
++import { Config } from "common/config";
++import { pluginConfig } from "plugins";
++import { MiddlewareListReducer } from "common/plugintypes";
++import { tooltipsMiddleware } from "./tooltips/tooltips-middleware";
++import { sidePanelReducer } from "./side-panel/side-panel-reducer";
++import { bannerReducer } from "./banner/banner-reducer";
++import { multiselectReducer } from "./multiselect/multiselect-reducer";
+
+declare global {
+ interface Window {
+ __REDUX_DEVTOOLS_EXTENSION_COMPOSE__?: typeof compose;
+ }
+}
+
- const composeEnhancers = (process.env.NODE_ENV === 'development' && window && window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__) || compose;
++const composeEnhancers = (process.env.NODE_ENV === "development" && window && window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__) || compose;
++import { composeWithDevTools } from "redux-devtools-extension";
export type RootState = ReturnType<ReturnType<typeof createRootReducer>>;
export function configureStore(history: History, services: ServiceRepository, config: Config): RootStore {
const rootReducer = createRootReducer(services);
- const projectPanelMiddleware = dataExplorerMiddleware(
- new ProjectPanelMiddlewareService(services, PROJECT_PANEL_ID)
- );
- const favoritePanelMiddleware = dataExplorerMiddleware(
- new FavoritePanelMiddlewareService(services, FAVORITE_PANEL_ID)
- );
- const allProcessessPanelMiddleware = dataExplorerMiddleware(
- new AllProcessesPanelMiddlewareService(services, ALL_PROCESSES_PANEL_ID)
- );
- const trashPanelMiddleware = dataExplorerMiddleware(
- new TrashPanelMiddlewareService(services, TRASH_PANEL_ID)
- );
- const searchResultsPanelMiddleware = dataExplorerMiddleware(
- new SearchResultsMiddlewareService(services, SEARCH_RESULTS_PANEL_ID)
- );
- const sharedWithMePanelMiddleware = dataExplorerMiddleware(
- new SharedWithMeMiddlewareService(services, SHARED_WITH_ME_PANEL_ID)
- );
- const workflowPanelMiddleware = dataExplorerMiddleware(
- new WorkflowMiddlewareService(services, WORKFLOW_PANEL_ID)
- );
- const userPanelMiddleware = dataExplorerMiddleware(
- new UserMiddlewareService(services, USERS_PANEL_ID)
- );
- const userProfileGroupsMiddleware = dataExplorerMiddleware(
- new UserProfileGroupsMiddlewareService(services, USER_PROFILE_PANEL_ID)
- );
- const groupsPanelMiddleware = dataExplorerMiddleware(
- new GroupsPanelMiddlewareService(services, GROUPS_PANEL_ID)
- );
+ const projectPanelMiddleware = dataExplorerMiddleware(new ProjectPanelMiddlewareService(services, PROJECT_PANEL_ID));
+ const favoritePanelMiddleware = dataExplorerMiddleware(new FavoritePanelMiddlewareService(services, FAVORITE_PANEL_ID));
+ const allProcessessPanelMiddleware = dataExplorerMiddleware(new AllProcessesPanelMiddlewareService(services, ALL_PROCESSES_PANEL_ID));
+ const trashPanelMiddleware = dataExplorerMiddleware(new TrashPanelMiddlewareService(services, TRASH_PANEL_ID));
+ const searchResultsPanelMiddleware = dataExplorerMiddleware(new SearchResultsMiddlewareService(services, SEARCH_RESULTS_PANEL_ID));
+ const sharedWithMePanelMiddleware = dataExplorerMiddleware(new SharedWithMeMiddlewareService(services, SHARED_WITH_ME_PANEL_ID));
+ const workflowPanelMiddleware = dataExplorerMiddleware(new WorkflowMiddlewareService(services, WORKFLOW_PANEL_ID));
+ const userPanelMiddleware = dataExplorerMiddleware(new UserMiddlewareService(services, USERS_PANEL_ID));
+ const userProfileGroupsMiddleware = dataExplorerMiddleware(new UserProfileGroupsMiddlewareService(services, USER_PROFILE_PANEL_ID));
+ const groupsPanelMiddleware = dataExplorerMiddleware(new GroupsPanelMiddlewareService(services, GROUPS_PANEL_ID));
- const groupDetailsPanelMembersMiddleware = dataExplorerMiddleware(new GroupDetailsPanelMembersMiddlewareService(services, GROUP_DETAILS_MEMBERS_PANEL_ID));
+ const groupDetailsPanelMembersMiddleware = dataExplorerMiddleware(
+ new GroupDetailsPanelMembersMiddlewareService(services, GROUP_DETAILS_MEMBERS_PANEL_ID)
+ );
const groupDetailsPanelPermissionsMiddleware = dataExplorerMiddleware(
new GroupDetailsPanelPermissionsMiddlewareService(services, GROUP_DETAILS_PERMISSIONS_PANEL_ID)
);
- const linkPanelMiddleware = dataExplorerMiddleware(
- new LinkMiddlewareService(services, LINK_PANEL_ID)
- );
+ const linkPanelMiddleware = dataExplorerMiddleware(new LinkMiddlewareService(services, LINK_PANEL_ID));
- const apiClientAuthorizationMiddlewareService = dataExplorerMiddleware(new ApiClientAuthorizationMiddlewareService(services, API_CLIENT_AUTHORIZATION_PANEL_ID));
+ const apiClientAuthorizationMiddlewareService = dataExplorerMiddleware(
+ new ApiClientAuthorizationMiddlewareService(services, API_CLIENT_AUTHORIZATION_PANEL_ID)
+ );
- const publicFavoritesMiddleware = dataExplorerMiddleware(
- new PublicFavoritesMiddlewareService(services, PUBLIC_FAVORITE_PANEL_ID)
- );
+ const publicFavoritesMiddleware = dataExplorerMiddleware(new PublicFavoritesMiddlewareService(services, PUBLIC_FAVORITE_PANEL_ID));
- const collectionsContentAddress = dataExplorerMiddleware(new CollectionsWithSameContentAddressMiddlewareService(services, COLLECTIONS_CONTENT_ADDRESS_PANEL_ID));
+ const collectionsContentAddress = dataExplorerMiddleware(
+ new CollectionsWithSameContentAddressMiddlewareService(services, COLLECTIONS_CONTENT_ADDRESS_PANEL_ID)
+ );
- const subprocessMiddleware = dataExplorerMiddleware(
- new SubprocessMiddlewareService(services, SUBPROCESS_PANEL_ID)
- );
+ const subprocessMiddleware = dataExplorerMiddleware(new SubprocessMiddlewareService(services, SUBPROCESS_PANEL_ID));
+
const redirectToMiddleware = (store: any) => (next: any) => (action: any) => {
const state = store.getState();
middlewares = pluginConfig.middlewares.reduce(reduceMiddlewaresFn, middlewares);
- const enhancer = composeEnhancers(applyMiddleware(redirectToMiddleware, ...middlewares));
- const enhancer = composeWithDevTools({/* options */ })(applyMiddleware(redirectToMiddleware, ...middlewares));
++ const enhancer = composeWithDevTools({
++ /* options */
++ })(applyMiddleware(redirectToMiddleware, ...middlewares));
return createStore(rootReducer, enhancer);
}
//
// SPDX-License-Identifier: AGPL-3.0
-import { Dispatch } from 'redux';
-import { RootState } from 'store/store';
-import { getUserUuid } from 'common/getuser';
-import { loadDetailsPanel } from 'store/details-panel/details-panel-action';
-import { snackbarActions, SnackbarKind } from 'store/snackbar/snackbar-actions';
-import {
- favoritePanelActions,
- loadFavoritePanel,
-} from 'store/favorite-panel/favorite-panel-action';
-import {
- getProjectPanelCurrentUuid,
- projectPanelActions,
- setIsProjectPanelTrashed,
-} from 'store/project-panel/project-panel-action';
+import { Dispatch } from "redux";
+import { RootState } from "store/store";
+import { getUserUuid } from "common/getuser";
+import { loadDetailsPanel } from "store/details-panel/details-panel-action";
+import { snackbarActions, SnackbarKind } from "store/snackbar/snackbar-actions";
+import { favoritePanelActions, loadFavoritePanel } from "store/favorite-panel/favorite-panel-action";
- import {
- getProjectPanelCurrentUuid,
- openProjectPanel,
- projectPanelActions,
- setIsProjectPanelTrashed,
- } from "store/project-panel/project-panel-action";
++import { getProjectPanelCurrentUuid, projectPanelActions, setIsProjectPanelTrashed } from "store/project-panel/project-panel-action";
import {
activateSidePanelTreeItem,
initSidePanelTree,
dispatch(groupDetailsPanelActions.loadGroupDetailsPanel(groupUuid));
});
-const finishLoadingProject =
- (project: GroupContentsResource | string) =>
- async (dispatch: Dispatch<any>) => {
- const uuid = typeof project === 'string' ? project : project.uuid;
- dispatch(loadDetailsPanel(uuid));
- if (typeof project !== 'string') {
- dispatch(updateResources([project]));
- }
- };
-
-const loadGroupContentsResource = async (params: {
- uuid: string;
- userUuid: string;
- services: ServiceRepository;
-}) => {
- const filters = new FilterBuilder()
- .addEqual('uuid', params.uuid)
- .getFilters();
- const { items } = await params.services.groupsService.contents(
- params.userUuid,
- {
- filters,
- recursive: true,
- includeTrash: true,
- }
- );
+const finishLoadingProject = (project: GroupContentsResource | string) => async (dispatch: Dispatch<any>) => {
+ const uuid = typeof project === "string" ? project : project.uuid;
- dispatch(openProjectPanel(uuid));
+ dispatch(loadDetailsPanel(uuid));
+ if (typeof project !== "string") {
+ dispatch(updateResources([project]));
+ }
+};
+
+const loadGroupContentsResource = async (params: { uuid: string; userUuid: string; services: ServiceRepository }) => {
+ const filters = new FilterBuilder().addEqual("uuid", params.uuid).getFilters();
+ const { items } = await params.services.groupsService.contents(params.userUuid, {
+ filters,
+ recursive: true,
+ includeTrash: true,
+ });
const resource = items.shift();
let handler: GroupContentsHandler;
if (resource) {
//
// SPDX-License-Identifier: AGPL-3.0
- import { ContextMenuActionSet } from '../context-menu-action-set';
- import { RemoveIcon, RenameIcon } from 'components/icon/icon';
- import { DownloadCollectionFileAction } from '../actions/download-collection-file-action';
- import { openFileRemoveDialog, openRenameFileDialog } from 'store/collection-panel/collection-panel-files/collection-panel-files-actions';
- import { CollectionFileViewerAction } from 'views-components/context-menu/actions/collection-file-viewer-action';
- import { CollectionCopyToClipboardAction } from '../actions/collection-copy-to-clipboard-action';
+ import { ContextMenuActionSet } from "../context-menu-action-set";
+ import { FileCopyIcon, FileMoveIcon, RemoveIcon, RenameIcon } from "components/icon/icon";
+ import { DownloadCollectionFileAction } from "../actions/download-collection-file-action";
-import { openFileRemoveDialog, openRenameFileDialog } from 'store/collection-panel/collection-panel-files/collection-panel-files-actions';
-import { CollectionFileViewerAction } from 'views-components/context-menu/actions/collection-file-viewer-action';
++import { openFileRemoveDialog, openRenameFileDialog } from "store/collection-panel/collection-panel-files/collection-panel-files-actions";
++import { CollectionFileViewerAction } from "views-components/context-menu/actions/collection-file-viewer-action";
+ import { CollectionCopyToClipboardAction } from "../actions/collection-copy-to-clipboard-action";
-import { openCollectionPartialMoveToExistingCollectionDialog, openCollectionPartialMoveToNewCollectionDialog } from "store/collections/collection-partial-move-actions";
-import { openCollectionPartialCopyToExistingCollectionDialog, openCollectionPartialCopyToNewCollectionDialog } from "store/collections/collection-partial-copy-actions";
++import {
++ openCollectionPartialMoveToExistingCollectionDialog,
++ openCollectionPartialMoveToNewCollectionDialog,
++} from "store/collections/collection-partial-move-actions";
++import {
++ openCollectionPartialCopyToExistingCollectionDialog,
++ openCollectionPartialCopyToNewCollectionDialog,
++} from "store/collections/collection-partial-copy-actions";
-export const readOnlyCollectionDirectoryItemActionSet: ContextMenuActionSet = [[
- {
- name: "Copy item into new collection",
- icon: FileCopyIcon,
- execute: (dispatch, resource) => {
- dispatch<any>(openCollectionPartialCopyToNewCollectionDialog(resource));
- }
- },
- {
- name: "Copy item into existing collection",
- icon: FileCopyIcon,
- execute: (dispatch, resource) => {
- dispatch<any>(openCollectionPartialCopyToExistingCollectionDialog(resource));
- }
- },
- {
- component: CollectionFileViewerAction,
- execute: () => { return; },
- },
- {
- component: CollectionCopyToClipboardAction,
- execute: () => { return; },
- }
-]];
+export const readOnlyCollectionDirectoryItemActionSet: ContextMenuActionSet = [
+ [
++ {
++ name: "Copy item into new collection",
++ icon: FileCopyIcon,
++ execute: (dispatch, resources) => {
++ dispatch<any>(openCollectionPartialCopyToNewCollectionDialog(resources[0]));
++ },
++ },
++ {
++ name: "Copy item into existing collection",
++ icon: FileCopyIcon,
++ execute: (dispatch, resources) => {
++ dispatch<any>(openCollectionPartialCopyToExistingCollectionDialog(resources[0]));
++ },
++ },
+ {
+ component: CollectionFileViewerAction,
+ execute: () => {
+ return;
+ },
+ },
+ {
+ component: CollectionCopyToClipboardAction,
+ execute: () => {
+ return;
+ },
+ },
+ ],
+];
-export const readOnlyCollectionFileItemActionSet: ContextMenuActionSet = [[
- {
- component: DownloadCollectionFileAction,
- execute: () => { return; }
- },
- ...readOnlyCollectionDirectoryItemActionSet.reduce((prev, next) => prev.concat(next), []),
-]];
+export const readOnlyCollectionFileItemActionSet: ContextMenuActionSet = [
+ [
+ {
+ component: DownloadCollectionFileAction,
+ execute: () => {
+ return;
+ },
+ },
+ ...readOnlyCollectionDirectoryItemActionSet.reduce((prev, next) => prev.concat(next), []),
+ ],
+];
-const writableActionSet: ContextMenuActionSet = [[
- {
- name: "Move item into new collection",
- icon: FileMoveIcon,
- execute: (dispatch, resource) => {
- dispatch<any>(openCollectionPartialMoveToNewCollectionDialog(resource));
- }
- },
- {
- name: "Move item into existing collection",
- icon: FileMoveIcon,
- execute: (dispatch, resource) => {
- dispatch<any>(openCollectionPartialMoveToExistingCollectionDialog(resource));
- }
- },
- {
- name: "Rename",
- icon: RenameIcon,
- execute: (dispatch, resource) => {
- dispatch<any>(openRenameFileDialog({
- name: resource.name,
- id: resource.uuid,
- path: resource.uuid.split('/').slice(1).join('/') }));
- }
- },
- {
- name: "Remove",
- icon: RemoveIcon,
- execute: (dispatch, resource) => {
- dispatch<any>(openFileRemoveDialog(resource.uuid));
- }
- }
-]];
+const writableActionSet: ContextMenuActionSet = [
+ [
+ {
- name: 'Rename',
++ name: "Move item into new collection",
++ icon: FileMoveIcon,
++ execute: (dispatch, resources) => {
++ dispatch<any>(openCollectionPartialMoveToNewCollectionDialog(resources[0]));
++ },
++ },
++ {
++ name: "Move item into existing collection",
++ icon: FileMoveIcon,
++ execute: (dispatch, resources) => {
++ dispatch<any>(openCollectionPartialMoveToExistingCollectionDialog(resources[0]));
++ },
++ },
++ {
++ name: "Rename",
+ icon: RenameIcon,
+ execute: (dispatch, resources) => {
- resources.forEach((resource) =>
- dispatch<any>(
- openRenameFileDialog({
- name: resource.name,
- id: resource.uuid,
- path: resource.uuid.split('/').slice(1).join('/'),
- })
- )
++ dispatch<any>(
++ openRenameFileDialog({
++ name: resources[0].name,
++ id: resources[0].uuid,
++ path: resources[0].uuid.split("/").slice(1).join("/"),
++ })
+ );
+ },
+ },
+ {
- name: 'Remove',
++ name: "Remove",
+ icon: RemoveIcon,
+ execute: (dispatch, resources) => {
- resources.forEach((resource) => dispatch<any>(openFileRemoveDialog(resource.uuid)));
++ dispatch<any>(openFileRemoveDialog(resources[0].uuid));
+ },
+ },
+ ],
+];
export const collectionDirectoryItemActionSet: ContextMenuActionSet = readOnlyCollectionDirectoryItemActionSet.concat(writableActionSet);
//
// SPDX-License-Identifier: AGPL-3.0
- import { ContextMenuActionSet } from 'views-components/context-menu/context-menu-action-set';
- import { openRunProcess } from 'store/workflow-panel/workflow-panel-actions';
- import { DetailsIcon, AdvancedIcon, OpenIcon, Link, StartIcon } from 'components/icon/icon';
- import { copyToClipboardAction, openInNewTabAction } from 'store/open-in-new-tab/open-in-new-tab.actions';
- import { toggleDetailsPanel } from 'store/details-panel/details-panel-action';
- import { openAdvancedTabDialog } from 'store/advanced-tab/advanced-tab';
+ import { ContextMenuActionSet } from "views-components/context-menu/context-menu-action-set";
+ import { openRunProcess, deleteWorkflow } from "store/workflow-panel/workflow-panel-actions";
-import {
- DetailsIcon,
- AdvancedIcon,
- OpenIcon,
- Link,
- StartIcon,
- TrashIcon
-} from "components/icon/icon";
++import { DetailsIcon, AdvancedIcon, OpenIcon, Link, StartIcon, TrashIcon } from "components/icon/icon";
+ import { copyToClipboardAction, openInNewTabAction } from "store/open-in-new-tab/open-in-new-tab.actions";
-import { toggleDetailsPanel } from 'store/details-panel/details-panel-action';
++import { toggleDetailsPanel } from "store/details-panel/details-panel-action";
+ import { openAdvancedTabDialog } from "store/advanced-tab/advanced-tab";
- export const workflowActionSet: ContextMenuActionSet = [
-export const readOnlyWorkflowActionSet: ContextMenuActionSet = [[
- {
- icon: OpenIcon,
- name: "Open in new tab",
- execute: (dispatch, resource) => {
- dispatch<any>(openInNewTabAction(resource));
- }
- },
- {
- icon: Link,
- name: "Copy to clipboard",
- execute: (dispatch, resource) => {
- dispatch<any>(copyToClipboardAction(resource));
- }
- },
- {
- icon: DetailsIcon,
- name: "View details",
- execute: dispatch => {
- dispatch<any>(toggleDetailsPanel());
- }
- },
- {
- icon: AdvancedIcon,
- name: "API Details",
- execute: (dispatch, resource) => {
- dispatch<any>(openAdvancedTabDialog(resource.uuid));
- }
- },
- {
- icon: StartIcon,
- name: "Run Workflow",
- execute: (dispatch, resource) => {
- dispatch<any>(openRunProcess(resource.uuid, resource.ownerUuid, resource.name));
- }
- }
-]];
++export const readOnlyWorkflowActionSet: ContextMenuActionSet = [
+ [
+ {
+ icon: OpenIcon,
- name: 'Open in new tab',
++ name: "Open in new tab",
+ execute: (dispatch, resources) => {
- resources.forEach((resource) => dispatch<any>(openInNewTabAction(resource)));
++ dispatch<any>(openInNewTabAction(resources[0]));
+ },
+ },
+ {
+ icon: Link,
- name: 'Copy to clipboard',
++ name: "Copy to clipboard",
+ execute: (dispatch, resources) => {
+ dispatch<any>(copyToClipboardAction(resources));
+ },
+ },
+ {
+ icon: DetailsIcon,
- name: 'View details',
- execute: (dispatch) => {
++ name: "View details",
++ execute: dispatch => {
+ dispatch<any>(toggleDetailsPanel());
+ },
+ },
+ {
+ icon: AdvancedIcon,
- name: 'API Details',
++ name: "API Details",
+ execute: (dispatch, resources) => {
- resources.forEach((resource) => dispatch<any>(openAdvancedTabDialog(resource.uuid)));
++ dispatch<any>(openAdvancedTabDialog(resources[0].uuid));
+ },
+ },
+ {
+ icon: StartIcon,
- name: 'Run Workflow',
++ name: "Run Workflow",
++ execute: (dispatch, resources) => {
++ dispatch<any>(openRunProcess(resources[0].uuid, resources[0].ownerUuid, resources[0].name));
++ },
++ },
++ ],
++];
+
-export const workflowActionSet: ContextMenuActionSet = [[
- ...readOnlyWorkflowActionSet[0],
- {
- icon: TrashIcon,
- name: "Delete Workflow",
- execute: (dispatch, resource) => {
- dispatch<any>(deleteWorkflow(resource.uuid, resource.ownerUuid));
- }
- },
-]];
++export const workflowActionSet: ContextMenuActionSet = [
++ [
++ ...readOnlyWorkflowActionSet[0],
++ {
++ icon: TrashIcon,
++ name: "Delete Workflow",
+ execute: (dispatch, resources) => {
- resources.forEach((resource) => dispatch<any>(openRunProcess(resource.uuid, resource.ownerUuid, resource.name)));
++ dispatch<any>(deleteWorkflow(resources[0].uuid, resources[0].ownerUuid));
+ },
+ },
+ ],
+];
COLLECTION_DIRECTORY_ITEM = "CollectionDirectoryItem",
READONLY_COLLECTION_FILE_ITEM = "ReadOnlyCollectionFileItem",
READONLY_COLLECTION_DIRECTORY_ITEM = "ReadOnlyCollectionDirectoryItem",
- COLLECTION_FILES_NOT_SELECTED = "CollectionFilesNotSelected",
- COLLECTION = 'Collection',
- COLLECTION_ADMIN = 'CollectionAdmin',
- READONLY_COLLECTION = 'ReadOnlyCollection',
- OLD_VERSION_COLLECTION = 'OldVersionCollection',
- TRASHED_COLLECTION = 'TrashedCollection',
+ COLLECTION = "Collection",
+ COLLECTION_ADMIN = "CollectionAdmin",
+ READONLY_COLLECTION = "ReadOnlyCollection",
+ OLD_VERSION_COLLECTION = "OldVersionCollection",
+ TRASHED_COLLECTION = "TrashedCollection",
PROCESS = "Process",
- PROCESS_ADMIN = 'ProcessAdmin',
- PROCESS_RESOURCE = 'ProcessResource',
- READONLY_PROCESS_RESOURCE = 'ReadOnlyProcessResource',
+ PROCESS_ADMIN = "ProcessAdmin",
+ PROCESS_RESOURCE = "ProcessResource",
+ READONLY_PROCESS_RESOURCE = "ReadOnlyProcessResource",
PROCESS_LOGS = "ProcessLogs",
REPOSITORY = "Repository",
SSH_KEY = "SshKey",
PERMISSION_EDIT = "PermissionEdit",
LINK = "Link",
WORKFLOW = "Workflow",
- SEARCH_RESULTS = "SearchResults"
+ READONLY_WORKFLOW = "ReadOnlyWorkflow",
+ SEARCH_RESULTS = "SearchResults",
}
//
// SPDX-License-Identifier: AGPL-3.0
++<<<<<<< HEAD
+import React from 'react';
+import { Grid, Typography, withStyles, Tooltip, IconButton, Checkbox, Chip } from '@material-ui/core';
+import { FavoriteStar, PublicFavoriteStar } from '../favorite-star/favorite-star';
+import { Resource, ResourceKind, TrashableResource } from 'models/resource';
++=======
+ import React from "react";
+ import { Grid, Typography, withStyles, Tooltip, IconButton, Checkbox, Chip } from "@material-ui/core";
+ import { FavoriteStar, PublicFavoriteStar } from "../favorite-star/favorite-star";
+ import { Resource, ResourceKind, TrashableResource } from "models/resource";
++>>>>>>> main
import {
FreezeIcon,
ProjectIcon,
ActiveIcon,
SetupIcon,
InactiveIcon,
++<<<<<<< HEAD
+} from 'components/icon/icon';
+import { formatDate, formatFileSize, formatTime } from 'common/formatters';
+import { resourceLabel } from 'common/labels';
+import { connect, DispatchProp } from 'react-redux';
+import { RootState } from 'store/store';
+import { getResource, filterResources } from 'store/resources/resources';
+import { GroupContentsResource } from 'services/groups-service/groups-service';
+import { getProcess, Process, getProcessStatus, getProcessStatusStyles, getProcessRuntime } from 'store/processes/process';
+import { ArvadosTheme } from 'common/custom-theme';
+import { compose, Dispatch } from 'redux';
+import { WorkflowResource } from 'models/workflow';
+import { ResourceStatus as WorkflowStatus } from 'views/workflow-panel/workflow-panel-view';
+import { getUuidPrefix, openRunProcess } from 'store/workflow-panel/workflow-panel-actions';
+import { openSharingDialog } from 'store/sharing-dialog/sharing-dialog-actions';
+import { getUserFullname, getUserDisplayName, User, UserResource } from 'models/user';
+import { toggleIsAdmin } from 'store/users/users-actions';
+import { LinkClass, LinkResource } from 'models/link';
+import { navigateTo, navigateToGroupDetails, navigateToUserProfile } from 'store/navigation/navigation-action';
+import { withResourceData } from 'views-components/data-explorer/with-resources';
+import { CollectionResource } from 'models/collection';
+import { IllegalNamingWarning } from 'components/warning/warning';
+import { loadResource } from 'store/resources/resources-actions';
+import { BuiltinGroups, getBuiltinGroupUuid, GroupClass, GroupResource, isBuiltinGroup } from 'models/group';
+import { openRemoveGroupMemberDialog } from 'store/group-details-panel/group-details-panel-actions';
+import { setMemberIsHidden } from 'store/group-details-panel/group-details-panel-actions';
+import { formatPermissionLevel } from 'views-components/sharing-dialog/permission-select';
+import { PermissionLevel } from 'models/permission';
+import { openPermissionEditContextMenu } from 'store/context-menu/context-menu-actions';
+import { getUserUuid } from 'common/getuser';
+import { VirtualMachinesResource } from 'models/virtual-machines';
+import { CopyToClipboardSnackbar } from 'components/copy-to-clipboard-snackbar/copy-to-clipboard-snackbar';
+import { ProjectResource } from 'models/project';
+import { ProcessResource } from 'models/process';
+
+const renderName = (dispatch: Dispatch, item: GroupContentsResource) => {
+ const navFunc = 'groupClass' in item && item.groupClass === GroupClass.ROLE ? navigateToGroupDetails : navigateTo;
+ return (
+ <Grid container alignItems='center' wrap='nowrap' spacing={16}>
+ <Grid item>{renderIcon(item)}</Grid>
+ <Grid item>
+ <Typography color='primary' style={{ width: 'auto', cursor: 'pointer' }} onClick={() => dispatch<any>(navFunc(item.uuid))}>
++=======
+ } from "components/icon/icon";
+ import { formatDate, formatFileSize, formatTime } from "common/formatters";
+ import { resourceLabel } from "common/labels";
+ import { connect, DispatchProp } from "react-redux";
+ import { RootState } from "store/store";
+ import { getResource, filterResources } from "store/resources/resources";
+ import { GroupContentsResource } from "services/groups-service/groups-service";
+ import { getProcess, Process, getProcessStatus, getProcessStatusStyles, getProcessRuntime } from "store/processes/process";
+ import { ArvadosTheme } from "common/custom-theme";
+ import { compose, Dispatch } from "redux";
+ import { WorkflowResource } from "models/workflow";
+ import { ResourceStatus as WorkflowStatus } from "views/workflow-panel/workflow-panel-view";
+ import { getUuidPrefix, openRunProcess } from "store/workflow-panel/workflow-panel-actions";
+ import { openSharingDialog } from "store/sharing-dialog/sharing-dialog-actions";
+ import { getUserFullname, getUserDisplayName, User, UserResource } from "models/user";
+ import { toggleIsAdmin } from "store/users/users-actions";
+ import { LinkClass, LinkResource } from "models/link";
+ import { navigateTo, navigateToGroupDetails, navigateToUserProfile } from "store/navigation/navigation-action";
+ import { withResourceData } from "views-components/data-explorer/with-resources";
+ import { CollectionResource } from "models/collection";
+ import { IllegalNamingWarning } from "components/warning/warning";
+ import { loadResource } from "store/resources/resources-actions";
+ import { BuiltinGroups, getBuiltinGroupUuid, GroupClass, GroupResource, isBuiltinGroup } from "models/group";
+ import { openRemoveGroupMemberDialog } from "store/group-details-panel/group-details-panel-actions";
+ import { setMemberIsHidden } from "store/group-details-panel/group-details-panel-actions";
+ import { formatPermissionLevel } from "views-components/sharing-dialog/permission-select";
+ import { PermissionLevel } from "models/permission";
+ import { openPermissionEditContextMenu } from "store/context-menu/context-menu-actions";
+ import { VirtualMachinesResource } from "models/virtual-machines";
+ import { CopyToClipboardSnackbar } from "components/copy-to-clipboard-snackbar/copy-to-clipboard-snackbar";
+ import { ProjectResource } from "models/project";
+ import { ProcessResource } from "models/process";
+
+ const renderName = (dispatch: Dispatch, item: GroupContentsResource) => {
+ const navFunc = "groupClass" in item && item.groupClass === GroupClass.ROLE ? navigateToGroupDetails : navigateTo;
+ return (
+ <Grid
+ container
+ alignItems="center"
+ wrap="nowrap"
+ spacing={16}
+ >
+ <Grid item>{renderIcon(item)}</Grid>
+ <Grid item>
+ <Typography
+ color="primary"
+ style={{ width: "auto", cursor: "pointer" }}
+ onClick={() => dispatch<any>(navFunc(item.uuid))}
+ >
++>>>>>>> main
{item.kind === ResourceKind.PROJECT || item.kind === ResourceKind.COLLECTION ? <IllegalNamingWarning name={item.name} /> : null}
{item.name}
</Typography>
</Grid>
<Grid item>
++<<<<<<< HEAD
+ <Typography variant='caption'>
++=======
+ <Typography variant="caption">
++>>>>>>> main
<FavoriteStar resourceUuid={item.uuid} />
<PublicFavoriteStar resourceUuid={item.uuid} />
{item.kind === ResourceKind.PROJECT && <FrozenProject item={item} />}
if (props.item.frozenByUuid) {
return (
++<<<<<<< HEAD
+ <Tooltip onOpen={getFullName} enterDelay={500} title={<span>Project was frozen by {fullUsername}</span>}>
+ <FreezeIcon style={{ fontSize: 'inherit' }} />
++=======
+ <Tooltip
+ onOpen={getFullName}
+ enterDelay={500}
+ title={<span>Project was frozen by {fullUsername}</span>}
+ >
+ <FreezeIcon style={{ fontSize: "inherit" }} />
++>>>>>>> main
</Tooltip>
);
} else {
const renderDate = (date?: string) => {
return (
++<<<<<<< HEAD
+ <Typography noWrap style={{ minWidth: '100px' }}>
++=======
+ <Typography
+ noWrap
+ style={{ minWidth: "100px" }}
+ >
++>>>>>>> main
{formatDate(date)}
</Typography>
);
};
const renderWorkflowName = (item: WorkflowResource) => (
++<<<<<<< HEAD
+ <Grid container alignItems='center' wrap='nowrap' spacing={16}>
+ <Grid item>{renderIcon(item)}</Grid>
+ <Grid item>
+ <Typography color='primary' style={{ width: '100px' }}>
++=======
+ <Grid
+ container
+ alignItems="center"
+ wrap="nowrap"
+ spacing={16}
+ >
+ <Grid item>{renderIcon(item)}</Grid>
+ <Grid item>
+ <Typography
+ color="primary"
+ style={{ width: "100px" }}
+ >
++>>>>>>> main
{item.name}
</Typography>
</Grid>
return (
<div>
{!isPublic && uuid && (
++<<<<<<< HEAD
+ <Tooltip title='Share'>
++=======
+ <Tooltip title="Share">
++>>>>>>> main
<IconButton onClick={() => dispatch<any>(openSharingDialog(uuid))}>
<ShareIcon />
</IconButton>
const resource = getResource<WorkflowResource>(props.uuid)(state.resources);
const uuidPrefix = getUuidPrefix(state);
return {
++<<<<<<< HEAD
+ uuid: resource ? resource.uuid : '',
+ ownerUuid: resource ? resource.ownerUuid : '',
++=======
+ uuid: resource ? resource.uuid : "",
+ ownerUuid: resource ? resource.ownerUuid : "",
++>>>>>>> main
uuidPrefix,
};
})((props: { ownerUuid?: string; uuidPrefix: string; uuid?: string } & DispatchProp<any>) =>
export const ResourceFirstName = connect((state: RootState, props: { uuid: string }) => {
const resource = getResource<UserResource>(props.uuid)(state.resources);
++<<<<<<< HEAD
+ return resource || { firstName: '' };
++=======
+ return resource || { firstName: "" };
++>>>>>>> main
})(renderFirstName);
const renderLastName = (item: { lastName: string }) => <Typography noWrap>{item.lastName}</Typography>;
export const ResourceLastName = connect((state: RootState, props: { uuid: string }) => {
const resource = getResource<UserResource>(props.uuid)(state.resources);
++<<<<<<< HEAD
+ return resource || { lastName: '' };
+})(renderLastName);
+
+const renderFullName = (dispatch: Dispatch, item: { uuid: string; firstName: string; lastName: string }, link?: boolean) => {
+ const displayName = (item.firstName + ' ' + item.lastName).trim() || item.uuid;
+ return link ? (
+ <Typography noWrap color='primary' style={{ cursor: 'pointer' }} onClick={() => dispatch<any>(navigateToUserProfile(item.uuid))}>
++=======
+ return resource || { lastName: "" };
+ })(renderLastName);
+
+ const renderFullName = (dispatch: Dispatch, item: { uuid: string; firstName: string; lastName: string }, link?: boolean) => {
+ const displayName = (item.firstName + " " + item.lastName).trim() || item.uuid;
+ return link ? (
+ <Typography
+ noWrap
+ color="primary"
+ style={{ cursor: "pointer" }}
+ onClick={() => dispatch<any>(navigateToUserProfile(item.uuid))}
+ >
++>>>>>>> main
{displayName}
</Typography>
) : (
export const UserResourceFullName = connect((state: RootState, props: { uuid: string; link?: boolean }) => {
const resource = getResource<UserResource>(props.uuid)(state.resources);
++<<<<<<< HEAD
+ return { item: resource || { uuid: '', firstName: '', lastName: '' }, link: props.link };
++=======
+ return { item: resource || { uuid: "", firstName: "", lastName: "" }, link: props.link };
++>>>>>>> main
})((props: { item: { uuid: string; firstName: string; lastName: string }; link?: boolean } & DispatchProp<any>) =>
renderFullName(props.dispatch, props.item, props.link)
);
const renderUuid = (item: { uuid: string }) => (
++<<<<<<< HEAD
+ <Typography data-cy='uuid' noWrap>
+ {item.uuid}
+ {(item.uuid && <CopyToClipboardSnackbar value={item.uuid} />) || '-'}
++=======
+ <Typography
+ data-cy="uuid"
+ noWrap
+ >
+ {item.uuid}
+ {(item.uuid && <CopyToClipboardSnackbar value={item.uuid} />) || "-"}
++>>>>>>> main
</Typography>
);
const renderUuidCopyIcon = (item: { uuid: string }) => (
++<<<<<<< HEAD
+ <Typography data-cy='uuid' noWrap>
+ {(item.uuid && <CopyToClipboardSnackbar value={item.uuid} />) || '-'}
+ </Typography>
+);
+
+export const ResourceUuid = connect((state: RootState, props: { uuid: string }) => getResource<UserResource>(props.uuid)(state.resources) || { uuid: '' })(renderUuid);
++=======
+ <Typography
+ data-cy="uuid"
+ noWrap
+ >
+ {(item.uuid && <CopyToClipboardSnackbar value={item.uuid} />) || "-"}
+ </Typography>
+ );
+
+ export const ResourceUuid = connect(
+ (state: RootState, props: { uuid: string }) => getResource<UserResource>(props.uuid)(state.resources) || { uuid: "" }
+ )(renderUuid);
++>>>>>>> main
const renderEmail = (item: { email: string }) => <Typography noWrap>{item.email}</Typography>;
export const ResourceEmail = connect((state: RootState, props: { uuid: string }) => {
const resource = getResource<UserResource>(props.uuid)(state.resources);
++<<<<<<< HEAD
+ return resource || { email: '' };
+})(renderEmail);
+
+enum UserAccountStatus {
+ ACTIVE = 'Active',
+ INACTIVE = 'Inactive',
+ SETUP = 'Setup',
+ UNKNOWN = '',
+}
+
+const renderAccountStatus = (props: { status: UserAccountStatus }) => (
+ <Grid container alignItems='center' wrap='nowrap' spacing={8} data-cy='account-status'>
++=======
+ return resource || { email: "" };
+ })(renderEmail);
+
+ enum UserAccountStatus {
+ ACTIVE = "Active",
+ INACTIVE = "Inactive",
+ SETUP = "Setup",
+ UNKNOWN = "",
+ }
+
+ const renderAccountStatus = (props: { status: UserAccountStatus }) => (
+ <Grid
+ container
+ alignItems="center"
+ wrap="nowrap"
+ spacing={8}
+ data-cy="account-status"
+ >
++>>>>>>> main
<Grid item>
{(() => {
switch (props.status) {
case UserAccountStatus.ACTIVE:
++<<<<<<< HEAD
+ return <ActiveIcon style={{ color: '#4caf50', verticalAlign: 'middle' }} />;
+ case UserAccountStatus.SETUP:
+ return <SetupIcon style={{ color: '#2196f3', verticalAlign: 'middle' }} />;
+ case UserAccountStatus.INACTIVE:
+ return <InactiveIcon style={{ color: '#9e9e9e', verticalAlign: 'middle' }} />;
++=======
+ return <ActiveIcon style={{ color: "#4caf50", verticalAlign: "middle" }} />;
+ case UserAccountStatus.SETUP:
+ return <SetupIcon style={{ color: "#2196f3", verticalAlign: "middle" }} />;
+ case UserAccountStatus.INACTIVE:
+ return <InactiveIcon style={{ color: "#9e9e9e", verticalAlign: "middle" }} />;
++>>>>>>> main
default:
return <></>;
}
if (props.memberLinkUuid) {
return (
<Checkbox
++<<<<<<< HEAD
+ data-cy='user-visible-checkbox'
+ color='primary'
+ checked={props.visible}
+ disabled={!props.canManage}
+ onClick={(e) => {
++=======
+ data-cy="user-visible-checkbox"
+ color="primary"
+ checked={props.visible}
+ disabled={!props.canManage}
+ onClick={e => {
++>>>>>>> main
e.stopPropagation();
props.setMemberIsHidden(props.memberLinkUuid, props.permissionLinkUuid, !props.visible);
}}
return member?.kind === ResourceKind.USER
? { memberLinkUuid: link?.uuid, permissionLinkUuid, visible: isVisible, canManage: !isBuiltin }
++<<<<<<< HEAD
+ : { memberLinkUuid: '', permissionLinkUuid: '', visible: false, canManage: false };
++=======
+ : { memberLinkUuid: "", permissionLinkUuid: "", visible: false, canManage: false };
++>>>>>>> main
},
{ setMemberIsHidden }
)(renderIsHidden);
const renderIsAdmin = (props: { uuid: string; isAdmin: boolean; toggleIsAdmin: (uuid: string) => void }) => (
<Checkbox
- color="primary"
+ color='primary'
checked={props.isAdmin}
- onClick={(e) => {
+ onClick={e => {
e.stopPropagation();
props.toggleIsAdmin(props.uuid);
}}
export const ResourceUsername = connect((state: RootState, props: { uuid: string }) => {
const resource = getResource<UserResource>(props.uuid)(state.resources);
++<<<<<<< HEAD
+ return resource || { username: '', uuid: props.uuid };
++=======
+ return resource || { username: "", uuid: props.uuid };
++>>>>>>> main
})(renderUsername);
// Virtual machine resource
export const VirtualMachineHostname = connect((state: RootState, props: { uuid: string }) => {
const resource = getResource<VirtualMachinesResource>(props.uuid)(state.resources);
++<<<<<<< HEAD
+ return resource || { hostname: '' };
++=======
+ return resource || { hostname: "" };
++>>>>>>> main
})(renderHostname);
const renderVirtualMachineLogin = (login: { user: string }) => <Typography noWrap>{login.user}</Typography>;
export const VirtualMachineLogin = connect((state: RootState, props: { linkUuid: string }) => {
const permission = getResource<LinkResource>(props.linkUuid)(state.resources);
++<<<<<<< HEAD
+ const user = getResource<UserResource>(permission?.tailUuid || '')(state.resources);
+
+ return { user: user?.username || permission?.tailUuid || '' };
++=======
+ const user = getResource<UserResource>(permission?.tailUuid || "")(state.resources);
+
+ return { user: user?.username || permission?.tailUuid || "" };
++>>>>>>> main
})(renderVirtualMachineLogin);
// Common methods
const renderCommonDate = (date: string) => <Typography noWrap>{formatDate(date)}</Typography>;
- export const CommonUuid = withResourceData('uuid', renderCommonData);
+ export const CommonUuid = withResourceData("uuid", renderCommonData);
// Api Client Authorizations
- export const TokenApiClientId = withResourceData('apiClientId', renderCommonData);
+ export const TokenApiClientId = withResourceData("apiClientId", renderCommonData);
- export const TokenApiToken = withResourceData('apiToken', renderCommonData);
+ export const TokenApiToken = withResourceData("apiToken", renderCommonData);
- export const TokenCreatedByIpAddress = withResourceData('createdByIpAddress', renderCommonDate);
+ export const TokenCreatedByIpAddress = withResourceData("createdByIpAddress", renderCommonDate);
- export const TokenDefaultOwnerUuid = withResourceData('defaultOwnerUuid', renderCommonData);
+ export const TokenDefaultOwnerUuid = withResourceData("defaultOwnerUuid", renderCommonData);
- export const TokenExpiresAt = withResourceData('expiresAt', renderCommonDate);
+ export const TokenExpiresAt = withResourceData("expiresAt", renderCommonDate);
- export const TokenLastUsedAt = withResourceData('lastUsedAt', renderCommonDate);
+ export const TokenLastUsedAt = withResourceData("lastUsedAt", renderCommonDate);
- export const TokenLastUsedByIpAddress = withResourceData('lastUsedByIpAddress', renderCommonData);
+ export const TokenLastUsedByIpAddress = withResourceData("lastUsedByIpAddress", renderCommonData);
- export const TokenScopes = withResourceData('scopes', renderCommonData);
+ export const TokenScopes = withResourceData("scopes", renderCommonData);
- export const TokenUserId = withResourceData('userId', renderCommonData);
+ export const TokenUserId = withResourceData("userId", renderCommonData);
const clusterColors = [
++<<<<<<< HEAD
+ ['#f44336', '#fff'],
+ ['#2196f3', '#fff'],
+ ['#009688', '#fff'],
+ ['#cddc39', '#fff'],
+ ['#ff9800', '#fff'],
++=======
+ ["#f44336", "#fff"],
+ ["#2196f3", "#fff"],
+ ["#009688", "#fff"],
+ ["#cddc39", "#fff"],
+ ["#ff9800", "#fff"],
++>>>>>>> main
];
export const ResourceCluster = (props: { uuid: string }) => {
const CLUSTER_ID_LENGTH = 5;
++<<<<<<< HEAD
+ const pos = props.uuid.length > CLUSTER_ID_LENGTH ? props.uuid.indexOf('-') : 5;
+ const clusterId = pos >= CLUSTER_ID_LENGTH ? props.uuid.substring(0, pos) : '';
+ const ci =
+ pos >= CLUSTER_ID_LENGTH
+ ? ((props.uuid.charCodeAt(0) * props.uuid.charCodeAt(1) + props.uuid.charCodeAt(2)) * props.uuid.charCodeAt(3) + props.uuid.charCodeAt(4)) %
++=======
+ const pos = props.uuid.length > CLUSTER_ID_LENGTH ? props.uuid.indexOf("-") : 5;
+ const clusterId = pos >= CLUSTER_ID_LENGTH ? props.uuid.substring(0, pos) : "";
+ const ci =
+ pos >= CLUSTER_ID_LENGTH
+ ? ((props.uuid.charCodeAt(0) * props.uuid.charCodeAt(1) + props.uuid.charCodeAt(2)) * props.uuid.charCodeAt(3) +
+ props.uuid.charCodeAt(4)) %
++>>>>>>> main
clusterColors.length
: 0;
return (
style={{
backgroundColor: clusterColors[ci][0],
color: clusterColors[ci][1],
++<<<<<<< HEAD
+ padding: '2px 7px',
++=======
+ padding: "2px 7px",
++>>>>>>> main
borderRadius: 3,
}}
>
};
// Links Resources
++<<<<<<< HEAD
+const renderLinkName = (item: { name: string }) => <Typography noWrap>{item.name || '-'}</Typography>;
+
+export const ResourceLinkName = connect((state: RootState, props: { uuid: string }) => {
+ const resource = getResource<LinkResource>(props.uuid)(state.resources);
+ return resource || { name: '' };
++=======
+ const renderLinkName = (item: { name: string }) => <Typography noWrap>{item.name || "-"}</Typography>;
+
+ export const ResourceLinkName = connect((state: RootState, props: { uuid: string }) => {
+ const resource = getResource<LinkResource>(props.uuid)(state.resources);
+ return resource || { name: "" };
++>>>>>>> main
})(renderLinkName);
const renderLinkClass = (item: { linkClass: string }) => <Typography noWrap>{item.linkClass}</Typography>;
export const ResourceLinkClass = connect((state: RootState, props: { uuid: string }) => {
const resource = getResource<LinkResource>(props.uuid)(state.resources);
++<<<<<<< HEAD
+ return resource || { linkClass: '' };
+})(renderLinkClass);
+
+const getResourceDisplayName = (resource: Resource): string => {
+ if ((resource as UserResource).kind === ResourceKind.USER && typeof (resource as UserResource).firstName !== 'undefined') {
++=======
+ return resource || { linkClass: "" };
+ })(renderLinkClass);
+
+ const getResourceDisplayName = (resource: Resource): string => {
+ if ((resource as UserResource).kind === ResourceKind.USER && typeof (resource as UserResource).firstName !== "undefined") {
++>>>>>>> main
// We can be sure the resource is UserResource
return getUserDisplayName(resource as UserResource);
} else {
var displayName = getResourceDisplayName(item);
return (
++<<<<<<< HEAD
+ <Typography noWrap color='primary' style={{ cursor: 'pointer' }} onClick={() => dispatch<any>(navigateTo(item.uuid))}>
+ {resourceLabel(item.kind, item && item.kind === ResourceKind.GROUP ? (item as GroupResource).groupClass || '' : '')}: {displayName || item.uuid}
++=======
+ <Typography
+ noWrap
+ color="primary"
+ style={{ cursor: "pointer" }}
+ onClick={() => {
+ console.log(item);
+ item.kind === ResourceKind.GROUP && (item as GroupResource).groupClass === "role"
+ ? dispatch<any>(navigateToGroupDetails(item.uuid))
+ : dispatch<any>(navigateTo(item.uuid));
+ }}
+ >
+ {resourceLabel(item.kind, item && item.kind === ResourceKind.GROUP ? (item as GroupResource).groupClass || "" : "")}:{" "}
+ {displayName || item.uuid}
++>>>>>>> main
</Typography>
);
};
export const ResourceLinkTail = connect((state: RootState, props: { uuid: string }) => {
const resource = getResource<LinkResource>(props.uuid)(state.resources);
++<<<<<<< HEAD
+ const tailResource = getResource<Resource>(resource?.tailUuid || '')(state.resources);
+
+ return {
+ item: tailResource || { uuid: resource?.tailUuid || '', kind: resource?.tailKind || ResourceKind.NONE },
++=======
+ const tailResource = getResource<Resource>(resource?.tailUuid || "")(state.resources);
+
+ return {
+ item: tailResource || { uuid: resource?.tailUuid || "", kind: resource?.tailKind || ResourceKind.NONE },
++>>>>>>> main
};
})((props: { item: Resource } & DispatchProp<any>) => renderResourceLink(props.dispatch, props.item));
export const ResourceLinkHead = connect((state: RootState, props: { uuid: string }) => {
const resource = getResource<LinkResource>(props.uuid)(state.resources);
++<<<<<<< HEAD
+ const headResource = getResource<Resource>(resource?.headUuid || '')(state.resources);
+
+ return {
+ item: headResource || { uuid: resource?.headUuid || '', kind: resource?.headKind || ResourceKind.NONE },
++=======
+ const headResource = getResource<Resource>(resource?.headUuid || "")(state.resources);
+
+ return {
+ item: headResource || { uuid: resource?.headUuid || "", kind: resource?.headKind || ResourceKind.NONE },
++>>>>>>> main
};
})((props: { item: Resource } & DispatchProp<any>) => renderResourceLink(props.dispatch, props.item));
export const ResourceLinkUuid = connect((state: RootState, props: { uuid: string }) => {
const resource = getResource<LinkResource>(props.uuid)(state.resources);
++<<<<<<< HEAD
+ return resource || { uuid: '' };
++=======
+ return resource || { uuid: "" };
++>>>>>>> main
})(renderUuid);
export const ResourceLinkHeadUuid = connect((state: RootState, props: { uuid: string }) => {
const link = getResource<LinkResource>(props.uuid)(state.resources);
++<<<<<<< HEAD
+ const headResource = getResource<Resource>(link?.headUuid || '')(state.resources);
+
+ return headResource || { uuid: '' };
++=======
+ const headResource = getResource<Resource>(link?.headUuid || "")(state.resources);
+
+ return headResource || { uuid: "" };
++>>>>>>> main
})(renderUuid);
export const ResourceLinkTailUuid = connect((state: RootState, props: { uuid: string }) => {
const link = getResource<LinkResource>(props.uuid)(state.resources);
++<<<<<<< HEAD
+ const tailResource = getResource<Resource>(link?.tailUuid || '')(state.resources);
+
+ return tailResource || { uuid: '' };
++=======
+ const tailResource = getResource<Resource>(link?.tailUuid || "")(state.resources);
+
+ return tailResource || { uuid: "" };
++>>>>>>> main
})(renderUuid);
const renderLinkDelete = (dispatch: Dispatch, item: LinkResource, canManage: boolean) => {
if (item.uuid) {
return canManage ? (
<Typography noWrap>
++<<<<<<< HEAD
+ <IconButton data-cy='resource-delete-button' onClick={() => dispatch<any>(openRemoveGroupMemberDialog(item.uuid))}>
++=======
+ <IconButton
+ data-cy="resource-delete-button"
+ onClick={() => dispatch<any>(openRemoveGroupMemberDialog(item.uuid))}
+ >
++>>>>>>> main
<RemoveIcon />
</IconButton>
</Typography>
) : (
<Typography noWrap>
++<<<<<<< HEAD
+ <IconButton disabled data-cy='resource-delete-button'>
++=======
+ <IconButton
+ disabled
+ data-cy="resource-delete-button"
+ >
++>>>>>>> main
<RemoveIcon />
</IconButton>
</Typography>
export const ResourceLinkDelete = connect((state: RootState, props: { uuid: string }) => {
const link = getResource<LinkResource>(props.uuid)(state.resources);
++<<<<<<< HEAD
+ const isBuiltin = isBuiltinGroup(link?.headUuid || '') || isBuiltinGroup(link?.tailUuid || '');
+
+ return {
+ item: link || { uuid: '', kind: ResourceKind.NONE },
++=======
+ const isBuiltin = isBuiltinGroup(link?.headUuid || "") || isBuiltinGroup(link?.tailUuid || "");
+
+ return {
+ item: link || { uuid: "", kind: ResourceKind.NONE },
++>>>>>>> main
canManage: link && getResourceLinkCanManage(state, link) && !isBuiltin,
};
})((props: { item: LinkResource; canManage: boolean } & DispatchProp<any>) => renderLinkDelete(props.dispatch, props.item, props.canManage));
export const ResourceLinkTailEmail = connect((state: RootState, props: { uuid: string }) => {
const link = getResource<LinkResource>(props.uuid)(state.resources);
++<<<<<<< HEAD
+ const resource = getResource<UserResource>(link?.tailUuid || '')(state.resources);
+
+ return resource || { email: '' };
++=======
+ const resource = getResource<UserResource>(link?.tailUuid || "")(state.resources);
+
+ return resource || { email: "" };
++>>>>>>> main
})(renderEmail);
export const ResourceLinkTailUsername = connect((state: RootState, props: { uuid: string }) => {
const link = getResource<LinkResource>(props.uuid)(state.resources);
++<<<<<<< HEAD
+ const resource = getResource<UserResource>(link?.tailUuid || '')(state.resources);
+
+ return resource || { username: '' };
++=======
+ const resource = getResource<UserResource>(link?.tailUuid || "")(state.resources);
+
+ return resource || { username: "" };
++>>>>>>> main
})(renderUsername);
const renderPermissionLevel = (dispatch: Dispatch, link: LinkResource, canManage: boolean) => {
<Typography noWrap>
{formatPermissionLevel(link.name as PermissionLevel)}
{canManage ? (
++<<<<<<< HEAD
+ <IconButton data-cy='edit-permission-button' onClick={(event) => dispatch<any>(openPermissionEditContextMenu(event, link))}>
+ <RenameIcon />
+ </IconButton>
+ ) : (
+ ''
++=======
+ <IconButton
+ data-cy="edit-permission-button"
+ onClick={event => dispatch<any>(openPermissionEditContextMenu(event, link))}
+ >
+ <RenameIcon />
+ </IconButton>
+ ) : (
+ ""
++>>>>>>> main
)}
</Typography>
);
export const ResourceLinkHeadPermissionLevel = connect((state: RootState, props: { uuid: string }) => {
const link = getResource<LinkResource>(props.uuid)(state.resources);
++<<<<<<< HEAD
+ const isBuiltin = isBuiltinGroup(link?.headUuid || '') || isBuiltinGroup(link?.tailUuid || '');
+
+ return {
+ link: link || { uuid: '', name: '', kind: ResourceKind.NONE },
++=======
+ const isBuiltin = isBuiltinGroup(link?.headUuid || "") || isBuiltinGroup(link?.tailUuid || "");
+
+ return {
+ link: link || { uuid: "", name: "", kind: ResourceKind.NONE },
++>>>>>>> main
canManage: link && getResourceLinkCanManage(state, link) && !isBuiltin,
};
})((props: { link: LinkResource; canManage: boolean } & DispatchProp<any>) => renderPermissionLevel(props.dispatch, props.link, props.canManage));
export const ResourceLinkTailPermissionLevel = connect((state: RootState, props: { uuid: string }) => {
const link = getResource<LinkResource>(props.uuid)(state.resources);
++<<<<<<< HEAD
+ const isBuiltin = isBuiltinGroup(link?.headUuid || '') || isBuiltinGroup(link?.tailUuid || '');
+
+ return {
+ link: link || { uuid: '', name: '', kind: ResourceKind.NONE },
++=======
+ const isBuiltin = isBuiltinGroup(link?.headUuid || "") || isBuiltinGroup(link?.tailUuid || "");
+
+ return {
+ link: link || { uuid: "", name: "", kind: ResourceKind.NONE },
++>>>>>>> main
canManage: link && getResourceLinkCanManage(state, link) && !isBuiltin,
};
})((props: { link: LinkResource; canManage: boolean } & DispatchProp<any>) => renderPermissionLevel(props.dispatch, props.link, props.canManage));
return (
<div>
{uuid && (
++<<<<<<< HEAD
+ <Tooltip title='Run process'>
++=======
+ <Tooltip title="Run process">
++>>>>>>> main
<IconButton onClick={() => dispatch<any>(openRunProcess(uuid))}>
<ProcessIcon />
</IconButton>
export const ResourceRunProcess = connect((state: RootState, props: { uuid: string }) => {
const resource = getResource<WorkflowResource>(props.uuid)(state.resources);
return {
++<<<<<<< HEAD
+ uuid: resource ? resource.uuid : '',
++=======
+ uuid: resource ? resource.uuid : "",
++>>>>>>> main
};
})((props: { uuid: string } & DispatchProp<any>) => resourceRunProcess(props.dispatch, props.uuid));
};
const renderStatus = (status: string) => (
++<<<<<<< HEAD
+ <Typography noWrap style={{ width: '60px' }}>
++=======
+ <Typography
+ noWrap
+ style={{ width: "60px" }}
+ >
++>>>>>>> main
{status}
</Typography>
);
const resource = getResource<WorkflowResource>(props.uuid)(state.resources);
const uuidPrefix = getUuidPrefix(state);
return {
++<<<<<<< HEAD
+ ownerUuid: resource ? resource.ownerUuid : '',
++=======
+ ownerUuid: resource ? resource.ownerUuid : "",
++>>>>>>> main
uuidPrefix,
};
})((props: { ownerUuid?: string; uuidPrefix: string }) => renderWorkflowStatus(props.uuidPrefix, props.ownerUuid));
export const ResourceContainerUuid = connect((state: RootState, props: { uuid: string }) => {
const process = getProcess(props.uuid)(state.resources);
++<<<<<<< HEAD
+ return { uuid: process?.container?.uuid ? process?.container?.uuid : '' };
+})((props: { uuid: string }) => renderUuid({ uuid: props.uuid }));
+
+enum ColumnSelection {
+ OUTPUT_UUID = 'outputUuid',
+ LOG_UUID = 'logUuid',
++=======
+ return { uuid: process?.container?.uuid ? process?.container?.uuid : "" };
+ })((props: { uuid: string }) => renderUuid({ uuid: props.uuid }));
+
+ enum ColumnSelection {
+ OUTPUT_UUID = "outputUuid",
+ LOG_UUID = "logUuid",
++>>>>>>> main
}
const renderUuidLinkWithCopyIcon = (dispatch: Dispatch, item: ProcessResource, column: string) => {
const selectedColumnUuid = item[column];
return (
++<<<<<<< HEAD
+ <Grid container alignItems='center' wrap='nowrap'>
+ <Grid item>
+ {selectedColumnUuid ? (
+ <Typography color='primary' style={{ width: 'auto', cursor: 'pointer' }} noWrap onClick={() => dispatch<any>(navigateTo(selectedColumnUuid))}>
+ {selectedColumnUuid}
+ </Typography>
+ ) : (
+ '-'
++=======
+ <Grid
+ container
+ alignItems="center"
+ wrap="nowrap"
+ >
+ <Grid item>
+ {selectedColumnUuid ? (
+ <Typography
+ color="primary"
+ style={{ width: "auto", cursor: "pointer" }}
+ noWrap
+ onClick={() => dispatch<any>(navigateTo(selectedColumnUuid))}
+ >
+ {selectedColumnUuid}
+ </Typography>
+ ) : (
+ "-"
++>>>>>>> main
)}
</Grid>
<Grid item>{selectedColumnUuid && renderUuidCopyIcon({ uuid: selectedColumnUuid })}</Grid>
export const ResourceParentProcess = connect((state: RootState, props: { uuid: string }) => {
const process = getProcess(props.uuid)(state.resources);
++<<<<<<< HEAD
+ return { parentProcess: process?.containerRequest?.requestingContainerUuid || '' };
++=======
+ return { parentProcess: process?.containerRequest?.requestingContainerUuid || "" };
++>>>>>>> main
})((props: { parentProcess: string }) => renderUuid({ uuid: props.parentProcess }));
export const ResourceModifiedByUserUuid = connect((state: RootState, props: { uuid: string }) => {
const process = getProcess(props.uuid)(state.resources);
++<<<<<<< HEAD
+ return { userUuid: process?.containerRequest?.modifiedByUserUuid || '' };
++=======
+ return { userUuid: process?.containerRequest?.modifiedByUserUuid || "" };
++>>>>>>> main
})((props: { userUuid: string }) => renderUuid({ uuid: props.userUuid }));
export const ResourceCreatedAtDate = connect((state: RootState, props: { uuid: string }) => {
const resource = getResource<GroupContentsResource>(props.uuid)(state.resources);
++<<<<<<< HEAD
+ return { date: resource ? resource.createdAt : '' };
++=======
+ return { date: resource ? resource.createdAt : "" };
++>>>>>>> main
})((props: { date: string }) => renderDate(props.date));
export const ResourceLastModifiedDate = connect((state: RootState, props: { uuid: string }) => {
const resource = getResource<GroupContentsResource>(props.uuid)(state.resources);
++<<<<<<< HEAD
+ return { date: resource ? resource.modifiedAt : '' };
++=======
+ return { date: resource ? resource.modifiedAt : "" };
++>>>>>>> main
})((props: { date: string }) => renderDate(props.date));
export const ResourceTrashDate = connect((state: RootState, props: { uuid: string }) => {
const resource = getResource<TrashableResource>(props.uuid)(state.resources);
++<<<<<<< HEAD
+ return { date: resource ? resource.trashAt : '' };
++=======
+ return { date: resource ? resource.trashAt : "" };
++>>>>>>> main
})((props: { date: string }) => renderDate(props.date));
export const ResourceDeleteDate = connect((state: RootState, props: { uuid: string }) => {
const resource = getResource<TrashableResource>(props.uuid)(state.resources);
++<<<<<<< HEAD
+ return { date: resource ? resource.deleteAt : '' };
+})((props: { date: string }) => renderDate(props.date));
+
+export const renderFileSize = (fileSize?: number) => (
+ <Typography noWrap style={{ minWidth: '45px' }}>
++=======
+ return { date: resource ? resource.deleteAt : "" };
+ })((props: { date: string }) => renderDate(props.date));
+
+ export const renderFileSize = (fileSize?: number) => (
+ <Typography
+ noWrap
+ style={{ minWidth: "45px" }}
+ >
++>>>>>>> main
{formatFileSize(fileSize)}
</Typography>
);
const resource = getResource<CollectionResource>(props.uuid)(state.resources);
if (resource && resource.kind !== ResourceKind.COLLECTION) {
++<<<<<<< HEAD
+ return { fileSize: '' };
++=======
+ return { fileSize: "" };
++>>>>>>> main
}
return { fileSize: resource ? resource.fileSizeTotal : 0 };
})((props: { fileSize?: number }) => renderFileSize(props.fileSize));
++<<<<<<< HEAD
+const renderOwner = (owner: string) => <Typography noWrap>{owner || '-'}</Typography>;
+
+export const ResourceOwner = connect((state: RootState, props: { uuid: string }) => {
+ const resource = getResource<GroupContentsResource>(props.uuid)(state.resources);
+ return { owner: resource ? resource.ownerUuid : '' };
++=======
+ const renderOwner = (owner: string) => <Typography noWrap>{owner || "-"}</Typography>;
+
+ export const ResourceOwner = connect((state: RootState, props: { uuid: string }) => {
+ const resource = getResource<GroupContentsResource>(props.uuid)(state.resources);
+ return { owner: resource ? resource.ownerUuid : "" };
++>>>>>>> main
})((props: { owner: string }) => renderOwner(props.owner));
export const ResourceOwnerName = connect((state: RootState, props: { uuid: string }) => {
const resource = getResource<GroupContentsResource>(props.uuid)(state.resources);
const ownerNameState = state.ownerName;
++<<<<<<< HEAD
+ const ownerName = ownerNameState.find((it) => it.uuid === resource!.ownerUuid);
++=======
+ const ownerName = ownerNameState.find(it => it.uuid === resource!.ownerUuid);
++>>>>>>> main
return { owner: ownerName ? ownerName!.name : resource!.ownerUuid };
})((props: { owner: string }) => renderOwner(props.owner));
export const ResourceUUID = connect((state: RootState, props: { uuid: string }) => {
const resource = getResource<CollectionResource>(props.uuid)(state.resources);
++<<<<<<< HEAD
+ return { uuid: resource ? resource.uuid : '' };
+})((props: { uuid: string }) => renderUuid({ uuid: props.uuid }));
+
+const renderVersion = (version: number) => {
+ return <Typography>{version ?? '-'}</Typography>;
++=======
+ return { uuid: resource ? resource.uuid : "" };
+ })((props: { uuid: string }) => renderUuid({ uuid: props.uuid }));
+
+ const renderVersion = (version: number) => {
+ return <Typography>{version ?? "-"}</Typography>;
++>>>>>>> main
};
export const ResourceVersion = connect((state: RootState, props: { uuid: string }) => {
const resource = getResource<CollectionResource>(props.uuid)(state.resources);
++<<<<<<< HEAD
+ return { version: resource ? resource.version : '' };
++=======
+ return { version: resource ? resource.version : "" };
++>>>>>>> main
})((props: { version: number }) => renderVersion(props.version));
const renderPortableDataHash = (portableDataHash: string | null) => (
<CopyToClipboardSnackbar value={portableDataHash} />
</>
) : (
++<<<<<<< HEAD
+ '-'
++=======
+ "-"
++>>>>>>> main
)}
</Typography>
);
export const ResourcePortableDataHash = connect((state: RootState, props: { uuid: string }) => {
const resource = getResource<CollectionResource>(props.uuid)(state.resources);
++<<<<<<< HEAD
+ return { portableDataHash: resource ? resource.portableDataHash : '' };
+})((props: { portableDataHash: string }) => renderPortableDataHash(props.portableDataHash));
+
+const renderFileCount = (fileCount: number) => {
+ return <Typography>{fileCount ?? '-'}</Typography>;
++=======
+ return { portableDataHash: resource ? resource.portableDataHash : "" };
+ })((props: { portableDataHash: string }) => renderPortableDataHash(props.portableDataHash));
+
+ const renderFileCount = (fileCount: number) => {
+ return <Typography>{fileCount ?? "-"}</Typography>;
++>>>>>>> main
};
export const ResourceFileCount = connect((state: RootState, props: { uuid: string }) => {
const resource = getResource<CollectionResource>(props.uuid)(state.resources);
++<<<<<<< HEAD
+ return { fileCount: resource ? resource.fileCount : '' };
+})((props: { fileCount: number }) => renderFileCount(props.fileCount));
+
+const userFromID = connect((state: RootState, props: { uuid: string }) => {
+ let userFullname = '';
++=======
+ return { fileCount: resource ? resource.fileCount : "" };
+ })((props: { fileCount: number }) => renderFileCount(props.fileCount));
+
+ const userFromID = connect((state: RootState, props: { uuid: string }) => {
+ let userFullname = "";
++>>>>>>> main
const resource = getResource<GroupContentsResource & UserResource>(props.uuid)(state.resources);
if (resource) {
const ownerFromResourceId = compose(
connect((state: RootState, props: { uuid: string }) => {
const childResource = getResource<GroupContentsResource & UserResource>(props.uuid)(state.resources);
++<<<<<<< HEAD
+ return { uuid: childResource ? (childResource as Resource).ownerUuid : '' };
++=======
+ return { uuid: childResource ? (childResource as Resource).ownerUuid : "" };
++>>>>>>> main
}),
userFromID
);
{ withTheme: true }
)((props: { uuid: string; userFullname: string; dispatch: Dispatch; theme: ArvadosTheme }) => {
const { uuid, userFullname, dispatch, theme } = props;
++<<<<<<< HEAD
+ if (userFullname === '') {
+ dispatch<any>(loadResource(uuid, false));
+ return (
+ <Typography style={{ color: theme.palette.primary.main }} inline noWrap>
++=======
+ if (userFullname === "") {
+ dispatch<any>(loadResource(uuid, false));
+ return (
+ <Typography
+ style={{ color: theme.palette.primary.main }}
+ inline
+ noWrap
+ >
++>>>>>>> main
{uuid}
</Typography>
);
}
return (
++<<<<<<< HEAD
+ <Typography style={{ color: theme.palette.primary.main }} inline noWrap>
++=======
+ <Typography
+ style={{ color: theme.palette.primary.main }}
+ inline
+ noWrap
+ >
++>>>>>>> main
{userFullname} ({uuid})
</Typography>
);
export const UserNameFromID = compose(userFromID)((props: { uuid: string; displayAsText?: string; userFullname: string; dispatch: Dispatch }) => {
const { uuid, userFullname, dispatch } = props;
++<<<<<<< HEAD
+ if (userFullname === '') {
++=======
+ if (userFullname === "") {
++>>>>>>> main
dispatch<any>(loadResource(uuid, false));
}
return <span>{userFullname ? userFullname : uuid}</span>;
export const ResponsiblePerson = compose(
connect((state: RootState, props: { uuid: string; parentRef: HTMLElement | null }) => {
++<<<<<<< HEAD
+ let responsiblePersonName: string = '';
+ let responsiblePersonUUID: string = '';
+ let responsiblePersonProperty: string = '';
++=======
+ let responsiblePersonName: string = "";
+ let responsiblePersonUUID: string = "";
+ let responsiblePersonProperty: string = "";
++>>>>>>> main
if (state.auth.config.clusterConfig.Collections.ManagedProperties) {
let index = 0;
while (!responsiblePersonProperty && keys[index]) {
const key = keys[index];
++<<<<<<< HEAD
+ if (state.auth.config.clusterConfig.Collections.ManagedProperties[key].Function === 'original_owner') {
++=======
+ if (state.auth.config.clusterConfig.Collections.ManagedProperties[key].Function === "original_owner") {
++>>>>>>> main
responsiblePersonProperty = key;
}
index++;
const { uuid, responsiblePersonName, parentRef, theme } = props;
if (!uuid && parentRef) {
++<<<<<<< HEAD
+ parentRef.style.display = 'none';
+ return null;
+ } else if (parentRef) {
+ parentRef.style.display = 'block';
++=======
+ parentRef.style.display = "none";
+ return null;
+ } else if (parentRef) {
+ parentRef.style.display = "block";
++>>>>>>> main
}
if (!responsiblePersonName) {
return (
++<<<<<<< HEAD
+ <Typography style={{ color: theme.palette.primary.main }} inline noWrap>
++=======
+ <Typography
+ style={{ color: theme.palette.primary.main }}
+ inline
+ noWrap
+ >
++>>>>>>> main
{uuid}
</Typography>
);
}
return (
++<<<<<<< HEAD
+ <Typography style={{ color: theme.palette.primary.main }} inline noWrap>
++=======
+ <Typography
+ style={{ color: theme.palette.primary.main }}
+ inline
+ noWrap
+ >
++>>>>>>> main
{responsiblePersonName} ({uuid})
</Typography>
);
export const ResourceType = connect((state: RootState, props: { uuid: string }) => {
const resource = getResource<GroupContentsResource>(props.uuid)(state.resources);
++<<<<<<< HEAD
+ return { type: resource ? resource.kind : '', subtype: resource && resource.kind === ResourceKind.GROUP ? resource.groupClass : '' };
++=======
+ return { type: resource ? resource.kind : "", subtype: resource && resource.kind === ResourceKind.GROUP ? resource.groupClass : "" };
++>>>>>>> main
})((props: { type: string; subtype: string }) => renderType(props.type, props.subtype));
export const ResourceStatus = connect((state: RootState, props: { uuid: string }) => {
return { resource: getResource<GroupContentsResource>(props.uuid)(state.resources) };
})((props: { resource: GroupContentsResource }) =>
++<<<<<<< HEAD
+ props.resource && props.resource.kind === ResourceKind.COLLECTION ? <CollectionStatus uuid={props.resource.uuid} /> : <ProcessStatus uuid={props.resource.uuid} />
++=======
+ props.resource && props.resource.kind === ResourceKind.COLLECTION ? (
+ <CollectionStatus uuid={props.resource.uuid} />
+ ) : (
+ <ProcessStatus uuid={props.resource.uuid} />
+ )
++>>>>>>> main
);
export const CollectionStatus = connect((state: RootState, props: { uuid: string }) => {
return { collection: getResource<CollectionResource>(props.uuid)(state.resources) };
})((props: { collection: CollectionResource }) =>
++<<<<<<< HEAD
+ props.collection.uuid !== props.collection.currentVersionUuid ? <Typography>version {props.collection.version}</Typography> : <Typography>head version</Typography>
++=======
+ props.collection.uuid !== props.collection.currentVersionUuid ? (
+ <Typography>version {props.collection.version}</Typography>
+ ) : (
+ <Typography>head version</Typography>
+ )
++>>>>>>> main
);
export const CollectionName = connect((state: RootState, props: { uuid: string; className?: string }) => {
height: props.theme.spacing.unit * 3,
width: props.theme.spacing.unit * 12,
...getProcessStatusStyles(getProcessStatus(props.process), props.theme),
++<<<<<<< HEAD
+ fontSize: '0.875rem',
++=======
+ fontSize: "0.875rem",
++>>>>>>> main
borderRadius: props.theme.spacing.unit * 0.625,
}}
/>
export const ProcessStartDate = connect((state: RootState, props: { uuid: string }) => {
const process = getProcess(props.uuid)(state.resources);
++<<<<<<< HEAD
+ return { date: process && process.container ? process.container.startedAt : '' };
+})((props: { date: string }) => renderDate(props.date));
+
+export const renderRunTime = (time: number) => (
+ <Typography noWrap style={{ minWidth: '45px' }}>
++=======
+ return { date: process && process.container ? process.container.startedAt : "" };
+ })((props: { date: string }) => renderDate(props.date));
+
+ export const renderRunTime = (time: number) => (
+ <Typography
+ noWrap
+ style={{ minWidth: "45px" }}
+ >
++>>>>>>> main
{formatTime(time, true)}
</Typography>
);
//
// SPDX-License-Identifier: AGPL-3.0
- import React, { ReactElement, useState } from "react";
-import React, { ReactElement, memo, useState } from 'react';
-import { Dispatch } from 'redux';
++import React, { ReactElement, memo, useState } from "react";
+import { Dispatch } from "redux";
import {
StyleRulesCallback,
WithStyles,
isPrimitiveOfType,
StringArrayCommandInputParameter,
StringCommandInputParameter,
- getEnumType
++ getEnumType,
} from "models/workflow";
-import { CommandOutputParameter } from 'cwlts/mappings/v1.0/CommandOutputParameter';
-import { File } from 'models/workflow';
-import { getInlineFileUrl } from 'views-components/context-menu/actions/helpers';
-import { AuthState } from 'store/auth/auth-reducer';
-import mime from 'mime';
-import { DefaultView } from 'components/default-view/default-view';
-import { getNavUrl } from 'routes/routes';
-import { Link as RouterLink } from 'react-router-dom';
-import { Link as MuiLink } from '@material-ui/core';
-import { InputCollectionMount } from 'store/processes/processes-actions';
-import { connect } from 'react-redux';
-import { RootState } from 'store/store';
-import { ProcessOutputCollectionFiles } from './process-output-collection-files';
-import { Process } from 'store/processes/process';
-import { navigateTo } from 'store/navigation/navigation-action';
-import classNames from 'classnames';
-import { DefaultCodeSnippet } from 'components/default-code-snippet/default-code-snippet';
-import { KEEP_URL_REGEX } from 'models/resource';
+import { CommandOutputParameter } from "cwlts/mappings/v1.0/CommandOutputParameter";
+import { File } from "models/workflow";
+import { getInlineFileUrl } from "views-components/context-menu/actions/helpers";
+import { AuthState } from "store/auth/auth-reducer";
+import mime from "mime";
+import { DefaultView } from "components/default-view/default-view";
+import { getNavUrl } from "routes/routes";
+import { Link as RouterLink } from "react-router-dom";
+import { Link as MuiLink } from "@material-ui/core";
+import { InputCollectionMount } from "store/processes/processes-actions";
+import { connect } from "react-redux";
+import { RootState } from "store/store";
+import { ProcessOutputCollectionFiles } from "./process-output-collection-files";
+import { Process } from "store/processes/process";
+import { navigateTo } from "store/navigation/navigation-action";
+import classNames from "classnames";
+import { DefaultCodeSnippet } from "components/default-code-snippet/default-code-snippet";
+import { KEEP_URL_REGEX } from "models/resource";
type CssRules =
| "card"
type ProcessIOCardProps = ProcessIOCardDataProps & ProcessIOCardActionProps & WithStyles<CssRules> & MPVPanelProps;
-export const ProcessIOCard = withStyles(styles)(connect(null, mapDispatchToProps)(
- ({ classes, label, params, raw, mounts, outputUuid, doHidePanel, doMaximizePanel,
- doUnMaximizePanel, panelMaximized, panelName, process, navigateTo, showParams }: ProcessIOCardProps) => {
- const [mainProcTabState, setMainProcTabState] = useState(0);
- const [subProcTabState, setSubProcTabState] = useState(0);
- const handleMainProcTabChange = (event: React.MouseEvent<HTMLElement>, value: number) => {
- setMainProcTabState(value);
- }
- const handleSubProcTabChange = (event: React.MouseEvent<HTMLElement>, value: number) => {
- setSubProcTabState(value);
- }
-
- const [showImagePreview, setShowImagePreview] = useState(false);
-
- const PanelIcon = label === ProcessIOCardType.INPUT ? InputIcon : OutputIcon;
- const mainProcess = !(process && process!.containerRequest.requestingContainerUuid);
-
- const loading = raw === null || raw === undefined || params === null;
- const hasRaw = !!(raw && Object.keys(raw).length > 0);
- const hasParams = !!(params && params.length > 0);
-
- // Subprocess
- const hasInputMounts = !!(label === ProcessIOCardType.INPUT && mounts && mounts.length);
- const hasOutputCollecton = !!(label === ProcessIOCardType.OUTPUT && outputUuid);
-
- return <Card className={classes.card} data-cy="process-io-card">
- <CardHeader
- className={classes.header}
- classes={{
- content: classes.title,
- avatar: classes.avatar,
- }}
- avatar={<PanelIcon className={classes.iconHeader} />}
- title={
- <Typography noWrap variant='h6' color='inherit'>
- {label}
- </Typography>
- }
- action={
- <div>
- {mainProcess && <Tooltip title={"Toggle Image Preview"} disableFocusListener>
- <IconButton data-cy="io-preview-image-toggle" onClick={() => { setShowImagePreview(!showImagePreview) }}>{showImagePreview ? <ImageIcon /> : <ImageOffIcon />}</IconButton>
- </Tooltip>}
- {doUnMaximizePanel && panelMaximized &&
- <Tooltip title={`Unmaximize ${panelName || 'panel'}`} disableFocusListener>
- <IconButton onClick={doUnMaximizePanel}><UnMaximizeIcon /></IconButton>
- </Tooltip>}
- {doMaximizePanel && !panelMaximized &&
- <Tooltip title={`Maximize ${panelName || 'panel'}`} disableFocusListener>
- <IconButton onClick={doMaximizePanel}><MaximizeIcon /></IconButton>
- </Tooltip>}
- {doHidePanel &&
- <Tooltip title={`Close ${panelName || 'panel'}`} disableFocusListener>
- <IconButton disabled={panelMaximized} onClick={doHidePanel}><CloseIcon /></IconButton>
- </Tooltip>}
- </div>
- } />
- <CardContent className={classes.content}>
- {(mainProcess || showParams) ?
- (<>
- {/* raw is undefined until params are loaded */}
- {loading && <Grid container item alignItems='center' justify='center'>
- <CircularProgress />
- </Grid>}
- {/* Once loaded, either raw or params may still be empty
- * Raw when all params are empty
- * Params when raw is provided by containerRequest properties but workflow mount is absent for preview
- */}
- {(!loading && (hasRaw || hasParams)) &&
+export const ProcessIOCard = withStyles(styles)(
+ connect(
+ null,
+ mapDispatchToProps
+ )(
+ ({
+ classes,
+ label,
+ params,
+ raw,
+ mounts,
+ outputUuid,
+ doHidePanel,
+ doMaximizePanel,
+ doUnMaximizePanel,
+ panelMaximized,
+ panelName,
+ process,
+ navigateTo,
+ showParams,
+ }: ProcessIOCardProps) => {
+ const [mainProcTabState, setMainProcTabState] = useState(0);
+ const [subProcTabState, setSubProcTabState] = useState(0);
+ const handleMainProcTabChange = (event: React.MouseEvent<HTMLElement>, value: number) => {
+ setMainProcTabState(value);
+ };
+ const handleSubProcTabChange = (event: React.MouseEvent<HTMLElement>, value: number) => {
+ setSubProcTabState(value);
+ };
+
+ const [showImagePreview, setShowImagePreview] = useState(false);
+
+ const PanelIcon = label === ProcessIOCardType.INPUT ? InputIcon : OutputIcon;
+ const mainProcess = !(process && process!.containerRequest.requestingContainerUuid);
+
+ const loading = raw === null || raw === undefined || params === null;
+ const hasRaw = !!(raw && Object.keys(raw).length > 0);
+ const hasParams = !!(params && params.length > 0);
+
+ // Subprocess
+ const hasInputMounts = !!(label === ProcessIOCardType.INPUT && mounts && mounts.length);
+ const hasOutputCollecton = !!(label === ProcessIOCardType.OUTPUT && outputUuid);
+
+ return (
+ <Card
+ className={classes.card}
- data-cy="process-io-card">
- {/* here */}
++ data-cy="process-io-card"
++ >
+ <CardHeader
+ className={classes.header}
+ classes={{
+ content: classes.title,
+ avatar: classes.avatar,
+ }}
+ avatar={<PanelIcon className={classes.iconHeader} />}
+ title={
+ <Typography
+ noWrap
+ variant="h6"
- color="inherit">
++ color="inherit"
++ >
+ {label}
+ </Typography>
+ }
+ action={
+ <div>
+ {mainProcess && (
+ <Tooltip
+ title={"Toggle Image Preview"}
- disableFocusListener>
++ disableFocusListener
++ >
+ <IconButton
+ data-cy="io-preview-image-toggle"
+ onClick={() => {
+ setShowImagePreview(!showImagePreview);
- }}>
++ }}
++ >
+ {showImagePreview ? <ImageIcon /> : <ImageOffIcon />}
+ </IconButton>
+ </Tooltip>
+ )}
+ {doUnMaximizePanel && panelMaximized && (
+ <Tooltip
+ title={`Unmaximize ${panelName || "panel"}`}
- disableFocusListener>
++ disableFocusListener
++ >
+ <IconButton onClick={doUnMaximizePanel}>
+ <UnMaximizeIcon />
+ </IconButton>
+ </Tooltip>
+ )}
+ {doMaximizePanel && !panelMaximized && (
+ <Tooltip
+ title={`Maximize ${panelName || "panel"}`}
- disableFocusListener>
++ disableFocusListener
++ >
+ <IconButton onClick={doMaximizePanel}>
+ <MaximizeIcon />
+ </IconButton>
+ </Tooltip>
+ )}
+ {doHidePanel && (
+ <Tooltip
+ title={`Close ${panelName || "panel"}`}
- disableFocusListener>
++ disableFocusListener
++ >
+ <IconButton
+ disabled={panelMaximized}
- onClick={doHidePanel}>
++ onClick={doHidePanel}
++ >
+ <CloseIcon />
+ </IconButton>
+ </Tooltip>
+ )}
+ </div>
+ }
+ />
+ <CardContent className={classes.content}>
+ {mainProcess || showParams ? (
<>
- <Tabs value={mainProcTabState} onChange={handleMainProcTabChange} variant="fullWidth" className={classes.symmetricTabs}>
- {/* params will be empty on processes without workflow definitions in mounts, so we only show raw */}
- {hasParams && <Tab label="Parameters" />}
- {!showParams && <Tab label="JSON" />}
- </Tabs>
- {(mainProcTabState === 0 && params && hasParams) && <div className={classes.tableWrapper}>
- <ProcessIOPreview data={params} showImagePreview={showImagePreview} valueLabel={showParams ? "Default value" : "Value"} />
- </div>}
- {(mainProcTabState === 1 || !hasParams) && <div className={classes.tableWrapper}>
- <ProcessIORaw data={raw} />
- </div>}
- </>}
- {!loading && !hasRaw && !hasParams && <Grid container item alignItems='center' justify='center'>
- <DefaultView messages={["No parameters found"]} />
- </Grid>}
- </>) :
- // Subprocess
- (<>
- {loading && <Grid container item alignItems='center' justify='center'>
- <CircularProgress />
- </Grid>}
- {!loading && (hasInputMounts || hasOutputCollecton || hasRaw) ?
+ {/* raw is undefined until params are loaded */}
+ {loading && (
+ <Grid
+ container
+ item
+ alignItems="center"
- justify="center">
++ justify="center"
++ >
+ <CircularProgress />
+ </Grid>
+ )}
+ {/* Once loaded, either raw or params may still be empty
+ * Raw when all params are empty
+ * Params when raw is provided by containerRequest properties but workflow mount is absent for preview
+ */}
+ {!loading && (hasRaw || hasParams) && (
+ <>
+ <Tabs
+ value={mainProcTabState}
+ onChange={handleMainProcTabChange}
+ variant="fullWidth"
- className={classes.symmetricTabs}>
++ className={classes.symmetricTabs}
++ >
+ {/* params will be empty on processes without workflow definitions in mounts, so we only show raw */}
+ {hasParams && <Tab label="Parameters" />}
+ {!showParams && <Tab label="JSON" />}
+ </Tabs>
+ {mainProcTabState === 0 && params && hasParams && (
+ <div className={classes.tableWrapper}>
+ <ProcessIOPreview
+ data={params}
+ showImagePreview={showImagePreview}
+ valueLabel={showParams ? "Default value" : "Value"}
+ />
+ </div>
+ )}
+ {(mainProcTabState === 1 || !hasParams) && (
+ <div className={classes.tableWrapper}>
+ <ProcessIORaw data={raw} />
+ </div>
+ )}
+ </>
+ )}
+ {!loading && !hasRaw && !hasParams && (
+ <Grid
+ container
+ item
+ alignItems="center"
- justify="center">
++ justify="center"
++ >
+ <DefaultView messages={["No parameters found"]} />
+ </Grid>
+ )}
+ </>
+ ) : (
+ // Subprocess
<>
- <Tabs value={subProcTabState} onChange={handleSubProcTabChange} variant="fullWidth" className={classes.symmetricTabs}>
- {hasInputMounts && <Tab label="Collections" />}
- {hasOutputCollecton && <Tab label="Collection" />}
- <Tab label="JSON" />
- </Tabs>
- <div className={classes.tableWrapper}>
- {subProcTabState === 0 && hasInputMounts && <ProcessInputMounts mounts={mounts || []} />}
- {subProcTabState === 0 && hasOutputCollecton && <>
- {outputUuid && <Typography className={classes.collectionLink}>
- Output Collection: <MuiLink className={classes.keepLink} onClick={() => { navigateTo(outputUuid || "") }}>
- {outputUuid}
- </MuiLink></Typography>}
- <ProcessOutputCollectionFiles isWritable={false} currentItemUuid={outputUuid} />
- </>}
- {(subProcTabState === 1 || (!hasInputMounts && !hasOutputCollecton)) && <div className={classes.tableWrapper}>
- <ProcessIORaw data={raw} />
- </div>}
- </div>
- </> :
- <Grid container item alignItems='center' justify='center'>
- <DefaultView messages={["No data to display"]} />
- </Grid>
- }
- </>)
- }
- </CardContent>
- </Card>;
- }
-));
+ {loading && (
+ <Grid
+ container
+ item
+ alignItems="center"
- justify="center">
++ justify="center"
++ >
+ <CircularProgress />
+ </Grid>
+ )}
+ {!loading && (hasInputMounts || hasOutputCollecton || hasRaw) ? (
+ <>
+ <Tabs
+ value={subProcTabState}
+ onChange={handleSubProcTabChange}
+ variant="fullWidth"
- className={classes.symmetricTabs}>
++ className={classes.symmetricTabs}
++ >
+ {hasInputMounts && <Tab label="Collections" />}
+ {hasOutputCollecton && <Tab label="Collection" />}
+ <Tab label="JSON" />
+ </Tabs>
+ <div className={classes.tableWrapper}>
+ {subProcTabState === 0 && hasInputMounts && <ProcessInputMounts mounts={mounts || []} />}
+ {subProcTabState === 0 && hasOutputCollecton && (
+ <>
+ {outputUuid && (
+ <Typography className={classes.collectionLink}>
+ Output Collection:{" "}
+ <MuiLink
+ className={classes.keepLink}
+ onClick={() => {
+ navigateTo(outputUuid || "");
- }}>
++ }}
++ >
+ {outputUuid}
+ </MuiLink>
+ </Typography>
+ )}
+ <ProcessOutputCollectionFiles
+ isWritable={false}
+ currentItemUuid={outputUuid}
+ />
+ </>
+ )}
+ {(subProcTabState === 1 || (!hasInputMounts && !hasOutputCollecton)) && (
+ <div className={classes.tableWrapper}>
+ <ProcessIORaw data={raw} />
+ </div>
+ )}
+ </div>
+ </>
+ ) : (
+ <Grid
+ container
+ item
+ alignItems="center"
- justify="center">
++ justify="center"
++ >
+ <DefaultView messages={["No data to display"]} />
+ </Grid>
+ )}
+ </>
+ )}
+ </CardContent>
+ </Card>
+ );
+ }
+ )
+);
export type ProcessIOValue = {
display: ReactElement<any, any>;
type ProcessIOPreviewProps = ProcessIOPreviewDataProps & WithStyles<CssRules>;
- const ProcessIOPreview = withStyles(styles)(({ classes, data, showImagePreview, valueLabel }: ProcessIOPreviewProps) => {
- const showLabel = data.some((param: ProcessIOParameter) => param.label);
- return (
- <Table
- className={classes.tableRoot}
- aria-label="Process IO Preview">
- <TableHead>
- <TableRow>
- <TableCell>Name</TableCell>
- {showLabel && <TableCell className={classes.labelColumn}>Label</TableCell>}
- <TableCell>{valueLabel}</TableCell>
- <TableCell>Collection</TableCell>
- </TableRow>
- </TableHead>
- <TableBody>
- {data.map((param: ProcessIOParameter) => {
- const firstVal = param.value.length > 0 ? param.value[0] : undefined;
- const rest = param.value.slice(1);
- const mainRowClasses = {
- [classes.noBorderRow]: rest.length > 0,
- };
-
- return (
- <React.Fragment key={param.id}>
- <TableRow
- className={classNames(mainRowClasses)}
- data-cy="process-io-param">
- <TableCell>{param.id}</TableCell>
- {showLabel && <TableCell>{param.label}</TableCell>}
- <TableCell>
- {firstVal && (
- <ProcessValuePreview
- value={firstVal}
- showImagePreview={showImagePreview}
- />
- )}
- </TableCell>
- <TableCell className={firstVal?.imageUrl ? classes.rowWithPreview : undefined}>
- <Typography className={classes.paramValue}>{firstVal?.collection}</Typography>
- </TableCell>
- </TableRow>
- {rest.map((val, i) => {
- const rowClasses = {
- [classes.noBorderRow]: i < rest.length - 1,
- [classes.secondaryRow]: val.secondary,
- };
- return (
- <TableRow
- className={classNames(rowClasses)}
- key={i}>
- <TableCell />
- {showLabel && <TableCell />}
- <TableCell>
-const ProcessIOPreview = memo(withStyles(styles)(
- ({ classes, data, showImagePreview, valueLabel }: ProcessIOPreviewProps) => {
++const ProcessIOPreview = memo(
++ withStyles(styles)(({ classes, data, showImagePreview, valueLabel }: ProcessIOPreviewProps) => {
+ const showLabel = data.some((param: ProcessIOParameter) => param.label);
- return <Table className={classes.tableRoot} aria-label="Process IO Preview">
- <TableHead>
- <TableRow>
- <TableCell>Name</TableCell>
- {showLabel && <TableCell className={classes.labelColumn}>Label</TableCell>}
- <TableCell>{valueLabel}</TableCell>
- <TableCell>Collection</TableCell>
- </TableRow>
- </TableHead>
- <TableBody>
- {data.map((param: ProcessIOParameter) => {
- const firstVal = param.value.length > 0 ? param.value[0] : undefined;
- const rest = param.value.slice(1);
- const mainRowClasses = {
- [classes.noBorderRow]: (rest.length > 0),
- };
-
- return <React.Fragment key={param.id}>
- <TableRow className={classNames(mainRowClasses)} data-cy="process-io-param">
- <TableCell>
- {param.id}
- </TableCell>
- {showLabel && <TableCell >{param.label}</TableCell>}
- <TableCell>
- {firstVal && <ProcessValuePreview value={firstVal} showImagePreview={showImagePreview} />}
- </TableCell>
- <TableCell className={firstVal?.imageUrl ? classes.rowWithPreview : undefined}>
- <Typography className={classes.paramValue}>
- {firstVal?.collection}
- </Typography>
- </TableCell>
- </TableRow>
- {rest.map((val, i) => {
- const rowClasses = {
- [classes.noBorderRow]: (i < rest.length - 1),
- [classes.secondaryRow]: val.secondary,
- };
- return <TableRow className={classNames(rowClasses)} key={i}>
- <TableCell />
- {showLabel && <TableCell />}
- <TableCell>
- <ProcessValuePreview value={val} showImagePreview={showImagePreview} />
- </TableCell>
- <TableCell className={firstVal?.imageUrl ? classes.rowWithPreview : undefined}>
- <Typography className={classes.paramValue}>
- {val.collection}
- </Typography>
- </TableCell>
- </TableRow>
- })}
- </React.Fragment>;
- })}
- </TableBody>
- </Table >;
- }));
++ return (
++ <Table
++ className={classes.tableRoot}
++ aria-label="Process IO Preview"
++ >
++ <TableHead>
++ <TableRow>
++ <TableCell>Name</TableCell>
++ {showLabel && <TableCell className={classes.labelColumn}>Label</TableCell>}
++ <TableCell>{valueLabel}</TableCell>
++ <TableCell>Collection</TableCell>
++ </TableRow>
++ </TableHead>
++ <TableBody>
++ {data.map((param: ProcessIOParameter) => {
++ const firstVal = param.value.length > 0 ? param.value[0] : undefined;
++ const rest = param.value.slice(1);
++ const mainRowClasses = {
++ [classes.noBorderRow]: rest.length > 0,
++ };
++
++ return (
++ <React.Fragment key={param.id}>
++ <TableRow
++ className={classNames(mainRowClasses)}
++ data-cy="process-io-param"
++ >
++ <TableCell>{param.id}</TableCell>
++ {showLabel && <TableCell>{param.label}</TableCell>}
++ <TableCell>
++ {firstVal && (
+ <ProcessValuePreview
- value={val}
++ value={firstVal}
+ showImagePreview={showImagePreview}
+ />
- </TableCell>
- <TableCell className={firstVal?.imageUrl ? classes.rowWithPreview : undefined}>
- <Typography className={classes.paramValue}>{val.collection}</Typography>
- </TableCell>
- </TableRow>
- );
- })}
- </React.Fragment>
- );
- })}
- </TableBody>
- </Table>
- );
- });
++ )}
++ </TableCell>
++ <TableCell className={firstVal?.imageUrl ? classes.rowWithPreview : undefined}>
++ <Typography className={classes.paramValue}>{firstVal?.collection}</Typography>
++ </TableCell>
++ </TableRow>
++ {rest.map((val, i) => {
++ const rowClasses = {
++ [classes.noBorderRow]: i < rest.length - 1,
++ [classes.secondaryRow]: val.secondary,
++ };
++ return (
++ <TableRow
++ className={classNames(rowClasses)}
++ key={i}
++ >
++ <TableCell />
++ {showLabel && <TableCell />}
++ <TableCell>
++ <ProcessValuePreview
++ value={val}
++ showImagePreview={showImagePreview}
++ />
++ </TableCell>
++ <TableCell className={firstVal?.imageUrl ? classes.rowWithPreview : undefined}>
++ <Typography className={classes.paramValue}>{val.collection}</Typography>
++ </TableCell>
++ </TableRow>
++ );
++ })}
++ </React.Fragment>
++ );
++ })}
++ </TableBody>
++ </Table>
++ );
++ })
++);
interface ProcessValuePreviewProps {
value: ProcessIOValue;
type ProcessInputMountsProps = ProcessInputMountsDataProps & WithStyles<CssRules>;
-const ProcessInputMounts = withStyles(styles)(connect((state: RootState) => ({
- auth: state.auth,
-}))(({ mounts, classes, auth }: ProcessInputMountsProps & { auth: AuthState }) => (
- <Table className={classes.tableRoot} aria-label="Process Input Mounts">
- <TableHead>
- <TableRow>
- <TableCell>Path</TableCell>
- <TableCell>Portable Data Hash</TableCell>
- </TableRow>
- </TableHead>
- <TableBody>
- {mounts.map(mount => (
- <TableRow key={mount.path}>
- <TableCell><pre>{mount.path}</pre></TableCell>
- <TableCell>
- <RouterLink to={getNavUrl(mount.pdh, auth)} className={classes.keepLink}>{mount.pdh}</RouterLink>
- </TableCell>
+const ProcessInputMounts = withStyles(styles)(
+ connect((state: RootState) => ({
+ auth: state.auth,
+ }))(({ mounts, classes, auth }: ProcessInputMountsProps & { auth: AuthState }) => (
+ <Table
+ className={classes.tableRoot}
- aria-label="Process Input Mounts">
++ aria-label="Process Input Mounts"
++ >
+ <TableHead>
+ <TableRow>
+ <TableCell>Path</TableCell>
+ <TableCell>Portable Data Hash</TableCell>
</TableRow>
- ))}
- </TableBody>
- </Table>
-)));
+ </TableHead>
+ <TableBody>
+ {mounts.map(mount => (
+ <TableRow key={mount.path}>
+ <TableCell>
+ <pre>{mount.path}</pre>
+ </TableCell>
+ <TableCell>
+ <RouterLink
+ to={getNavUrl(mount.pdh, auth)}
- className={classes.keepLink}>
++ className={classes.keepLink}
++ >
+ {mount.pdh}
+ </RouterLink>
+ </TableCell>
+ </TableRow>
+ ))}
+ </TableBody>
+ </Table>
+ ))
+);
type FileWithSecondaryFiles = {
secondaryFiles: File[];
case isPrimitiveOfType(input, CWLType.DIRECTORY):
const directory = (input as DirectoryCommandInputParameter).value;
- return directory !== undefined &&
- !(Array.isArray(directory) && directory.length === 0) ?
- [directoryToProcessIOValue(directory, auth, pdh)] :
- [{ display: <EmptyValue /> }];
+ return directory !== undefined && !(Array.isArray(directory) && directory.length === 0)
+ ? [directoryToProcessIOValue(directory, auth, pdh)]
+ : [{ display: <EmptyValue /> }];
- case typeof input.type === "object" && !(input.type instanceof Array) && input.type.type === "enum":
+ case getEnumType(input) !== null:
const enumValue = (input as EnumCommandInputParameter).value;
- return enumValue !== undefined && enumValue ?
- [{ display: <pre>{enumValue}</pre> }] :
- [{ display: <EmptyValue /> }];
+ return enumValue !== undefined && enumValue ? [{ display: <pre>{enumValue}</pre> }] : [{ display: <EmptyValue /> }];
case isArrayOfType(input, CWLType.STRING):
const strArray = (input as StringArrayCommandInputParameter).value || [];
case isArrayOfType(input, CWLType.FLOAT):
case isArrayOfType(input, CWLType.DOUBLE):
const floatArray = (input as FloatArrayCommandInputParameter).value || [];
- return floatArray.length ?
- [{ display: <>{floatArray.map((val) => renderPrimitiveValue(val, true))}</> }] :
- [{ display: <EmptyValue /> }];
+ return floatArray.length ? [{ display: <>{floatArray.map(val => renderPrimitiveValue(val, true))}</> }] : [{ display: <EmptyValue /> }];
case isArrayOfType(input, CWLType.FILE):
- const fileArrayMainFiles = ((input as FileArrayCommandInputParameter).value || []);
- const firstMainFilePdh = (fileArrayMainFiles.length > 0 && fileArrayMainFiles[0]) ? getResourcePdhUrl(fileArrayMainFiles[0], pdh) : "";
+ const fileArrayMainFiles = (input as FileArrayCommandInputParameter).value || [];
+ const firstMainFilePdh = fileArrayMainFiles.length > 0 && fileArrayMainFiles[0] ? getResourcePdhUrl(fileArrayMainFiles[0], pdh) : "";
- // Convert each main file into separate arrays of ProcessIOValue to preserve secondaryFile grouping
- const fileArrayValues = fileArrayMainFiles
- .map((mainFile: File, i): ProcessIOValue[] => {
- const secondaryFiles = (mainFile as unknown as FileWithSecondaryFiles)?.secondaryFiles || [];
- return [
- // Pass firstMainFilePdh to secondary files and every main file besides the first to hide pdh if equal
- ...(mainFile ? [fileToProcessIOValue(mainFile, false, auth, pdh, i > 0 ? firstMainFilePdh : "")] : []),
- ...secondaryFiles.map(file => fileToProcessIOValue(file, true, auth, pdh, firstMainFilePdh)),
- ];
- // Reduce each mainFile/secondaryFile group into single array preserving ordering
- })
- .reduce((acc: ProcessIOValue[], mainFile: ProcessIOValue[]) => acc.concat(mainFile), []);
+ // Convert each main and secondaryFiles into array of ProcessIOValue preserving ordering
+ let fileArrayValues: ProcessIOValue[] = [];
+ for (let i = 0; i < fileArrayMainFiles.length; i++) {
- const secondaryFiles = ((fileArrayMainFiles[i] as unknown) as FileWithSecondaryFiles)?.secondaryFiles || [];
++ const secondaryFiles = (fileArrayMainFiles[i] as unknown as FileWithSecondaryFiles)?.secondaryFiles || [];
+ fileArrayValues.push(
+ // Pass firstMainFilePdh to secondary files and every main file besides the first to hide pdh if equal
+ ...(fileArrayMainFiles[i] ? [fileToProcessIOValue(fileArrayMainFiles[i], false, auth, pdh, i > 0 ? firstMainFilePdh : "")] : []),
- ...(secondaryFiles.map(file => fileToProcessIOValue(file, true, auth, pdh, firstMainFilePdh)))
++ ...secondaryFiles.map(file => fileToProcessIOValue(file, true, auth, pdh, firstMainFilePdh))
+ );
+ }
- return fileArrayValues.length ?
- fileArrayValues :
- [{ display: <EmptyValue /> }];
+ return fileArrayValues.length ? fileArrayValues : [{ display: <EmptyValue /> }];
case isArrayOfType(input, CWLType.DIRECTORY):
const directories = (input as DirectoryArrayCommandInputParameter).value || [];
const pdhUrl = getResourcePdhUrl(res, pdh);
// Passing a pdh always returns a relative wb2 collection url
const pdhWbPath = getNavUrl(pdhUrl, auth);
- return pdhUrl && pdhWbPath ?
- <Tooltip title={"View collection in Workbench"}><RouterLink to={pdhWbPath} className={classes.keepLink}>{pdhUrl}</RouterLink></Tooltip> :
- <></>;
+ return pdhUrl && pdhWbPath ? (
+ <Tooltip title={"View collection in Workbench"}>
+ <RouterLink
+ to={pdhWbPath}
- className={classes.keepLink}>
++ className={classes.keepLink}
++ >
+ {pdhUrl}
+ </RouterLink>
+ </Tooltip>
+ ) : (
+ <></>
+ );
});
const KeepUrlPath = withStyles(styles)(({ auth, res, pdh, classes }: KeepUrlProps & WithStyles<CssRules>) => {
const keepUrl = getKeepUrl(res, pdh);
- const keepUrlParts = keepUrl ? keepUrl.split('/') : [];
- const keepUrlPath = keepUrlParts.length > 1 ? keepUrlParts.slice(1).join('/') : '';
+ const keepUrlParts = keepUrl ? keepUrl.split("/") : [];
+ const keepUrlPath = keepUrlParts.length > 1 ? keepUrlParts.slice(1).join("/") : "";
const keepUrlPathNav = getKeepNavUrl(auth, res, pdh);
- return keepUrlPathNav ?
- <Tooltip title={"View in keep-web"}><a className={classes.keepLink} href={keepUrlPathNav} target="_blank" rel="noopener noreferrer">{keepUrlPath || '/'}</a></Tooltip> :
- <EmptyValue />;
+ return keepUrlPathNav ? (
+ <Tooltip title={"View in keep-web"}>
+ <a
+ className={classes.keepLink}
+ href={keepUrlPathNav}
+ target="_blank"
- rel="noopener noreferrer">
++ rel="noopener noreferrer"
++ >
+ {keepUrlPath || "/"}
+ </a>
+ </Tooltip>
+ ) : (
+ <EmptyValue />
+ );
});
const getKeepNavUrl = (auth: AuthState, file: File | Directory, pdh?: string): string => {
if (isFileUrl(file.location)) {
return {
- display: <MuiLink href={file.location} target="_blank">{file.location}</MuiLink>,
+ display: (
+ <MuiLink
+ href={file.location}
- target="_blank">
++ target="_blank"
++ >
+ {file.location}
+ </MuiLink>
+ ),
secondary,
};
}
lines={getProcessPanelLogs(processLogsPanel)}
selectedFilter={{
label: processLogsPanel.selectedFilter,
- value: processLogsPanel.selectedFilter
+ value: processLogsPanel.selectedFilter,
}}
- filters={processLogsPanel.filters.map(
- filter => ({ label: filter, value: filter })
- )}
+ filters={processLogsPanel.filters.map(filter => ({ label: filter, value: filter }))}
onLogFilterChange={props.onLogFilterChange}
navigateToLog={props.navigateToLog}
+ pollProcessLogs={props.pollProcessLogs}
/>
</MPVPanelContent>
- <MPVPanelContent forwardProps xs maxHeight='50%' data-cy="process-inputs">
+ <MPVPanelContent
+ forwardProps
+ xs
+ maxHeight="50%"
+ data-cy="process-inputs">
<ProcessIOCard
label={ProcessIOCardType.INPUT}
process={process}
loadOutputs,
toggleProcessPanelFilter,
updateOutputParams,
- loadNodeJson
-} from 'store/process-panel/process-panel-actions';
-import { cancelRunningWorkflow, resumeOnHoldWorkflow, startWorkflow } from 'store/processes/processes-actions';
-import { navigateToLogCollection, pollProcessLogs, setProcessLogsPanelFilter } from 'store/process-logs-panel/process-logs-panel-actions';
-import { snackbarActions, SnackbarKind } from 'store/snackbar/snackbar-actions';
+ loadNodeJson,
+} from "store/process-panel/process-panel-actions";
+import { cancelRunningWorkflow, resumeOnHoldWorkflow, startWorkflow } from "store/processes/processes-actions";
- import { navigateToLogCollection, setProcessLogsPanelFilter } from "store/process-logs-panel/process-logs-panel-actions";
++import { navigateToLogCollection, pollProcessLogs, setProcessLogsPanelFilter } from "store/process-logs-panel/process-logs-panel-actions";
+import { snackbarActions, SnackbarKind } from "store/snackbar/snackbar-actions";
const mapStateToProps = ({ router, auth, resources, processPanel, processLogsPanel }: RootState): ProcessPanelRootDataProps => {
- const uuid = getProcessPanelCurrentUuid(router) || '';
+ const uuid = getProcessPanelCurrentUuid(router) || "";
const subprocesses = getSubprocesses(uuid)(resources);
return {
process: getProcess(uuid)(resources),
onToggle: status => {
dispatch<any>(toggleProcessPanelFilter(status));
},
- cancelProcess: (uuid) => dispatch<any>(cancelRunningWorkflow(uuid)),
- startProcess: (uuid) => dispatch<any>(startWorkflow(uuid)),
- resumeOnHoldWorkflow: (uuid) => dispatch<any>(resumeOnHoldWorkflow(uuid)),
- onLogFilterChange: (filter) => dispatch(setProcessLogsPanelFilter(filter.value)),
- navigateToLog: (uuid) => dispatch<any>(navigateToLogCollection(uuid)),
- loadInputs: (containerRequest) => dispatch<any>(loadInputs(containerRequest)),
- loadOutputs: (containerRequest) => dispatch<any>(loadOutputs(containerRequest)),
- loadOutputDefinitions: (containerRequest) => dispatch<any>(loadOutputDefinitions(containerRequest)),
+ cancelProcess: uuid => dispatch<any>(cancelRunningWorkflow(uuid)),
+ startProcess: uuid => dispatch<any>(startWorkflow(uuid)),
+ resumeOnHoldWorkflow: uuid => dispatch<any>(resumeOnHoldWorkflow(uuid)),
+ onLogFilterChange: filter => dispatch(setProcessLogsPanelFilter(filter.value)),
+ navigateToLog: uuid => dispatch<any>(navigateToLogCollection(uuid)),
+ loadInputs: containerRequest => dispatch<any>(loadInputs(containerRequest)),
+ loadOutputs: containerRequest => dispatch<any>(loadOutputs(containerRequest)),
+ loadOutputDefinitions: containerRequest => dispatch<any>(loadOutputDefinitions(containerRequest)),
updateOutputParams: () => dispatch<any>(updateOutputParams()),
- loadNodeJson: (containerRequest) => dispatch<any>(loadNodeJson(containerRequest)),
- pollProcessLogs: (processUuid) => dispatch<any>(pollProcessLogs(processUuid)),
+ loadNodeJson: containerRequest => dispatch<any>(loadNodeJson(containerRequest)),
++ pollProcessLogs: processUuid => dispatch<any>(pollProcessLogs(processUuid)),
});
const getFilters = (processPanel: ProcessPanelState, processes: Process[]) => {
//
// SPDX-License-Identifier: AGPL-3.0
-import React from 'react';
-import { StyleRulesCallback, WithStyles, withStyles } from '@material-ui/core/styles';
+import React from "react";
+import { StyleRulesCallback, WithStyles, withStyles } from "@material-ui/core/styles";
import { Route, Switch } from "react-router";
import { ProjectPanel } from "views/project-panel/project-panel";
-import { DetailsPanel } from 'views-components/details-panel/details-panel';
-import { ArvadosTheme } from 'common/custom-theme';
+import { DetailsPanel } from "views-components/details-panel/details-panel";
+import { ArvadosTheme } from "common/custom-theme";
import { ContextMenu } from "views-components/context-menu/context-menu";
import { FavoritePanel } from "../favorite-panel/favorite-panel";
-import { TokenDialog } from 'views-components/token-dialog/token-dialog';
-import { RichTextEditorDialog } from 'views-components/rich-text-editor-dialog/rich-text-editor-dialog';
-import { Snackbar } from 'views-components/snackbar/snackbar';
-import { CollectionPanel } from '../collection-panel/collection-panel';
-import { RenameFileDialog } from 'views-components/rename-file-dialog/rename-file-dialog';
-import { FileRemoveDialog } from 'views-components/file-remove-dialog/file-remove-dialog';
-import { MultipleFilesRemoveDialog } from 'views-components/file-remove-dialog/multiple-files-remove-dialog';
-import { Routes } from 'routes/routes';
-import { SidePanel } from 'views-components/side-panel/side-panel';
-import { ProcessPanel } from 'views/process-panel/process-panel';
-import { ChangeWorkflowDialog } from 'views-components/run-process-dialog/change-workflow-dialog';
-import { CreateProjectDialog } from 'views-components/dialog-forms/create-project-dialog';
-import { CreateCollectionDialog } from 'views-components/dialog-forms/create-collection-dialog';
-import { CopyCollectionDialog } from 'views-components/dialog-forms/copy-collection-dialog';
-import { CopyProcessDialog } from 'views-components/dialog-forms/copy-process-dialog';
-import { UpdateCollectionDialog } from 'views-components/dialog-forms/update-collection-dialog';
-import { UpdateProcessDialog } from 'views-components/dialog-forms/update-process-dialog';
-import { UpdateProjectDialog } from 'views-components/dialog-forms/update-project-dialog';
-import { MoveProcessDialog } from 'views-components/dialog-forms/move-process-dialog';
-import { MoveProjectDialog } from 'views-components/dialog-forms/move-project-dialog';
-import { MoveCollectionDialog } from 'views-components/dialog-forms/move-collection-dialog';
-import { FilesUploadCollectionDialog } from 'views-components/dialog-forms/files-upload-collection-dialog';
-import { PartialCopyToNewCollectionDialog } from 'views-components/dialog-forms/partial-copy-to-new-collection-dialog';
-import { PartialCopyToExistingCollectionDialog } from 'views-components/dialog-forms/partial-copy-to-existing-collection-dialog';
-import { PartialCopyToSeparateCollectionsDialog } from 'views-components/dialog-forms/partial-copy-to-separate-collections-dialog';
-import { PartialMoveToNewCollectionDialog } from 'views-components/dialog-forms/partial-move-to-new-collection-dialog';
-import { PartialMoveToExistingCollectionDialog } from 'views-components/dialog-forms/partial-move-to-existing-collection-dialog';
-import { PartialMoveToSeparateCollectionsDialog } from 'views-components/dialog-forms/partial-move-to-separate-collections-dialog';
-import { RemoveProcessDialog } from 'views-components/process-remove-dialog/process-remove-dialog';
-import { MainContentBar } from 'views-components/main-content-bar/main-content-bar';
-import { Grid } from '@material-ui/core';
+import { TokenDialog } from "views-components/token-dialog/token-dialog";
+import { RichTextEditorDialog } from "views-components/rich-text-editor-dialog/rich-text-editor-dialog";
+import { Snackbar } from "views-components/snackbar/snackbar";
+import { CollectionPanel } from "../collection-panel/collection-panel";
+import { RenameFileDialog } from "views-components/rename-file-dialog/rename-file-dialog";
+import { FileRemoveDialog } from "views-components/file-remove-dialog/file-remove-dialog";
+import { MultipleFilesRemoveDialog } from "views-components/file-remove-dialog/multiple-files-remove-dialog";
+import { Routes } from "routes/routes";
+import { SidePanel } from "views-components/side-panel/side-panel";
+import { ProcessPanel } from "views/process-panel/process-panel";
+import { ChangeWorkflowDialog } from "views-components/run-process-dialog/change-workflow-dialog";
+import { CreateProjectDialog } from "views-components/dialog-forms/create-project-dialog";
+import { CreateCollectionDialog } from "views-components/dialog-forms/create-collection-dialog";
+import { CopyCollectionDialog, CopyMultiCollectionDialog } from "views-components/dialog-forms/copy-collection-dialog";
+import { CopyProcessDialog } from "views-components/dialog-forms/copy-process-dialog";
+import { UpdateCollectionDialog } from "views-components/dialog-forms/update-collection-dialog";
+import { UpdateProcessDialog } from "views-components/dialog-forms/update-process-dialog";
+import { UpdateProjectDialog } from "views-components/dialog-forms/update-project-dialog";
+import { MoveProcessDialog } from "views-components/dialog-forms/move-process-dialog";
+import { MoveProjectDialog } from "views-components/dialog-forms/move-project-dialog";
+import { MoveCollectionDialog } from "views-components/dialog-forms/move-collection-dialog";
+import { FilesUploadCollectionDialog } from "views-components/dialog-forms/files-upload-collection-dialog";
- import { PartialCopyCollectionDialog } from "views-components/dialog-forms/partial-copy-collection-dialog";
++import { PartialCopyToNewCollectionDialog } from "views-components/dialog-forms/partial-copy-to-new-collection-dialog";
++import { PartialCopyToExistingCollectionDialog } from "views-components/dialog-forms/partial-copy-to-existing-collection-dialog";
++import { PartialCopyToSeparateCollectionsDialog } from "views-components/dialog-forms/partial-copy-to-separate-collections-dialog";
++import { PartialMoveToNewCollectionDialog } from "views-components/dialog-forms/partial-move-to-new-collection-dialog";
++import { PartialMoveToExistingCollectionDialog } from "views-components/dialog-forms/partial-move-to-existing-collection-dialog";
++import { PartialMoveToSeparateCollectionsDialog } from "views-components/dialog-forms/partial-move-to-separate-collections-dialog";
+import { RemoveProcessDialog } from "views-components/process-remove-dialog/process-remove-dialog";
+import { MainContentBar } from "views-components/main-content-bar/main-content-bar";
+import { Grid } from "@material-ui/core";
import { TrashPanel } from "views/trash-panel/trash-panel";
-import { SharedWithMePanel } from 'views/shared-with-me-panel/shared-with-me-panel';
-import { RunProcessPanel } from 'views/run-process-panel/run-process-panel';
-import SplitterLayout from 'react-splitter-layout';
-import { WorkflowPanel } from 'views/workflow-panel/workflow-panel';
-import { RegisteredWorkflowPanel } from 'views/workflow-panel/registered-workflow-panel';
-import { SearchResultsPanel } from 'views/search-results-panel/search-results-panel';
-import { SshKeyPanel } from 'views/ssh-key-panel/ssh-key-panel';
-import { SshKeyAdminPanel } from 'views/ssh-key-panel/ssh-key-admin-panel';
+import { SharedWithMePanel } from "views/shared-with-me-panel/shared-with-me-panel";
+import { RunProcessPanel } from "views/run-process-panel/run-process-panel";
+import SplitterLayout from "react-splitter-layout";
+import { WorkflowPanel } from "views/workflow-panel/workflow-panel";
+import { RegisteredWorkflowPanel } from "views/workflow-panel/registered-workflow-panel";
+import { SearchResultsPanel } from "views/search-results-panel/search-results-panel";
+import { SshKeyPanel } from "views/ssh-key-panel/ssh-key-panel";
+import { SshKeyAdminPanel } from "views/ssh-key-panel/ssh-key-admin-panel";
import { SiteManagerPanel } from "views/site-manager-panel/site-manager-panel";
-import { UserProfilePanel } from 'views/user-profile-panel/user-profile-panel';
-import { SharingDialog } from 'views-components/sharing-dialog/sharing-dialog';
-import { NotFoundDialog } from 'views-components/not-found-dialog/not-found-dialog';
-import { AdvancedTabDialog } from 'views-components/advanced-tab-dialog/advanced-tab-dialog';
-import { ProcessInputDialog } from 'views-components/process-input-dialog/process-input-dialog';
-import { VirtualMachineUserPanel } from 'views/virtual-machine-panel/virtual-machine-user-panel';
-import { VirtualMachineAdminPanel } from 'views/virtual-machine-panel/virtual-machine-admin-panel';
-import { RepositoriesPanel } from 'views/repositories-panel/repositories-panel';
-import { KeepServicePanel } from 'views/keep-service-panel/keep-service-panel';
-import { ApiClientAuthorizationPanel } from 'views/api-client-authorization-panel/api-client-authorization-panel';
-import { LinkPanel } from 'views/link-panel/link-panel';
-import { RepositoriesSampleGitDialog } from 'views-components/repositories-sample-git-dialog/repositories-sample-git-dialog';
-import { RepositoryAttributesDialog } from 'views-components/repository-attributes-dialog/repository-attributes-dialog';
-import { CreateRepositoryDialog } from 'views-components/dialog-forms/create-repository-dialog';
-import { RemoveRepositoryDialog } from 'views-components/repository-remove-dialog/repository-remove-dialog';
-import { CreateSshKeyDialog } from 'views-components/dialog-forms/create-ssh-key-dialog';
-import { PublicKeyDialog } from 'views-components/ssh-keys-dialog/public-key-dialog';
-import { RemoveApiClientAuthorizationDialog } from 'views-components/api-client-authorizations-dialog/remove-dialog';
-import { RemoveKeepServiceDialog } from 'views-components/keep-services-dialog/remove-dialog';
-import { RemoveLinkDialog } from 'views-components/links-dialog/remove-dialog';
-import { RemoveSshKeyDialog } from 'views-components/ssh-keys-dialog/remove-dialog';
-import { VirtualMachineAttributesDialog } from 'views-components/virtual-machines-dialog/attributes-dialog';
-import { RemoveVirtualMachineDialog } from 'views-components/virtual-machines-dialog/remove-dialog';
-import { RemoveVirtualMachineLoginDialog } from 'views-components/virtual-machines-dialog/remove-login-dialog';
-import { VirtualMachineAddLoginDialog } from 'views-components/virtual-machines-dialog/add-login-dialog';
-import { AttributesApiClientAuthorizationDialog } from 'views-components/api-client-authorizations-dialog/attributes-dialog';
-import { AttributesKeepServiceDialog } from 'views-components/keep-services-dialog/attributes-dialog';
-import { AttributesLinkDialog } from 'views-components/links-dialog/attributes-dialog';
-import { AttributesSshKeyDialog } from 'views-components/ssh-keys-dialog/attributes-dialog';
-import { UserPanel } from 'views/user-panel/user-panel';
-import { UserAttributesDialog } from 'views-components/user-dialog/attributes-dialog';
-import { CreateUserDialog } from 'views-components/dialog-forms/create-user-dialog';
-import { HelpApiClientAuthorizationDialog } from 'views-components/api-client-authorizations-dialog/help-dialog';
-import { DeactivateDialog } from 'views-components/user-dialog/deactivate-dialog';
-import { ActivateDialog } from 'views-components/user-dialog/activate-dialog';
-import { SetupDialog } from 'views-components/user-dialog/setup-dialog';
-import { GroupsPanel } from 'views/groups-panel/groups-panel';
-import { RemoveGroupDialog } from 'views-components/groups-dialog/remove-dialog';
-import { GroupAttributesDialog } from 'views-components/groups-dialog/attributes-dialog';
-import { GroupDetailsPanel } from 'views/group-details-panel/group-details-panel';
-import { RemoveGroupMemberDialog } from 'views-components/groups-dialog/member-remove-dialog';
-import { GroupMemberAttributesDialog } from 'views-components/groups-dialog/member-attributes-dialog';
-import { PublicFavoritePanel } from 'views/public-favorites-panel/public-favorites-panel';
-import { LinkAccountPanel } from 'views/link-account-panel/link-account-panel';
-import { FedLogin } from './fed-login';
-import { CollectionsContentAddressPanel } from 'views/collection-content-address-panel/collection-content-address-panel';
-import { AllProcessesPanel } from '../all-processes-panel/all-processes-panel';
-import { NotFoundPanel } from '../not-found-panel/not-found-panel';
-import { AutoLogout } from 'views-components/auto-logout/auto-logout';
-import { RestoreCollectionVersionDialog } from 'views-components/collections-dialog/restore-version-dialog';
-import { WebDavS3InfoDialog } from 'views-components/webdav-s3-dialog/webdav-s3-dialog';
-import { pluginConfig } from 'plugins';
-import { ElementListReducer } from 'common/plugintypes';
-import { COLLAPSE_ICON_SIZE } from 'views-components/side-panel-toggle/side-panel-toggle'
-import { Banner } from 'views-components/baner/banner';
+import { UserProfilePanel } from "views/user-profile-panel/user-profile-panel";
+import { SharingDialog } from "views-components/sharing-dialog/sharing-dialog";
+import { NotFoundDialog } from "views-components/not-found-dialog/not-found-dialog";
+import { AdvancedTabDialog } from "views-components/advanced-tab-dialog/advanced-tab-dialog";
+import { ProcessInputDialog } from "views-components/process-input-dialog/process-input-dialog";
+import { VirtualMachineUserPanel } from "views/virtual-machine-panel/virtual-machine-user-panel";
+import { VirtualMachineAdminPanel } from "views/virtual-machine-panel/virtual-machine-admin-panel";
+import { RepositoriesPanel } from "views/repositories-panel/repositories-panel";
+import { KeepServicePanel } from "views/keep-service-panel/keep-service-panel";
+import { ApiClientAuthorizationPanel } from "views/api-client-authorization-panel/api-client-authorization-panel";
+import { LinkPanel } from "views/link-panel/link-panel";
+import { RepositoriesSampleGitDialog } from "views-components/repositories-sample-git-dialog/repositories-sample-git-dialog";
+import { RepositoryAttributesDialog } from "views-components/repository-attributes-dialog/repository-attributes-dialog";
+import { CreateRepositoryDialog } from "views-components/dialog-forms/create-repository-dialog";
+import { RemoveRepositoryDialog } from "views-components/repository-remove-dialog/repository-remove-dialog";
+import { CreateSshKeyDialog } from "views-components/dialog-forms/create-ssh-key-dialog";
+import { PublicKeyDialog } from "views-components/ssh-keys-dialog/public-key-dialog";
+import { RemoveApiClientAuthorizationDialog } from "views-components/api-client-authorizations-dialog/remove-dialog";
+import { RemoveKeepServiceDialog } from "views-components/keep-services-dialog/remove-dialog";
+import { RemoveLinkDialog } from "views-components/links-dialog/remove-dialog";
+import { RemoveSshKeyDialog } from "views-components/ssh-keys-dialog/remove-dialog";
+import { VirtualMachineAttributesDialog } from "views-components/virtual-machines-dialog/attributes-dialog";
+import { RemoveVirtualMachineDialog } from "views-components/virtual-machines-dialog/remove-dialog";
+import { RemoveVirtualMachineLoginDialog } from "views-components/virtual-machines-dialog/remove-login-dialog";
+import { VirtualMachineAddLoginDialog } from "views-components/virtual-machines-dialog/add-login-dialog";
+import { AttributesApiClientAuthorizationDialog } from "views-components/api-client-authorizations-dialog/attributes-dialog";
+import { AttributesKeepServiceDialog } from "views-components/keep-services-dialog/attributes-dialog";
+import { AttributesLinkDialog } from "views-components/links-dialog/attributes-dialog";
+import { AttributesSshKeyDialog } from "views-components/ssh-keys-dialog/attributes-dialog";
+import { UserPanel } from "views/user-panel/user-panel";
+import { UserAttributesDialog } from "views-components/user-dialog/attributes-dialog";
+import { CreateUserDialog } from "views-components/dialog-forms/create-user-dialog";
+import { HelpApiClientAuthorizationDialog } from "views-components/api-client-authorizations-dialog/help-dialog";
+import { DeactivateDialog } from "views-components/user-dialog/deactivate-dialog";
+import { ActivateDialog } from "views-components/user-dialog/activate-dialog";
+import { SetupDialog } from "views-components/user-dialog/setup-dialog";
+import { GroupsPanel } from "views/groups-panel/groups-panel";
+import { RemoveGroupDialog } from "views-components/groups-dialog/remove-dialog";
+import { GroupAttributesDialog } from "views-components/groups-dialog/attributes-dialog";
+import { GroupDetailsPanel } from "views/group-details-panel/group-details-panel";
+import { RemoveGroupMemberDialog } from "views-components/groups-dialog/member-remove-dialog";
+import { GroupMemberAttributesDialog } from "views-components/groups-dialog/member-attributes-dialog";
- import { PartialCopyToCollectionDialog } from "views-components/dialog-forms/partial-copy-to-collection-dialog";
+import { PublicFavoritePanel } from "views/public-favorites-panel/public-favorites-panel";
+import { LinkAccountPanel } from "views/link-account-panel/link-account-panel";
+import { FedLogin } from "./fed-login";
+import { CollectionsContentAddressPanel } from "views/collection-content-address-panel/collection-content-address-panel";
+import { AllProcessesPanel } from "../all-processes-panel/all-processes-panel";
+import { NotFoundPanel } from "../not-found-panel/not-found-panel";
+import { AutoLogout } from "views-components/auto-logout/auto-logout";
+import { RestoreCollectionVersionDialog } from "views-components/collections-dialog/restore-version-dialog";
+import { WebDavS3InfoDialog } from "views-components/webdav-s3-dialog/webdav-s3-dialog";
+import { pluginConfig } from "plugins";
+import { ElementListReducer } from "common/plugintypes";
+import { COLLAPSE_ICON_SIZE } from "views-components/side-panel-toggle/side-panel-toggle";
+import { Banner } from "views-components/baner/banner";
-type CssRules = 'root' | 'container' | 'splitter' | 'asidePanel' | 'contentWrapper' | 'content';
+type CssRules = "root" | "container" | "splitter" | "asidePanel" | "contentWrapper" | "content";
const styles: StyleRulesCallback<CssRules> = (theme: ArvadosTheme) => ({
root: {
return splitterSize ? Number(splitterSize) : defaultSplitterSize;
};
-const saveSplitterSize = (size: number) => localStorage.setItem('splitterSize', size.toString());
+const saveSplitterSize = (size: number) => localStorage.setItem("splitterSize", size.toString());
-let routes = <>
- <Route path={Routes.PROJECTS} component={ProjectPanel} />
- <Route path={Routes.COLLECTIONS} component={CollectionPanel} />
- <Route path={Routes.FAVORITES} component={FavoritePanel} />
- <Route path={Routes.ALL_PROCESSES} component={AllProcessesPanel} />
- <Route path={Routes.PROCESSES} component={ProcessPanel} />
- <Route path={Routes.TRASH} component={TrashPanel} />
- <Route path={Routes.SHARED_WITH_ME} component={SharedWithMePanel} />
- <Route path={Routes.RUN_PROCESS} component={RunProcessPanel} />
- <Route path={Routes.REGISTEREDWORKFLOW} component={RegisteredWorkflowPanel} />
- <Route path={Routes.WORKFLOWS} component={WorkflowPanel} />
- <Route path={Routes.SEARCH_RESULTS} component={SearchResultsPanel} />
- <Route path={Routes.VIRTUAL_MACHINES_USER} component={VirtualMachineUserPanel} />
- <Route path={Routes.VIRTUAL_MACHINES_ADMIN} component={VirtualMachineAdminPanel} />
- <Route path={Routes.REPOSITORIES} component={RepositoriesPanel} />
- <Route path={Routes.SSH_KEYS_USER} component={SshKeyPanel} />
- <Route path={Routes.SSH_KEYS_ADMIN} component={SshKeyAdminPanel} />
- <Route path={Routes.SITE_MANAGER} component={SiteManagerPanel} />
- <Route path={Routes.KEEP_SERVICES} component={KeepServicePanel} />
- <Route path={Routes.USERS} component={UserPanel} />
- <Route path={Routes.API_CLIENT_AUTHORIZATIONS} component={ApiClientAuthorizationPanel} />
- <Route path={Routes.MY_ACCOUNT} component={UserProfilePanel} />
- <Route path={Routes.USER_PROFILE} component={UserProfilePanel} />
- <Route path={Routes.GROUPS} component={GroupsPanel} />
- <Route path={Routes.GROUP_DETAILS} component={GroupDetailsPanel} />
- <Route path={Routes.LINKS} component={LinkPanel} />
- <Route path={Routes.PUBLIC_FAVORITES} component={PublicFavoritePanel} />
- <Route path={Routes.LINK_ACCOUNT} component={LinkAccountPanel} />
- <Route path={Routes.COLLECTIONS_CONTENT_ADDRESS} component={CollectionsContentAddressPanel} />
-</>;
+let routes = (
+ <>
+ <Route
+ path={Routes.PROJECTS}
+ component={ProjectPanel}
+ />
+ <Route
+ path={Routes.COLLECTIONS}
+ component={CollectionPanel}
+ />
+ <Route
+ path={Routes.FAVORITES}
+ component={FavoritePanel}
+ />
+ <Route
+ path={Routes.ALL_PROCESSES}
+ component={AllProcessesPanel}
+ />
+ <Route
+ path={Routes.PROCESSES}
+ component={ProcessPanel}
+ />
+ <Route
+ path={Routes.TRASH}
+ component={TrashPanel}
+ />
+ <Route
+ path={Routes.SHARED_WITH_ME}
+ component={SharedWithMePanel}
+ />
+ <Route
+ path={Routes.RUN_PROCESS}
+ component={RunProcessPanel}
+ />
+ <Route
+ path={Routes.REGISTEREDWORKFLOW}
+ component={RegisteredWorkflowPanel}
+ />
+ <Route
+ path={Routes.WORKFLOWS}
+ component={WorkflowPanel}
+ />
+ <Route
+ path={Routes.SEARCH_RESULTS}
+ component={SearchResultsPanel}
+ />
+ <Route
+ path={Routes.VIRTUAL_MACHINES_USER}
+ component={VirtualMachineUserPanel}
+ />
+ <Route
+ path={Routes.VIRTUAL_MACHINES_ADMIN}
+ component={VirtualMachineAdminPanel}
+ />
+ <Route
+ path={Routes.REPOSITORIES}
+ component={RepositoriesPanel}
+ />
+ <Route
+ path={Routes.SSH_KEYS_USER}
+ component={SshKeyPanel}
+ />
+ <Route
+ path={Routes.SSH_KEYS_ADMIN}
+ component={SshKeyAdminPanel}
+ />
+ <Route
+ path={Routes.SITE_MANAGER}
+ component={SiteManagerPanel}
+ />
+ <Route
+ path={Routes.KEEP_SERVICES}
+ component={KeepServicePanel}
+ />
+ <Route
+ path={Routes.USERS}
+ component={UserPanel}
+ />
+ <Route
+ path={Routes.API_CLIENT_AUTHORIZATIONS}
+ component={ApiClientAuthorizationPanel}
+ />
+ <Route
+ path={Routes.MY_ACCOUNT}
+ component={UserProfilePanel}
+ />
+ <Route
+ path={Routes.USER_PROFILE}
+ component={UserProfilePanel}
+ />
+ <Route
+ path={Routes.GROUPS}
+ component={GroupsPanel}
+ />
+ <Route
+ path={Routes.GROUP_DETAILS}
+ component={GroupDetailsPanel}
+ />
+ <Route
+ path={Routes.LINKS}
+ component={LinkPanel}
+ />
+ <Route
+ path={Routes.PUBLIC_FAVORITES}
+ component={PublicFavoritePanel}
+ />
+ <Route
+ path={Routes.LINK_ACCOUNT}
+ component={LinkAccountPanel}
+ />
+ <Route
+ path={Routes.COLLECTIONS_CONTENT_ADDRESS}
+ component={CollectionsContentAddressPanel}
+ />
+ </>
+);
-const reduceRoutesFn: (a: React.ReactElement[],
- b: ElementListReducer) => React.ReactElement[] = (a, b) => b(a);
+const reduceRoutesFn: (a: React.ReactElement[], b: ElementListReducer) => React.ReactElement[] = (a, b) => b(a);
-routes = React.createElement(React.Fragment, null, pluginConfig.centerPanelList.reduce(reduceRoutesFn, React.Children.toArray(routes.props.children)));
+routes = React.createElement(
+ React.Fragment,
+ null,
+ pluginConfig.centerPanelList.reduce(reduceRoutesFn, React.Children.toArray(routes.props.children))
+);
-const applyCollapsedState = (isCollapsed) => {
- const rightPanel: Element = document.getElementsByClassName('layout-pane')[1]
- const totalWidth: number = document.getElementsByClassName('splitter-layout')[0]?.clientWidth
- const rightPanelExpandedWidth = ((totalWidth - COLLAPSE_ICON_SIZE)) / (totalWidth / 100)
+const applyCollapsedState = isCollapsed => {
+ const rightPanel: Element = document.getElementsByClassName("layout-pane")[1];
+ const totalWidth: number = document.getElementsByClassName("splitter-layout")[0]?.clientWidth;
+ const rightPanelExpandedWidth = (totalWidth - COLLAPSE_ICON_SIZE) / (totalWidth / 100);
if (rightPanel) {
- rightPanel.setAttribute('style', `width: ${isCollapsed ? rightPanelExpandedWidth : getSplitterInitialSize()}%`)
+ rightPanel.setAttribute("style", `width: ${isCollapsed ? rightPanelExpandedWidth : getSplitterInitialSize()}%`);
}
- const splitter = document.getElementsByClassName('layout-splitter')[0]
- isCollapsed ? splitter?.classList.add('layout-splitter-disabled') : splitter?.classList.remove('layout-splitter-disabled')
-
-}
-
-export const WorkbenchPanel =
- withStyles(styles)((props: WorkbenchPanelProps) => {
+ const splitter = document.getElementsByClassName("layout-splitter")[0];
+ isCollapsed ? splitter?.classList.add("layout-splitter-disabled") : splitter?.classList.remove("layout-splitter-disabled");
+};
- //panel size will not scale automatically on window resize, so we do it manually
- window.addEventListener('resize', () => applyCollapsedState(props.sidePanelIsCollapsed))
- applyCollapsedState(props.sidePanelIsCollapsed)
+export const WorkbenchPanel = withStyles(styles)((props: WorkbenchPanelProps) => {
+ //panel size will not scale automatically on window resize, so we do it manually
+ window.addEventListener("resize", () => applyCollapsedState(props.sidePanelIsCollapsed));
+ applyCollapsedState(props.sidePanelIsCollapsed);
- return <Grid container item xs className={props.classes.root}>
+ return (
+ <Grid
+ container
+ item
+ xs
- className={props.classes.root}>
++ className={props.classes.root}
++ >
{props.sessionIdleTimeout > 0 && <AutoLogout />}
- <Grid container item xs className={props.classes.container}>
- <SplitterLayout customClassName={props.classes.splitter} percentage={true}
- primaryIndex={0} primaryMinSize={10}
- secondaryInitialSize={getSplitterInitialSize()} secondaryMinSize={40}
- onSecondaryPaneSizeChange={saveSplitterSize}>
- {props.isUserActive && props.isNotLinking && <Grid container item xs component='aside' direction='column' className={props.classes.asidePanel}>
- <SidePanel />
- </Grid>}
- <Grid container item xs component="main" direction="column" className={props.classes.contentWrapper}>
- <Grid item xs>
+ <Grid
+ container
+ item
+ xs
- className={props.classes.container}>
++ className={props.classes.container}
++ >
+ <SplitterLayout
+ customClassName={props.classes.splitter}
+ percentage={true}
+ primaryIndex={0}
+ primaryMinSize={10}
+ secondaryInitialSize={getSplitterInitialSize()}
+ secondaryMinSize={40}
- onSecondaryPaneSizeChange={saveSplitterSize}>
++ onSecondaryPaneSizeChange={saveSplitterSize}
++ >
+ {props.isUserActive && props.isNotLinking && (
+ <Grid
+ container
+ item
+ xs
+ component="aside"
+ direction="column"
- className={props.classes.asidePanel}>
++ className={props.classes.asidePanel}
++ >
+ <SidePanel />
+ </Grid>
+ )}
+ <Grid
+ container
+ item
+ xs
+ component="main"
+ direction="column"
- className={props.classes.contentWrapper}>
++ className={props.classes.contentWrapper}
++ >
+ <Grid
+ item
- xs>
++ xs
++ >
{props.isNotLinking && <MainContentBar />}
</Grid>
- <Grid item xs className={props.classes.content}>
+ <Grid
+ item
+ xs
- className={props.classes.content}>
++ className={props.classes.content}
++ >
<Switch>
{routes.props.children}
- <Route path={Routes.NO_MATCH} component={NotFoundPanel} />
+ <Route
+ path={Routes.NO_MATCH}
+ component={NotFoundPanel}
+ />
</Switch>
</Grid>
</Grid>