17568: Merge branch 'master' into 17568-api-token-dialog-expiration-fix
authorLucas Di Pentima <lucas@di-pentima.com.ar>
Mon, 26 Apr 2021 23:35:36 +0000 (20:35 -0300)
committerLucas Di Pentima <lucas@di-pentima.com.ar>
Mon, 26 Apr 2021 23:35:36 +0000 (20:35 -0300)
Arvados-DCO-1.1-Signed-off-by: Lucas Di Pentima <lucas@di-pentima.com.ar>

cypress.json
cypress/integration/collection.spec.js
cypress/plugins/index.js
package.json
src/store/collections/collection-info-actions.ts
src/views-components/webdav-s3-dialog/webdav-s3-dialog.tsx
yarn.lock

index ebe064ea68f651c0d09c60a138c414062ea1c95d..47f77ede2f141113d1c6ad8f353151d29a8a5d54 100644 (file)
@@ -2,5 +2,6 @@
     "baseUrl": "https://localhost:3000/",
     "chromeWebSecurity": false,
     "viewportWidth": 1920,
-    "viewportHeight": 1080
+    "viewportHeight": 1080,
+    "downloadsFolder": "cypress/downloads"
 }
index 05cd097dcbdaaae34e7efa74edf61d11859f1acb..5bad87b3e7f4bee8256a0e515963a32f454bbcdd 100644 (file)
@@ -2,9 +2,12 @@
 //
 // SPDX-License-Identifier: AGPL-3.0
 
+const path = require('path');
+
 describe('Collection panel tests', function () {
     let activeUser;
     let adminUser;
+    let downloadsFolder;
 
     before(function () {
         // Only set up common users once. These aren't set up as aliases because
@@ -21,6 +24,7 @@ describe('Collection panel tests', function () {
                 activeUser = this.activeUser;
             }
             );
+        downloadsFolder = Cypress.config('downloadsFolder');
     });
 
     beforeEach(function () {
@@ -28,6 +32,49 @@ describe('Collection panel tests', function () {
         cy.clearLocalStorage();
     });
 
+    it('allows to download mountain duck config for a collection', () => {
+        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 (testCollection) {
+            cy.loginAs(activeUser);
+            cy.goToPath(`/collections/${testCollection.uuid}`);
+
+            cy.get('[data-cy=collection-panel-options-btn]').click();
+            cy.get('[data-cy=context-menu]').contains('Open as network folder or S3 bucket').click();
+            cy.get('[data-cy=download-button').click();
+
+            const filename = path.join(downloadsFolder, `${testCollection.name}.duck`);
+
+            cy.readFile(filename, { timeout: 15000 })
+                .then((body) => {
+                    const childrenCollection = Array.prototype.slice.call(Cypress.$(body).find('dict')[0].children);
+                    const map = {};
+                    let i, j = 2;
+                    
+                    for (i=0; i < childrenCollection.length; i += j) {
+                      map[childrenCollection[i].outerText] = childrenCollection[i + 1].outerText;
+                    }
+
+                    cy.get('#simple-tabpanel-0').find('a')
+                        .then((a) => {
+                            const [host, port] = a.text().split('@')[1].split('/')[0].split(':');
+                            expect(map['Protocol']).to.equal('davs');
+                            expect(map['UUID']).to.equal(testCollection.uuid);
+                            expect(map['Username']).to.equal(activeUser.user.username);
+                            expect(map['Port']).to.equal(port);
+                            expect(map['Hostname']).to.equal(host);
+                            if (map['Path']) {
+                                expect(map['Path']).to.equal(`/c=${testCollection.uuid}`);
+                            }
+                        });
+                })
+                .then(() => cy.task('clearDownload', { filename }));
+        });
+    });
+
     it('uses the property editor with vocabulary terms', function () {
         cy.createCollection(adminUser.token, {
             name: `Test collection ${Math.floor(Math.random() * 999999)}`,
index b11d5528ce391160faf4bd5e6b98d448c6cf43e9..132f9b0d1f402dbbf3b7fd865e39be4e3feb8877 100644 (file)
 // This function is called when a project is opened or re-opened (e.g. due to
 // the project's config changing)
 
+const fs = require('fs');
+const path = require('path');
+
 /**
  * @type {Cypress.PluginConfig}
  */
 module.exports = (on, config) => {
   // `on` is used to hook into various events Cypress emits
   // `config` is the resolved Cypress config
+  on("before:browser:launch", (browser = {}, launchOptions) => {
+    const downloadDirectory = path.join(__dirname, "..", "downloads");
+    if (browser.family === 'chromium' && browser.name !== 'electron') {
+     launchOptions.preferences.default["download"] = {
+      default_directory: downloadDirectory
+     };
+    }
+    return launchOptions;
+  });
+
+  on('task', {
+    clearDownload({ filename }) {
+      fs.unlinkSync(filename);
+      return null;
+    }
+  });
 }
index bee46703051f3c5afb02f1e2af50f5e61cae85aa..735224471b3762d4b85fecc3d6f02f2c57bcd467 100644 (file)
     "@types/sinon": "7.5",
     "@types/uuid": "3.4.4",
     "axios-mock-adapter": "1.17.0",
-    "cypress": "6.2.1",
+    "cypress": "6.3.0",
     "enzyme": "3.6.0",
     "enzyme-adapter-react-16": "1.5.0",
     "jest-localstorage-mock": "2.2.0",
index 29dc6b879eb744693f7fcfd0bfa3087f647cbb87..5aa1cf804666d68bb85b84c8868e4dfbc9144a3a 100644 (file)
@@ -7,6 +7,7 @@ import { RootState } from "~/store/store";
 import { ServiceRepository } from "~/services/services";
 import { dialogActions } from '~/store/dialog/dialog-actions';
 import { getNewExtraToken } from "../auth/auth-action";
+import { CollectionResource } from "~/models/collection";
 
 export const COLLECTION_WEBDAV_S3_DIALOG_NAME = 'collectionWebdavS3Dialog';
 
@@ -18,6 +19,7 @@ export interface WebDavS3InfoDialogData {
     localCluster: string;
     username: string;
     activeTab: number;
+    collectionName: string;
     setActiveTab: (event: any, tabNr: number) => void;
 }
 
@@ -34,6 +36,7 @@ export const openWebDavS3InfoDialog = (uuid: string, activeTab?: number) =>
                 localCluster: getState().auth.localCluster,
                 username: getState().auth.user!.username,
                 activeTab: activeTab || 0,
+                collectionName: (getState().resources[uuid] as CollectionResource).name,
                 setActiveTab: (event: any, tabNr: number) => dispatch<any>(openWebDavS3InfoDialog(uuid, tabNr)),
                 uuid
             }
index 018a0ef096b550191a953f545460d25b4f275d6d..8cd1faf31ec05b2a772626df6a041c3b5c944e58 100644 (file)
@@ -9,13 +9,17 @@ import { COLLECTION_WEBDAV_S3_DIALOG_NAME, WebDavS3InfoDialogData } from '~/stor
 import { WithDialogProps } from '~/store/dialog/with-dialog';
 import { compose } from 'redux';
 import { DetailsAttribute } from "~/components/details-attribute/details-attribute";
+import { DownloadIcon } from "~/components/icon/icon";
 
-export type CssRules = 'details';
+export type CssRules = 'details' | 'downloadButton';
 
 const styles: StyleRulesCallback<CssRules> = theme => ({
     details: {
         marginLeft: theme.spacing.unit * 3,
         marginRight: theme.spacing.unit * 3,
+    },
+    downloadButton: {
+        marginTop: theme.spacing.unit * 2,
     }
 });
 
@@ -40,6 +44,60 @@ function TabPanel(props: TabPanelData) {
     );
 }
 
+const isValidIpAddress = (ipAddress: string): Boolean => {
+    if (/^(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/.test(ipAddress)) {
+        return true;
+    }
+
+    return false;
+};
+
+const mountainduckTemplate = ({
+    uuid, 
+    username,
+    cyberDavStr,
+    collectionsUrl
+}: any) => {
+    
+    return `<?xml version="1.0" encoding="UTF-8"?>
+        <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+        <plist version="1.0">
+        <dict>
+            <key>Protocol</key>
+            <string>davs</string>
+            <key>Provider</key>
+            <string>iterate GmbH</string>
+            <key>UUID</key>
+            <string>${uuid}</string>
+            <key>Hostname</key>
+            <string>${collectionsUrl.replace('https://', ``).replace('*', uuid).split(':')[0]}</string>
+            <key>Port</key>
+            <string>${(cyberDavStr.split(':')[2] || '443').split('/')[0]}</string>
+            <key>Username</key>
+            <string>${username}</string>${isValidIpAddress(collectionsUrl.replace('https://', ``).split(':')[0])? 
+            `
+            <key>Path</key>
+            <string>/c=${uuid}</string>` : ''}
+            <key>Labels</key>
+            <array>
+            </array>
+        </dict>
+        </plist>`.split(/\r?\n/).join('\n');
+};
+
+const downloadMountainduckFileHandler = (filename: string, text: string) => {
+    const element = document.createElement('a');
+    element.setAttribute('href', 'data:text/plain;charset=utf-8,' + encodeURIComponent(text));
+    element.setAttribute('download', filename);
+  
+    element.style.display = 'none';
+    document.body.appendChild(element);
+  
+    element.click();
+  
+    document.body.removeChild(element);
+};
+
 export const WebDavS3InfoDialog = compose(
     withDialog(COLLECTION_WEBDAV_S3_DIALOG_NAME),
     withStyles(styles),
@@ -146,6 +204,17 @@ export const WebDavS3InfoDialog = compose(
                         value={props.data.token}
                         copyValue={props.data.token} />
 
+                    <Button
+                        data-cy='download-button'
+                        className={props.classes.downloadButton}
+                        onClick={() => downloadMountainduckFileHandler(`${props.data.collectionName || props.data.uuid}.duck`, mountainduckTemplate({ ...props.data, cyberDavStr }))}
+                        variant='contained'
+                        color='primary'
+                        size='small'>
+                        <DownloadIcon />
+                        Download Cyber/Mountain Duck bookmark
+                    </Button>
+
                     <h3>Gnome</h3>
                     <ol>
                         <li>Open Files</li>
index 3a36d800b1baa716760dded9bea1f91e053241b9..465d82aa135f2d05fdeae335da66e5c27a5723f9 100644 (file)
--- a/yarn.lock
+++ b/yarn.lock
@@ -3189,10 +3189,10 @@ cyclist@^1.0.1:
   resolved "https://registry.yarnpkg.com/cyclist/-/cyclist-1.0.1.tgz#596e9698fd0c80e12038c2b82d6eb1b35b6224d9"
   integrity sha1-WW6WmP0MgOEgOMK4LW6xs1tiJNk=
 
-cypress@6.2.1:
-  version "6.2.1"
-  resolved "https://registry.yarnpkg.com/cypress/-/cypress-6.2.1.tgz#27d5fbcf008c698c390fdb0c03441804176d06c4"
-  integrity sha512-OYkSgzA4J4Q7eMjZvNf5qWpBLR4RXrkqjL3UZ1UzGGLAskO0nFTi/RomNTG6TKvL3Zp4tw4zFY1gp5MtmkCZrA==
+cypress@6.3.0:
+  version "6.3.0"
+  resolved "https://registry.yarnpkg.com/cypress/-/cypress-6.3.0.tgz#e27bba01d7e493265700e1e85333dca0b0127ede"
+  integrity sha512-Ec6TAFOxdSB2HPINNJ1f7z75pENXcfCaQkz+A9j0eGSvusFJ2NNErq650DexCbNJAnCQkPqXB4XPH9kXnSQnUA==
   dependencies:
     "@cypress/listr-verbose-renderer" "^0.4.1"
     "@cypress/request" "^2.88.5"