From 110b0b40141ad24775191add0ef8e4f5328c08ef Mon Sep 17 00:00:00 2001 From: Stanislaw Adaszewski Date: Sat, 25 Jan 2020 17:54:08 +0100 Subject: [PATCH 001/170] Initial commit. --- .gitignore | 3 +++ wbadvanced/__main__.py | 31 ++++++++++++++++++++++ wbadvanced/collections.py | 56 +++++++++++++++++++++++++++++++++++++++ wbadvanced/projects.py | 50 ++++++++++++++++++++++++++++++++++ wbadvanced/users.py | 6 +++++ 5 files changed, 146 insertions(+) create mode 100644 .gitignore create mode 100755 wbadvanced/__main__.py create mode 100755 wbadvanced/collections.py create mode 100755 wbadvanced/projects.py create mode 100755 wbadvanced/users.py diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..3901713 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +__pycache__ +*.pyc + diff --git a/wbadvanced/__main__.py b/wbadvanced/__main__.py new file mode 100755 index 0000000..3613c18 --- /dev/null +++ b/wbadvanced/__main__.py @@ -0,0 +1,31 @@ +import dash +import dash_bootstrap_components as dbc +import dash_html_components as html +import socket +import arvados +import functools +from .projects import list_projects_html +from .collections import list_collections_html + + +def main(): + app = dash.Dash(__name__, external_stylesheets=[dbc.themes.BOOTSTRAP]) + arv = arvados.api('v1') + + user = arv.users().current().execute() + + # nav_btns = [ dbc.Button(a['name']) for a in projects ] + projects_table = list_projects_html(arv, user['uuid']) + collections_table = list_collections_html(arv, user['uuid']) + + app.layout = html.Div([ + html.H5('Projects'), + projects_table, + html.H5('Collections'), + collections_table + ], style={ 'margin': '10px' }) + + app.run_server(host=socket.getfqdn(), debug=True) + + +main() diff --git a/wbadvanced/collections.py b/wbadvanced/collections.py new file mode 100755 index 0000000..4303aac --- /dev/null +++ b/wbadvanced/collections.py @@ -0,0 +1,56 @@ +import dash_html_components as html +import dash_bootstrap_components as dbc +import humanize +from .users import get_user + + +def list_collections(arv, owner_uuid): + res = arv.collections().list(where = { + 'owner_uuid': owner_uuid + }).execute() + res = res['items'] + #while res['items_available'] > len(res['items']): + #res = arv.collections().list(where = {}) + return res + + +def list_collections_html(arv, parent): + projects = list_collections(arv, parent) + + header = html.Tr([ + html.Th('Name'), + html.Th('Description'), + html.Th('Owner'), + html.Th('Size') + ]) + header = [ html.Thead(header) ] + + rows = [] + for a in projects: + owner = get_user(arv, a['owner_uuid']) + rows.append( + html.Tr([ + html.Td([ + html.Div(html.A(href='/projects/' + a['uuid'], + children=a['name'])), + html.Div(a['uuid']) + ]), + + html.Td(a['description'] or 'Modified at ' + a['modified_at']), + + html.Td([ + html.Div(html.A(href='mailto:' + owner['email'], + children=owner['first_name'] + ' ' + owner['last_name'])), + html.Div(a['owner_uuid']) + ]), + + html.Td(humanize.naturalsize(a['file_size_total'])) + ])) + rows = [ html.Tbody(rows) ] + + res = dbc.Table(header + rows, + striped=True, + hover=True, + responsive=True) + + return res diff --git a/wbadvanced/projects.py b/wbadvanced/projects.py new file mode 100755 index 0000000..8d9b636 --- /dev/null +++ b/wbadvanced/projects.py @@ -0,0 +1,50 @@ +import dash_bootstrap_components as dbc +import dash_html_components as html +from .users import get_user + + +def list_projects(arv, parent): + res = arv.groups().list(where={ + 'owner_uuid': parent, + 'group_class': 'project' + }).execute()['items'] + return res + + +def list_projects_html(arv, parent): + projects = list_projects(arv, parent) + + header = html.Tr([ + html.Th('Name'), + html.Th('Description'), + html.Th('Owner') + ]) + header = [ html.Thead(header) ] + + rows = [] + for a in projects: + owner = get_user(arv, a['owner_uuid']) + rows.append( + html.Tr([ + html.Td([ + html.Div(html.A(href='/projects/' + a['uuid'], + children=a['name'])), + html.Div(a['uuid']) + ]), + + html.Td(a['description'] or 'Project'), + + html.Td([ + html.Div(html.A(href='mailto:' + owner['email'], + children=owner['first_name'] + ' ' + owner['last_name'])), + html.Div(a['owner_uuid']) + ]) + ])) + rows = [ html.Tbody(rows) ] + + res = dbc.Table(header + rows, + striped=True, + hover=True, + responsive=True) + + return res diff --git a/wbadvanced/users.py b/wbadvanced/users.py new file mode 100755 index 0000000..e3679c1 --- /dev/null +++ b/wbadvanced/users.py @@ -0,0 +1,6 @@ +import functools + +@functools.lru_cache(maxsize=128) +def get_user(arv, uuid): + res = arv.users().get(uuid=uuid).execute() + return res From cb37bf41aa358a4febb6be60271117009381a0f2 Mon Sep 17 00:00:00 2001 From: Stanislaw Adaszewski Date: Tue, 28 Jan 2020 16:29:52 +0100 Subject: [PATCH 002/170] Decided to go for native frontend/backend instead of Dash. --- .gitignore | 4 ++- frontend/package.json | 19 ++++++++++++++ frontend/rollup.config.js | 35 ++++++++++++++++++++++++++ frontend/src/css/index.css | 0 frontend/src/html/index.html | 0 frontend/src/js/index.js | 0 wbadvanced/__main__.py | 40 +++++++++++++++++++++++------ wbadvanced/collections.py | 8 +++--- wbadvanced/processes.py | 49 ++++++++++++++++++++++++++++++++++++ wbadvanced/projects.py | 3 ++- 10 files changed, 144 insertions(+), 14 deletions(-) create mode 100755 frontend/package.json create mode 100755 frontend/rollup.config.js create mode 100755 frontend/src/css/index.css create mode 100755 frontend/src/html/index.html create mode 100755 frontend/src/js/index.js create mode 100755 wbadvanced/processes.py diff --git a/.gitignore b/.gitignore index 3901713..3e653ed 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,5 @@ __pycache__ *.pyc - +node_modules +package-lock.json +/frontend/dist/ diff --git a/frontend/package.json b/frontend/package.json new file mode 100755 index 0000000..db9ad82 --- /dev/null +++ b/frontend/package.json @@ -0,0 +1,19 @@ +{ + "dependencies": { + "linkstate": "^1.1.1", + "preact": "^8.2.9", + "preact-router": "^2.6.1", + "rollup": "^0.62.0", + "rollup-plugin-buble": "^0.19.2", + "rollup-plugin-copy": "^0.2.3", + "rollup-plugin-includepaths": "^0.2.3", + "rollup-plugin-license": "^0.7.0", + "rollup-plugin-minify": "^1.0.3", + "rollup-plugin-node-resolve": "^3.3.0", + "watch": "^1.0.2" + }, + "scripts": { + "rollup": "rollup -c", + "watch": "watch \"rollup -c\" src" + } +} diff --git a/frontend/rollup.config.js b/frontend/rollup.config.js new file mode 100755 index 0000000..8209599 --- /dev/null +++ b/frontend/rollup.config.js @@ -0,0 +1,35 @@ +import resolve from 'rollup-plugin-node-resolve' +import buble from 'rollup-plugin-buble'; +import minify from 'rollup-plugin-minify'; +import copy from 'rollup-plugin-copy'; +import includePaths from 'rollup-plugin-includepaths'; +import license from 'rollup-plugin-license'; + +export default { + //dest: 'dist/app.min.js', + input: 'src/js/index.js', + output: { + file: 'dist/js/app.min.js', + name: 'CHEMTOP', + format: 'umd', + sourceMap: true + }, + plugins: [ + includePaths({ + paths: ['src/js', 'src/js/widget', 'src/js/misc'] + }), + copy({ + 'src/html/index.html': 'dist/index.html', + 'src/css/index.css': 'dist/css/index.css', + verbose: true + }), + buble({jsx: 'h'}), + resolve({}), + license({ + banner: 'Copyright (C) F. Hoffmann-La Roche AG, 2020.\nAuthor: stanislaw.adaszewski@roche.com\nAll Rights Reserved.', + }) /* , + minify({ + iife: 'dist/app.min.js' + }) */ + ] +} diff --git a/frontend/src/css/index.css b/frontend/src/css/index.css new file mode 100755 index 0000000..e69de29 diff --git a/frontend/src/html/index.html b/frontend/src/html/index.html new file mode 100755 index 0000000..e69de29 diff --git a/frontend/src/js/index.js b/frontend/src/js/index.js new file mode 100755 index 0000000..e69de29 diff --git a/wbadvanced/__main__.py b/wbadvanced/__main__.py index 3613c18..3e4f2cc 100755 --- a/wbadvanced/__main__.py +++ b/wbadvanced/__main__.py @@ -1,11 +1,18 @@ import dash import dash_bootstrap_components as dbc import dash_html_components as html +import dash_core_components as dcc import socket import arvados import functools from .projects import list_projects_html from .collections import list_collections_html +from .processes import list_processes_html + + +# list_projects_html = functools.lru_cache(maxsize=128)(list_projects_html) +# list_collections_html = functools.lru_cache(maxsize=128)(list_collections_html) +# list_processes_html = functools.lru_cache(maxsize=128)(list_processes_html) def main(): @@ -13,19 +20,36 @@ def main(): arv = arvados.api('v1') user = arv.users().current().execute() - # nav_btns = [ dbc.Button(a['name']) for a in projects ] - projects_table = list_projects_html(arv, user['uuid']) - collections_table = list_collections_html(arv, user['uuid']) app.layout = html.Div([ - html.H5('Projects'), - projects_table, - html.H5('Collections'), - collections_table + dcc.Location(id='url', refresh=False), + html.Div(id='page-content') ], style={ 'margin': '10px' }) - app.run_server(host=socket.getfqdn(), debug=True) + projects_table = list_projects_html(arv, user['uuid']) + collections_table = list_collections_html(arv, user['uuid']) + processes_table = list_processes_html(arv, user['uuid']) + + + @app.callback(dash.dependencies.Output('page-content', 'children'), + [ dash.dependencies.Input('url', 'pathname') ]) + def display_page(pathname): + print('display_page(), pathname:', pathname) + return html.Div([ + dbc.Tabs([ + dbc.Tab(projects_table, label="Projects", + label_style={ 'cursor': 'pointer' }) + ], style={ 'borderBottom': 'none' }), + dbc.Tabs([ + dbc.Tab(collections_table, label="Collections", + label_style={ 'cursor': 'pointer' }), + dbc.Tab(processes_table, label="Processes", + label_style={ 'cursor': 'pointer' }) + ], style={ 'borderBottom': 'none' }) + ]) + + app.run_server(host=socket.getfqdn(), debug=False) main() diff --git a/wbadvanced/collections.py b/wbadvanced/collections.py index 4303aac..5ba14f1 100755 --- a/wbadvanced/collections.py +++ b/wbadvanced/collections.py @@ -4,18 +4,18 @@ import humanize from .users import get_user -def list_collections(arv, owner_uuid): +def list_collections(arv, owner_uuid, limit=100): res = arv.collections().list(where = { 'owner_uuid': owner_uuid - }).execute() + }, limit=limit).execute() res = res['items'] #while res['items_available'] > len(res['items']): #res = arv.collections().list(where = {}) return res -def list_collections_html(arv, parent): - projects = list_collections(arv, parent) +def list_collections_html(arv, parent, limit=100): + projects = list_collections(arv, parent, limit=limit) header = html.Tr([ html.Th('Name'), diff --git a/wbadvanced/processes.py b/wbadvanced/processes.py new file mode 100755 index 0000000..7c1b71b --- /dev/null +++ b/wbadvanced/processes.py @@ -0,0 +1,49 @@ +import dash_bootstrap_components as dbc +import dash_html_components as html +from .users import get_user + + +def list_processes(arv, parent, limit=100): + res = arv.container_requests().list(where={ + 'owner_uuid': parent + }, limit=100).execute()['items'] + return res + + +def list_processes_html(arv, parent, limit=100): + processes = list_processes(arv, parent, limit=limit) + + header = html.Tr([ + html.Th('Name'), + html.Th('Description'), + html.Th('Owner') + ]) + header = [ html.Thead(header) ] + + rows = [] + for a in processes: + owner = get_user(arv, a['owner_uuid']) + rows.append( + html.Tr([ + html.Td([ + html.Div(html.A(href='/processes/' + a['uuid'], + children=a['name'])), + html.Div(a['uuid']) + ]), + + html.Td(a['description'] or 'Process'), + + html.Td([ + html.Div(html.A(href='mailto:' + owner['email'], + children=owner['first_name'] + ' ' + owner['last_name'])), + html.Div(a['owner_uuid']) + ]) + ])) + rows = [ html.Tbody(rows) ] + + res = dbc.Table(header + rows, + striped=True, + hover=True, + responsive=True) + + return res diff --git a/wbadvanced/projects.py b/wbadvanced/projects.py index 8d9b636..6adee71 100755 --- a/wbadvanced/projects.py +++ b/wbadvanced/projects.py @@ -1,5 +1,6 @@ import dash_bootstrap_components as dbc import dash_html_components as html +import dash_core_components as dcc from .users import get_user @@ -27,7 +28,7 @@ def list_projects_html(arv, parent): rows.append( html.Tr([ html.Td([ - html.Div(html.A(href='/projects/' + a['uuid'], + html.Div(dcc.Link(href='/projects/' + a['uuid'], children=a['name'])), html.Div(a['uuid']) ]), From 09de8983980ef1b977534b1cb1c20da04458dd48 Mon Sep 17 00:00:00 2001 From: Stanislaw Adaszewski Date: Thu, 30 Jan 2020 13:06:54 +0100 Subject: [PATCH 003/170] Slowly slowly starting the implementation. --- .gitignore | 2 ++ backend/srv.py | 7 +++++ frontend/package.json | 4 +++ frontend/rollup.config.js | 6 ++-- frontend/src/html/index.html | 10 +++++++ frontend/src/js/component/wb-app.js | 45 +++++++++++++++++++++++++++++ frontend/src/js/index.js | 6 ++++ frontend/src/js/widget/wb-tabs.js | 33 +++++++++++++++++++++ 8 files changed, 111 insertions(+), 2 deletions(-) create mode 100644 backend/srv.py create mode 100644 frontend/src/js/component/wb-app.js create mode 100644 frontend/src/js/widget/wb-tabs.js diff --git a/.gitignore b/.gitignore index 3e653ed..3ca30a8 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,5 @@ __pycache__ node_modules package-lock.json /frontend/dist/ +/backend/server.pem + diff --git a/backend/srv.py b/backend/srv.py new file mode 100644 index 0000000..9972690 --- /dev/null +++ b/backend/srv.py @@ -0,0 +1,7 @@ +import BaseHTTPServer, SimpleHTTPServer +import ssl + +httpd = BaseHTTPServer.HTTPServer(('0.0.0.0', 4443), SimpleHTTPServer.SimpleHTTPRequestHandler) +httpd.socket = ssl.wrap_socket (httpd.socket, certfile='/pstore/home/adaszews/workspace/arvados-workbench-advanced/backend/server.pem', server_side=True) +httpd.serve_forever() + diff --git a/frontend/package.json b/frontend/package.json index db9ad82..1d4a9c9 100755 --- a/frontend/package.json +++ b/frontend/package.json @@ -1,8 +1,12 @@ { "dependencies": { + "bootstrap": "^4.4.1", + "jquery": "^3.4.1", "linkstate": "^1.1.1", + "popper.js": "^1.16.1", "preact": "^8.2.9", "preact-router": "^2.6.1", + "random-bytes": "^1.0.0", "rollup": "^0.62.0", "rollup-plugin-buble": "^0.19.2", "rollup-plugin-copy": "^0.2.3", diff --git a/frontend/rollup.config.js b/frontend/rollup.config.js index 8209599..db67adf 100755 --- a/frontend/rollup.config.js +++ b/frontend/rollup.config.js @@ -10,17 +10,19 @@ export default { input: 'src/js/index.js', output: { file: 'dist/js/app.min.js', - name: 'CHEMTOP', + name: 'WBADV', format: 'umd', sourceMap: true }, plugins: [ includePaths({ - paths: ['src/js', 'src/js/widget', 'src/js/misc'] + paths: ['src/js', 'src/js/widget', 'src/js/misc', 'src/js/component'] }), copy({ 'src/html/index.html': 'dist/index.html', 'src/css/index.css': 'dist/css/index.css', + 'node_modules/bootstrap/dist/css/bootstrap.min.css': 'dist/css/bootstrap.min.css', + 'node_modules/bootstrap/dist/js/bootstrap.min.js': 'dist/js/bootstrap.min.js', verbose: true }), buble({jsx: 'h'}), diff --git a/frontend/src/html/index.html b/frontend/src/html/index.html index e69de29..c48306f 100755 --- a/frontend/src/html/index.html +++ b/frontend/src/html/index.html @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/frontend/src/js/component/wb-app.js b/frontend/src/js/component/wb-app.js new file mode 100644 index 0000000..8794e72 --- /dev/null +++ b/frontend/src/js/component/wb-app.js @@ -0,0 +1,45 @@ +import { h, Component } from 'preact'; +import WBTabs from 'wb-tabs'; + +class WBApp extends Component { + render() { + return ( +
+

WBApp

+ + + + + + + + + + + + + + + + + + + + + + + + + +
NameDescriptionSize
NameDescription0 bytes
NameDescription0 bytes
NameDescription0 bytes
+ alert(idx) } /> +
+ ); + } +} + +export default WBApp; diff --git a/frontend/src/js/index.js b/frontend/src/js/index.js index e69de29..a030442 100755 --- a/frontend/src/js/index.js +++ b/frontend/src/js/index.js @@ -0,0 +1,6 @@ +import { h, render } from 'preact'; +import WBApp from 'wb-app'; + +render(( + +), document.body); diff --git a/frontend/src/js/widget/wb-tabs.js b/frontend/src/js/widget/wb-tabs.js new file mode 100644 index 0000000..61c711b --- /dev/null +++ b/frontend/src/js/widget/wb-tabs.js @@ -0,0 +1,33 @@ +import { h, Component } from 'preact'; + +class WBTabs extends Component { + render({ tabs, onTabChanged }) { + return ( + + ); + } +} + +export default WBTabs; From 41e59baaa64468b81f16ec9616bdd85a9945efa5 Mon Sep 17 00:00:00 2001 From: Stanislaw Adaszewski Date: Thu, 30 Jan 2020 14:30:27 +0100 Subject: [PATCH 004/170] Trying to implement pagination. --- frontend/rollup.config.js | 1 + frontend/src/html/index.html | 1 + frontend/src/js/component/wb-app.js | 39 ++++------ .../src/js/component/wb-project-listing.js | 20 +++++ frontend/src/js/misc/make-arvados-request.js | 27 +++++++ frontend/src/js/widget/wb-pagination.js | 73 +++++++++++++++++++ frontend/src/js/widget/wb-table.js | 26 +++++++ 7 files changed, 161 insertions(+), 26 deletions(-) create mode 100644 frontend/src/js/component/wb-project-listing.js create mode 100644 frontend/src/js/misc/make-arvados-request.js create mode 100644 frontend/src/js/widget/wb-pagination.js create mode 100644 frontend/src/js/widget/wb-table.js diff --git a/frontend/rollup.config.js b/frontend/rollup.config.js index db67adf..9fafe04 100755 --- a/frontend/rollup.config.js +++ b/frontend/rollup.config.js @@ -23,6 +23,7 @@ export default { 'src/css/index.css': 'dist/css/index.css', 'node_modules/bootstrap/dist/css/bootstrap.min.css': 'dist/css/bootstrap.min.css', 'node_modules/bootstrap/dist/js/bootstrap.min.js': 'dist/js/bootstrap.min.js', + 'node_modules/jquery/dist/jquery.min.js': 'dist/js/jquery.min.js', verbose: true }), buble({jsx: 'h'}), diff --git a/frontend/src/html/index.html b/frontend/src/html/index.html index c48306f..711a065 100755 --- a/frontend/src/html/index.html +++ b/frontend/src/html/index.html @@ -2,6 +2,7 @@ + diff --git a/frontend/src/js/component/wb-app.js b/frontend/src/js/component/wb-app.js index 8794e72..711b4e8 100644 --- a/frontend/src/js/component/wb-app.js +++ b/frontend/src/js/component/wb-app.js @@ -1,37 +1,24 @@ import { h, Component } from 'preact'; import WBTabs from 'wb-tabs'; +import WBTable from 'wb-table'; +import WBPagination from 'wb-pagination'; class WBApp extends Component { render() { return (

WBApp

- - - - - - - - - - - - - - - - - - - - - - - - - -
NameDescriptionSize
NameDescription0 bytes
NameDescription0 bytes
NameDescription0 bytes
+ alert(idx) } /> + { + xhr.onreadystatechange = () => { + if (xhr.readyState !== 4) + return; + if (xhr.status !== 200) + reject(xhr); + else + accept(xhr); + }; + xhr.send(data); + }); + + return res; +} + +export default makeArvadosRequest; diff --git a/frontend/src/js/widget/wb-pagination.js b/frontend/src/js/widget/wb-pagination.js new file mode 100644 index 0000000..3a7c1e3 --- /dev/null +++ b/frontend/src/js/widget/wb-pagination.js @@ -0,0 +1,73 @@ +import { h, Component } from 'preact'; + +class WBPagination extends Component { + renderVisiblePages(numPages, activePage, chunkSize, onPageChanged) { + let visible = {}; + + let begActChnk = activePage - Math.floor(chunkSize / 2); + let endActChnk = activePage + Math.floor(chunkSize / 2) + 1; + for (let i = begActChnk; i < endActChnk; i++) + visible[i] = true; + + for (let i = 0; i < chunkSize; i++) + visible[i] = true; + + for (let i = Math.max(numPages - chunkSize, 0); i < numPages; i++) + visible[i] = true; + + visible = Object.keys(visible).map(n => Number(n)); + + let res = []; + let prev = 0; + + res.push(( +
  • + onPageChanged(activePage - 1) }>Previous +
  • + )); + + for (let i in visible) { + if (i > prev + 1) + res.push(( +
  • + onPageChanged(i - 1) }>... +
  • + )); + prev = i; + + res.push(( +
  • + onPageChanged(i) }>{ i + 1 } +
  • + )); + } + + res.push(( +
  • = numPages - 1 ? "page-item disabled" : "page-item" }> + onPageChanged(activePage + 1) }>Next +
  • + )); + + return res; + } + + render({ numPages, activePage, chunkSize, onPageChanged }) { + return ( + + ); + } +} + +WBPagination.defaultProps = { + 'chunkSize': 5 +}; + +export default WBPagination; diff --git a/frontend/src/js/widget/wb-table.js b/frontend/src/js/widget/wb-table.js new file mode 100644 index 0000000..c698135 --- /dev/null +++ b/frontend/src/js/widget/wb-table.js @@ -0,0 +1,26 @@ +import { h, Component } from 'preact'; + +class WBTable extends Component { + render({ columns, rows }) { + return ( + + + + { columns.map(c => ) } + + + + { rows.map(r => ( + + { columns.map((_, idx) => ( + + )) } + + )) } + +
    { c }
    { r[idx] }
    + ); + } +} + +export default WBTable; From 712d366579431cb2e837b27ac8436592a7680b44 Mon Sep 17 00:00:00 2001 From: Stanislaw Adaszewski Date: Thu, 30 Jan 2020 15:03:18 +0100 Subject: [PATCH 005/170] Cool, can actually browse the projects already. --- frontend/src/js/component/wb-app.js | 4 ++ .../src/js/component/wb-project-listing.js | 55 +++++++++++++++++-- frontend/src/js/misc/make-arvados-request.js | 2 +- frontend/src/js/widget/wb-pagination.js | 8 ++- 4 files changed, 59 insertions(+), 10 deletions(-) diff --git a/frontend/src/js/component/wb-app.js b/frontend/src/js/component/wb-app.js index 711b4e8..25f4857 100644 --- a/frontend/src/js/component/wb-app.js +++ b/frontend/src/js/component/wb-app.js @@ -2,12 +2,16 @@ import { h, Component } from 'preact'; import WBTabs from 'wb-tabs'; import WBTable from 'wb-table'; import WBPagination from 'wb-pagination'; +import WBProjectListing from 'wb-project-listing'; class WBApp extends Component { render() { return (

    WBApp

    + + alert(idx) } /> [ + item['name'], + item['description'], + item['owner_uuid'] + ]); + } + + setActivePage(i) { + this.state.activePage = i; let filters = [ - [ 'group_class', '=', 'project' ], - [ 'owner_uuid', '=', this.props.ownerUuid ] + [ 'group_class', '=', 'project' ] ]; - let req = makeArvadosRequest(his.props.arvHost, this.props.arvToken, - '/arvados/v1/groups?filters=' + encodeURIComponent(JSON.stringify(filters))) + if (this.props.ownerUuid !== null) + filters.push([ 'owner_uuid', '=', this.props.ownerUuid ]); + let prom = makeArvadosRequest(this.props.arvHost, this.props.arvToken, + '/arvados/v1/groups?filters=' + encodeURIComponent(JSON.stringify(filters)) + + '&limit=' + encodeURIComponent(this.props.itemsPerPage) + + '&offset=' + encodeURIComponent(this.props.itemsPerPage * i)); + prom = prom.then(xhr => + this.setState({ + 'numPages': Math.ceil(xhr.response['items_available'] / xhr.response['limit']), + 'rows': this.prepareRows(xhr.response['items']) + })); } - render({ arvHost, arvToken, ownerUuid }) { + render({ arvHost, arvToken, ownerUuid }, { rows, numPages, activePage }) { return ( -
    Project Listing
    +
    + + this.setActivePage(i) } /> +
    ); } } + +WBProjectListing.defaultProps = { + 'itemsPerPage': 100, + 'ownerUuid': null +}; + +export default WBProjectListing; diff --git a/frontend/src/js/misc/make-arvados-request.js b/frontend/src/js/misc/make-arvados-request.js index 59cc31e..dc12cc0 100644 --- a/frontend/src/js/misc/make-arvados-request.js +++ b/frontend/src/js/misc/make-arvados-request.js @@ -1,5 +1,5 @@ -makeArvadosRequest(arvHost, arvToken, endpoint, method='GET', data=null, +function makeArvadosRequest(arvHost, arvToken, endpoint, method='GET', data=null, contentType='application/json;charset=utf-8', responseType='json') { let xhr = new XMLHttpRequest(); diff --git a/frontend/src/js/widget/wb-pagination.js b/frontend/src/js/widget/wb-pagination.js index 3a7c1e3..9c2d091 100644 --- a/frontend/src/js/widget/wb-pagination.js +++ b/frontend/src/js/widget/wb-pagination.js @@ -6,16 +6,17 @@ class WBPagination extends Component { let begActChnk = activePage - Math.floor(chunkSize / 2); let endActChnk = activePage + Math.floor(chunkSize / 2) + 1; - for (let i = begActChnk; i < endActChnk; i++) + for (let i = Math.max(0, begActChnk); i < Math.min(numPages, endActChnk); i++) visible[i] = true; - for (let i = 0; i < chunkSize; i++) + for (let i = 0; i < Math.min(numPages, chunkSize); i++) visible[i] = true; for (let i = Math.max(numPages - chunkSize, 0); i < numPages; i++) visible[i] = true; visible = Object.keys(visible).map(n => Number(n)); + visible.sort(); let res = []; let prev = 0; @@ -27,7 +28,8 @@ class WBPagination extends Component { )); - for (let i in visible) { + for (let idx in visible) { + let i = visible[idx]; if (i > prev + 1) res.push((
  • From 0d4b5780253731573cd9bb7014b9bdceb3592a82 Mon Sep 17 00:00:00 2001 From: Stanislaw Adaszewski Date: Thu, 30 Jan 2020 15:40:48 +0100 Subject: [PATCH 006/170] Small fixes for pagination. --- frontend/src/js/component/wb-app.js | 9 +++++---- frontend/src/js/widget/wb-pagination.js | 10 +++++----- 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/frontend/src/js/component/wb-app.js b/frontend/src/js/component/wb-app.js index 25f4857..c86691b 100644 --- a/frontend/src/js/component/wb-app.js +++ b/frontend/src/js/component/wb-app.js @@ -5,15 +5,16 @@ import WBPagination from 'wb-pagination'; import WBProjectListing from 'wb-project-listing'; class WBApp extends Component { - render() { + render({}, { activePage }) { return (

    WBApp

    + arvToken="v2/arkau-gj3su-uf4hnu2o2qkvm8j/15kla38mafzq6b31d5t74ynhk6iuy32v1ticslodr0obvvhde9" + itemsPerPage="5" /> - alert(idx) } /> + this.setState({ 'activePage': i }) } /> Number(n)); - visible.sort(); + visible.sort((a, b) => (a - b)); let res = []; let prev = 0; @@ -24,7 +24,7 @@ class WBPagination extends Component { res.push((
  • onPageChanged(activePage - 1) }>Previous + onclick={ e => { e.preventDefault(); onPageChanged(activePage - 1); } }>Previous
  • )); @@ -34,7 +34,7 @@ class WBPagination extends Component { res.push((
  • onPageChanged(i - 1) }>... + onclick={ e => { e.preventDefault(); onPageChanged(i - 1); } }>...
  • )); prev = i; @@ -42,7 +42,7 @@ class WBPagination extends Component { res.push((
  • onPageChanged(i) }>{ i + 1 } + onclick={ e => { e.preventDefault(); onPageChanged(i); } }>{ i + 1 }
  • )); } @@ -50,7 +50,7 @@ class WBPagination extends Component { res.push((
  • = numPages - 1 ? "page-item disabled" : "page-item" }> onPageChanged(activePage + 1) }>Next + onclick={ e => { e.preventDefault(); onPageChanged(activePage + 1); } }>Next
  • )); From 1e2c7387ae9076936fcc35f4232b1ee62c723adb Mon Sep 17 00:00:00 2001 From: Stanislaw Adaszewski Date: Thu, 30 Jan 2020 16:33:01 +0100 Subject: [PATCH 007/170] Started working on navbar. --- frontend/package.json | 2 ++ frontend/rollup.config.js | 17 +++++++++ frontend/src/html/index.html | 2 ++ frontend/src/js/component/wb-app.js | 4 ++- frontend/src/js/component/wb-navbar.js | 49 ++++++++++++++++++++++++++ 5 files changed, 73 insertions(+), 1 deletion(-) create mode 100644 frontend/src/js/component/wb-navbar.js diff --git a/frontend/package.json b/frontend/package.json index 1d4a9c9..30b05ca 100755 --- a/frontend/package.json +++ b/frontend/package.json @@ -1,6 +1,8 @@ { "dependencies": { + "@fortawesome/fontawesome-free": "^5.12.0", "bootstrap": "^4.4.1", + "font-awesome": "^4.7.0", "jquery": "^3.4.1", "linkstate": "^1.1.1", "popper.js": "^1.16.1", diff --git a/frontend/rollup.config.js b/frontend/rollup.config.js index 9fafe04..2a9330a 100755 --- a/frontend/rollup.config.js +++ b/frontend/rollup.config.js @@ -24,6 +24,23 @@ export default { 'node_modules/bootstrap/dist/css/bootstrap.min.css': 'dist/css/bootstrap.min.css', 'node_modules/bootstrap/dist/js/bootstrap.min.js': 'dist/js/bootstrap.min.js', 'node_modules/jquery/dist/jquery.min.js': 'dist/js/jquery.min.js', + 'node_modules/@fortawesome/fontawesome-free/js/fontawesome.min.js': 'dist/js/fontawesome.min.js', + 'node_modules/@fortawesome/fontawesome-free/css/all.min.css': 'dist/css/all.min.css', + 'node_modules/@fortawesome/fontawesome-free/webfonts/fa-regular-400.eot': 'dist/webfonts/fa-regular-400.eot', + 'node_modules/@fortawesome/fontawesome-free/webfonts/fa-regular-400.svg': 'dist/webfonts/fa-regular-400.svg', + 'node_modules/@fortawesome/fontawesome-free/webfonts/fa-regular-400.ttf': 'dist/webfonts/fa-regular-400.ttf', + 'node_modules/@fortawesome/fontawesome-free/webfonts/fa-regular-400.woff': 'dist/webfonts/fa-regular-400.woff', + 'node_modules/@fortawesome/fontawesome-free/webfonts/fa-regular-400.woff2': 'dist/webfonts/fa-regular-400.woff2', + 'node_modules/@fortawesome/fontawesome-free/webfonts/fa-solid-900.eot': 'dist/webfonts/fa-solid-900.eot', + 'node_modules/@fortawesome/fontawesome-free/webfonts/fa-solid-900.svg': 'dist/webfonts/fa-solid-900.svg', + 'node_modules/@fortawesome/fontawesome-free/webfonts/fa-solid-900.ttf': 'dist/webfonts/fa-solid-900.ttf', + 'node_modules/@fortawesome/fontawesome-free/webfonts/fa-solid-900.woff': 'dist/webfonts/fa-solid-900.woff', + 'node_modules/@fortawesome/fontawesome-free/webfonts/fa-solid-900.woff2': 'dist/webfonts/fa-solid-900.woff2', + 'node_modules/@fortawesome/fontawesome-free/webfonts/fa-brands-400.eot': 'dist/webfonts/fa-brands-400.eot', + 'node_modules/@fortawesome/fontawesome-free/webfonts/fa-brands-400.svg': 'dist/webfonts/fa-brands-400.svg', + 'node_modules/@fortawesome/fontawesome-free/webfonts/fa-brands-400.ttf': 'dist/webfonts/fa-brands-400.ttf', + 'node_modules/@fortawesome/fontawesome-free/webfonts/fa-brands-400.woff': 'dist/webfonts/fa-brands-400.woff', + 'node_modules/@fortawesome/fontawesome-free/webfonts/fa-brands-400.woff2': 'dist/webfonts/fa-brands-400.woff2', verbose: true }), buble({jsx: 'h'}), diff --git a/frontend/src/html/index.html b/frontend/src/html/index.html index 711a065..8f02e32 100755 --- a/frontend/src/html/index.html +++ b/frontend/src/html/index.html @@ -2,8 +2,10 @@ + + diff --git a/frontend/src/js/component/wb-app.js b/frontend/src/js/component/wb-app.js index c86691b..a449c5e 100644 --- a/frontend/src/js/component/wb-app.js +++ b/frontend/src/js/component/wb-app.js @@ -3,12 +3,14 @@ import WBTabs from 'wb-tabs'; import WBTable from 'wb-table'; import WBPagination from 'wb-pagination'; import WBProjectListing from 'wb-project-listing'; +import WBNavbar from 'wb-navbar'; class WBApp extends Component { render({}, { activePage }) { return (
    -

    WBApp

    +

    WBApp

    + diff --git a/frontend/src/js/component/wb-navbar.js b/frontend/src/js/component/wb-navbar.js new file mode 100644 index 0000000..a11d733 --- /dev/null +++ b/frontend/src/js/component/wb-navbar.js @@ -0,0 +1,49 @@ +import { h, Component } from 'preact'; + +class WBNavbar extends Component { + render() { + return ( + + ); + } +} + +export default WBNavbar; From a5f2377b65479457ec723d1892ac3337263e88f9 Mon Sep 17 00:00:00 2001 From: Stanislaw Adaszewski Date: Thu, 30 Jan 2020 18:56:53 +0100 Subject: [PATCH 008/170] A basic navigation is in place. --- backend/srv.py | 10 ++++- frontend/package.json | 2 - frontend/rollup.config.js | 2 +- frontend/src/js/component/wb-app.js | 37 +++++++------------ .../src/js/component/wb-project-listing.js | 2 +- frontend/src/js/page/wb-browse.js | 19 ++++++++++ 6 files changed, 43 insertions(+), 29 deletions(-) create mode 100644 frontend/src/js/page/wb-browse.js diff --git a/backend/srv.py b/backend/srv.py index 9972690..711e099 100644 --- a/backend/srv.py +++ b/backend/srv.py @@ -1,7 +1,15 @@ import BaseHTTPServer, SimpleHTTPServer import ssl -httpd = BaseHTTPServer.HTTPServer(('0.0.0.0', 4443), SimpleHTTPServer.SimpleHTTPRequestHandler) +class RequestHandler(SimpleHTTPServer.SimpleHTTPRequestHandler): + def do_GET(self): + print(self.path) + if '.' not in self.path: + self.path = '/' + SimpleHTTPServer.SimpleHTTPRequestHandler.do_GET(self) + # RequestHandler, self).do_GET() + +httpd = BaseHTTPServer.HTTPServer(('0.0.0.0', 4443), RequestHandler) httpd.socket = ssl.wrap_socket (httpd.socket, certfile='/pstore/home/adaszews/workspace/arvados-workbench-advanced/backend/server.pem', server_side=True) httpd.serve_forever() diff --git a/frontend/package.json b/frontend/package.json index 30b05ca..86ac08b 100755 --- a/frontend/package.json +++ b/frontend/package.json @@ -2,13 +2,11 @@ "dependencies": { "@fortawesome/fontawesome-free": "^5.12.0", "bootstrap": "^4.4.1", - "font-awesome": "^4.7.0", "jquery": "^3.4.1", "linkstate": "^1.1.1", "popper.js": "^1.16.1", "preact": "^8.2.9", "preact-router": "^2.6.1", - "random-bytes": "^1.0.0", "rollup": "^0.62.0", "rollup-plugin-buble": "^0.19.2", "rollup-plugin-copy": "^0.2.3", diff --git a/frontend/rollup.config.js b/frontend/rollup.config.js index 2a9330a..b1c184b 100755 --- a/frontend/rollup.config.js +++ b/frontend/rollup.config.js @@ -16,7 +16,7 @@ export default { }, plugins: [ includePaths({ - paths: ['src/js', 'src/js/widget', 'src/js/misc', 'src/js/component'] + paths: ['src/js', 'src/js/widget', 'src/js/misc', 'src/js/component', 'src/js/page'] }), copy({ 'src/html/index.html': 'dist/index.html', diff --git a/frontend/src/js/component/wb-app.js b/frontend/src/js/component/wb-app.js index a449c5e..8c7f7b6 100644 --- a/frontend/src/js/component/wb-app.js +++ b/frontend/src/js/component/wb-app.js @@ -1,37 +1,26 @@ import { h, Component } from 'preact'; +import { Router, route } from 'preact-router'; import WBTabs from 'wb-tabs'; import WBTable from 'wb-table'; import WBPagination from 'wb-pagination'; import WBProjectListing from 'wb-project-listing'; import WBNavbar from 'wb-navbar'; +import WBBrowse from 'wb-browse'; class WBApp extends Component { render({}, { activePage }) { return ( -
    -

    WBApp

    - - - - this.setState({ 'activePage': i }) } /> - - alert(idx) } /> -
    + +
    + Hello, world! +
    + +
    + +
    + + +
    ); } } diff --git a/frontend/src/js/component/wb-project-listing.js b/frontend/src/js/component/wb-project-listing.js index 9b5c647..6b43f03 100644 --- a/frontend/src/js/component/wb-project-listing.js +++ b/frontend/src/js/component/wb-project-listing.js @@ -29,7 +29,7 @@ class WBProjectListing extends Component { let filters = [ [ 'group_class', '=', 'project' ] ]; - if (this.props.ownerUuid !== null) + if (this.props.ownerUuid) filters.push([ 'owner_uuid', '=', this.props.ownerUuid ]); let prom = makeArvadosRequest(this.props.arvHost, this.props.arvToken, '/arvados/v1/groups?filters=' + encodeURIComponent(JSON.stringify(filters)) + diff --git a/frontend/src/js/page/wb-browse.js b/frontend/src/js/page/wb-browse.js new file mode 100644 index 0000000..30eed7a --- /dev/null +++ b/frontend/src/js/page/wb-browse.js @@ -0,0 +1,19 @@ +import { h, Component } from 'preact'; +import WBNavbar from 'wb-navbar'; +import WBProjectListing from 'wb-project-listing'; + +class WBBrowse extends Component { + render({ ownerUuid }) { + return ( +
    + + +
    + ); + } +} + +export default WBBrowse; From 0bb1ec1259bce439b3dd830b28ccd8ff7872f218 Mon Sep 17 00:00:00 2001 From: Stanislaw Adaszewski Date: Fri, 31 Jan 2020 09:23:22 +0100 Subject: [PATCH 009/170] Display UUID in project listing. --- frontend/src/js/component/wb-project-listing.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/frontend/src/js/component/wb-project-listing.js b/frontend/src/js/component/wb-project-listing.js index 6b43f03..0f6013d 100644 --- a/frontend/src/js/component/wb-project-listing.js +++ b/frontend/src/js/component/wb-project-listing.js @@ -18,7 +18,10 @@ class WBProjectListing extends Component { prepareRows(items) { return items.map(item => [ - item['name'], + (
    +
    { item['name'] }
    +
    { item['uuid'] }
    +
    ), item['description'], item['owner_uuid'] ]); From f3b6e9794ca6f564eec0a23b08f427ba53651ef7 Mon Sep 17 00:00:00 2001 From: Stanislaw Adaszewski Date: Fri, 31 Jan 2020 10:30:10 +0100 Subject: [PATCH 010/170] Making the navbar more configurable. --- frontend/src/js/component/wb-inline-search.js | 14 +++ frontend/src/js/component/wb-navbar.js | 49 ----------- frontend/src/js/{component => page}/wb-app.js | 5 +- frontend/src/js/page/wb-browse.js | 8 +- frontend/src/js/page/wb-sign-in.js | 31 +++++++ frontend/src/js/widget/wb-navbar.js | 87 +++++++++++++++++++ 6 files changed, 141 insertions(+), 53 deletions(-) create mode 100644 frontend/src/js/component/wb-inline-search.js delete mode 100644 frontend/src/js/component/wb-navbar.js rename frontend/src/js/{component => page}/wb-app.js (85%) create mode 100644 frontend/src/js/page/wb-sign-in.js create mode 100644 frontend/src/js/widget/wb-navbar.js diff --git a/frontend/src/js/component/wb-inline-search.js b/frontend/src/js/component/wb-inline-search.js new file mode 100644 index 0000000..7f7b685 --- /dev/null +++ b/frontend/src/js/component/wb-inline-search.js @@ -0,0 +1,14 @@ +import { h, Component } from 'preact'; + +class WBInlineSearch extends Component { + render() { + return ( +
    + + +
    + ); + } +} + +export default WBInlineSearch; diff --git a/frontend/src/js/component/wb-navbar.js b/frontend/src/js/component/wb-navbar.js deleted file mode 100644 index a11d733..0000000 --- a/frontend/src/js/component/wb-navbar.js +++ /dev/null @@ -1,49 +0,0 @@ -import { h, Component } from 'preact'; - -class WBNavbar extends Component { - render() { - return ( - - ); - } -} - -export default WBNavbar; diff --git a/frontend/src/js/component/wb-app.js b/frontend/src/js/page/wb-app.js similarity index 85% rename from frontend/src/js/component/wb-app.js rename to frontend/src/js/page/wb-app.js index 8c7f7b6..6afce09 100644 --- a/frontend/src/js/component/wb-app.js +++ b/frontend/src/js/page/wb-app.js @@ -6,6 +6,7 @@ import WBPagination from 'wb-pagination'; import WBProjectListing from 'wb-project-listing'; import WBNavbar from 'wb-navbar'; import WBBrowse from 'wb-browse'; +import WBSignIn from 'wb-sign-in'; class WBApp extends Component { render({}, { activePage }) { @@ -15,9 +16,7 @@ class WBApp extends Component { Hello, world!
    -
    - -
    + diff --git a/frontend/src/js/page/wb-browse.js b/frontend/src/js/page/wb-browse.js index 30eed7a..be0cdb3 100644 --- a/frontend/src/js/page/wb-browse.js +++ b/frontend/src/js/page/wb-browse.js @@ -1,12 +1,18 @@ import { h, Component } from 'preact'; import WBNavbar from 'wb-navbar'; import WBProjectListing from 'wb-project-listing'; +import WBInlineSearch from 'wb-inline-search'; class WBBrowse extends Component { render({ ownerUuid }) { return (
    - + + ) } /> +
    +
    +
    +

    Sign In

    +
    +
    + + +
    +
    + + +
    + +
    +
    +
    +
    +
    + ); + } +} + +export default WBLogin; diff --git a/frontend/src/js/widget/wb-navbar.js b/frontend/src/js/widget/wb-navbar.js new file mode 100644 index 0000000..2ab453c --- /dev/null +++ b/frontend/src/js/widget/wb-navbar.js @@ -0,0 +1,87 @@ +import { h, Component } from 'preact'; + +class WBNavbar extends Component { + render({ title, items, rhs, onItemClicked }) { + return ( + + ); + } +} + +WBNavbar.defaultProps = { + 'title': 'Workbench Advanced', + 'items': [], + 'form': null, + 'onItemClicked': () => {} +} + +export default WBNavbar; From 5c30ed4766ca1b3d4a84045fef29d9459739dcf5 Mon Sep 17 00:00:00 2001 From: Stanislaw Adaszewski Date: Fri, 31 Jan 2020 12:21:45 +0100 Subject: [PATCH 011/170] Sign in/out seems to work. --- frontend/src/js/misc/make-arvados-request.js | 3 ++ frontend/src/js/page/wb-app.js | 32 +++++++++++----- frontend/src/js/page/wb-browse.js | 7 ++-- frontend/src/js/page/wb-landing-page.js | 20 ++++++++++ frontend/src/js/page/wb-sign-in.js | 39 ++++++++++++++++---- frontend/src/js/widget/wb-navbar.js | 12 ++++-- 6 files changed, 90 insertions(+), 23 deletions(-) create mode 100644 frontend/src/js/page/wb-landing-page.js diff --git a/frontend/src/js/misc/make-arvados-request.js b/frontend/src/js/misc/make-arvados-request.js index dc12cc0..b9b1e74 100644 --- a/frontend/src/js/misc/make-arvados-request.js +++ b/frontend/src/js/misc/make-arvados-request.js @@ -2,6 +2,9 @@ function makeArvadosRequest(arvHost, arvToken, endpoint, method='GET', data=null, contentType='application/json;charset=utf-8', responseType='json') { + if (!arvHost || !arvToken) + return new Promise((accept, reject) => reject()); + let xhr = new XMLHttpRequest(); xhr.open(method, 'https://' + arvHost + endpoint); xhr.setRequestHeader('Authorization', 'OAuth2 ' + arvToken); diff --git a/frontend/src/js/page/wb-app.js b/frontend/src/js/page/wb-app.js index 6afce09..c0674b8 100644 --- a/frontend/src/js/page/wb-app.js +++ b/frontend/src/js/page/wb-app.js @@ -1,24 +1,36 @@ import { h, Component } from 'preact'; import { Router, route } from 'preact-router'; -import WBTabs from 'wb-tabs'; -import WBTable from 'wb-table'; -import WBPagination from 'wb-pagination'; -import WBProjectListing from 'wb-project-listing'; -import WBNavbar from 'wb-navbar'; import WBBrowse from 'wb-browse'; import WBSignIn from 'wb-sign-in'; +import WBLandingPage from 'wb-landing-page'; class WBApp extends Component { - render({}, { activePage }) { + constructor(...args) { + super(...args); + this.state.arvHost = window.localStorage['arvHost']; + this.state.arvToken = window.localStorage['arvToken']; + this.appCallbacks = { + 'navbarItemClicked': this.navbarItemClicked + }; + } + + navbarItemClicked(item) { + if (item['id'] === 'sign-out') { + delete window.localStorage['arvHost']; + delete window.localStorage['arvToken']; + delete window.localStorage['currentUser']; + route('/sign-in'); + } + } + + render({}, { activePage, arvHost, arvToken }) { return ( -
    - Hello, world! -
    + - +
    ); } diff --git a/frontend/src/js/page/wb-browse.js b/frontend/src/js/page/wb-browse.js index be0cdb3..3ad52c7 100644 --- a/frontend/src/js/page/wb-browse.js +++ b/frontend/src/js/page/wb-browse.js @@ -4,15 +4,16 @@ import WBProjectListing from 'wb-project-listing'; import WBInlineSearch from 'wb-inline-search'; class WBBrowse extends Component { - render({ ownerUuid }) { + render({ ownerUuid, appCallbacks }) { return (
    - ) } /> + ) } onItemClicked={ appCallbacks.navbarItemClicked } /> + route('/browse/' + xhr.response['uuid'])); + prom = prom.catch(() => route('/sign-in')); + } + + render() { + return ( +
    Please wait...
    + ); + } +} + +export default WBLandingPage; diff --git a/frontend/src/js/page/wb-sign-in.js b/frontend/src/js/page/wb-sign-in.js index dc1706d..a9ea513 100644 --- a/frontend/src/js/page/wb-sign-in.js +++ b/frontend/src/js/page/wb-sign-in.js @@ -1,24 +1,49 @@ import { h, Component } from 'preact'; +import { route } from 'preact-router'; import WBNavbar from 'wb-navbar'; +import linkState from 'linkstate'; +import makeArvadosRequest from 'make-arvados-request'; -class WBLogin extends Component { - render() { +class WBSignIn extends Component { + onSubmit() { + let { arvHost, arvToken } = this.state; + window.localStorage['arvHost'] = arvHost; + window.localStorage['arvToken'] = arvToken; + let prom = makeArvadosRequest(arvHost, arvToken, '/arvados/v1/users/current'); + prom = prom.then(xhr => { + window.localStorage['currentUser'] = JSON.stringify(xhr.response); + route('/browse/' + xhr.response['uuid']); + }); + prom = prom.catch(() => { + alert('Sign in unsuccessful. Verify your input and try again.') + }); + } + + render({}, { arvHost, arvToken }) { return (
    -
    + +

    Sign In

    - +
    - +
    - +
    @@ -28,4 +53,4 @@ class WBLogin extends Component { } } -export default WBLogin; +export default WBSignIn; diff --git a/frontend/src/js/widget/wb-navbar.js b/frontend/src/js/widget/wb-navbar.js index 2ab453c..c5bbef5 100644 --- a/frontend/src/js/widget/wb-navbar.js +++ b/frontend/src/js/widget/wb-navbar.js @@ -1,10 +1,10 @@ import { h, Component } from 'preact'; class WBNavbar extends Component { - render({ title, items, rhs, onItemClicked }) { + render({ title, items, rhs, onItemClicked, onTitleClicked }) { return ( + ); + } +} + +WBBreadcrumbs.defaultProps = { + 'onItemClicked': () => {} +} + +export default WBBreadcrumbs; From a7c041799bba1f4e7a2156f65514516b643c0e41 Mon Sep 17 00:00:00 2001 From: Stanislaw Adaszewski Date: Fri, 31 Jan 2020 14:24:01 +0100 Subject: [PATCH 014/170] Some improvements to navigation. --- frontend/src/js/component/wb-project-crumbs.js | 7 ++++++- frontend/src/js/page/wb-app.js | 15 ++++++++++++--- frontend/src/js/page/wb-browse.js | 3 ++- frontend/src/js/page/wb-sign-in.js | 8 ++++++-- 4 files changed, 26 insertions(+), 7 deletions(-) diff --git a/frontend/src/js/component/wb-project-crumbs.js b/frontend/src/js/component/wb-project-crumbs.js index 41a74e1..d1c4652 100644 --- a/frontend/src/js/component/wb-project-crumbs.js +++ b/frontend/src/js/component/wb-project-crumbs.js @@ -5,10 +5,15 @@ import fetchProjectParents from 'fetch-project-parents'; class WBProjectCrumbs extends Component { constructor(...args) { super(...args); - this.state.items = []; + this.state.items = [ { 'name': 'All Projects' } ]; } fetchCrumbs() { + if (!this.props.uuid) { + this.setState({ 'items': [ { 'name': 'All Projects' } ] }); + return; + } + let { arvHost, arvToken } = this.props.appState; let prom = fetchProjectParents(arvHost, arvToken, this.props.uuid); prom = prom.then(parents => this.setState({ 'items': parents })); diff --git a/frontend/src/js/page/wb-app.js b/frontend/src/js/page/wb-app.js index 2a95d2e..85c1138 100644 --- a/frontend/src/js/page/wb-app.js +++ b/frontend/src/js/page/wb-app.js @@ -9,12 +9,14 @@ class WBApp extends Component { super(...args); this.state.arvHost = window.localStorage['arvHost']; this.state.arvToken = window.localStorage['arvToken']; + this.state.currentUser = JSON.parse(window.localStorage['currentUser']); this.appCallbacks = { - 'navbarItemClicked': this.navbarItemClicked + 'navbarItemClicked': item => this.navbarItemClicked(item) }; this.appState = { 'arvHost': this.state.arvHost, - 'arvToken': this.state.arvToken + 'arvToken': this.state.arvToken, + 'currentUser': this.state.currentUser }; } @@ -24,6 +26,13 @@ class WBApp extends Component { delete window.localStorage['arvToken']; delete window.localStorage['currentUser']; route('/sign-in'); + + } else if (item['id'] === 'home') { + route('/browse/' + this.appState.currentUser.uuid); + + } else if (item['id'] === 'all-projects') { + route('/browse'); + } } @@ -32,7 +41,7 @@ class WBApp extends Component { - + diff --git a/frontend/src/js/page/wb-sign-in.js b/frontend/src/js/page/wb-sign-in.js index a9ea513..cacc7f4 100644 --- a/frontend/src/js/page/wb-sign-in.js +++ b/frontend/src/js/page/wb-sign-in.js @@ -6,12 +6,16 @@ import makeArvadosRequest from 'make-arvados-request'; class WBSignIn extends Component { onSubmit() { + let { appState } = this.props; let { arvHost, arvToken } = this.state; - window.localStorage['arvHost'] = arvHost; - window.localStorage['arvToken'] = arvToken; let prom = makeArvadosRequest(arvHost, arvToken, '/arvados/v1/users/current'); prom = prom.then(xhr => { + window.localStorage['arvHost'] = arvHost; + window.localStorage['arvToken'] = arvToken; window.localStorage['currentUser'] = JSON.stringify(xhr.response); + appState.arvHost = arvHost; + appState.arvToken = arvToken; + appState.currentUser = xhr.response; route('/browse/' + xhr.response['uuid']); }); prom = prom.catch(() => { From d879964fdcff2c9283197906bb2e24bc5f5ed386 Mon Sep 17 00:00:00 2001 From: Stanislaw Adaszewski Date: Fri, 31 Jan 2020 16:21:06 +0100 Subject: [PATCH 015/170] Object type tabs mockup. --- frontend/src/js/page/wb-browse.js | 5 +++++ frontend/src/js/widget/wb-tabs.js | 4 ++++ 2 files changed, 9 insertions(+) diff --git a/frontend/src/js/page/wb-browse.js b/frontend/src/js/page/wb-browse.js index 1360613..9d010b7 100644 --- a/frontend/src/js/page/wb-browse.js +++ b/frontend/src/js/page/wb-browse.js @@ -4,6 +4,7 @@ import WBNavbar from 'wb-navbar'; import WBProjectListing from 'wb-project-listing'; import WBInlineSearch from 'wb-inline-search'; import WBProjectCrumbs from 'wb-project-crumbs'; +import WBTabs from 'wb-tabs'; class WBBrowse extends Component { render({ ownerUuid, activePage, appCallbacks, appState }) { @@ -20,12 +21,16 @@ class WBBrowse extends Component { route('/browse/' + item['uuid']) } /> + + route('/browse/' + (ownerUuid || '') + '/' + i)} /> + +
    ); } diff --git a/frontend/src/js/widget/wb-tabs.js b/frontend/src/js/widget/wb-tabs.js index 61c711b..140b6b7 100644 --- a/frontend/src/js/widget/wb-tabs.js +++ b/frontend/src/js/widget/wb-tabs.js @@ -30,4 +30,8 @@ class WBTabs extends Component { } } +WBTabs.defaultProps = { + 'onTabChanged': () => {} +}; + export default WBTabs; From 5e94a2581618f69be2c2279ef0b27828e4ec76f8 Mon Sep 17 00:00:00 2001 From: Stanislaw Adaszewski Date: Fri, 31 Jan 2020 18:10:54 +0100 Subject: [PATCH 016/170] Filtering by container_request state. --- .../src/js/component/wb-process-listing.js | 97 +++++++++++++++++++ frontend/src/js/page/wb-app.js | 5 +- frontend/src/js/page/wb-browse.js | 35 ++++++- frontend/src/js/widget/wb-checkboxes.js | 30 ++++++ frontend/src/js/widget/wb-tabs.js | 2 +- 5 files changed, 164 insertions(+), 5 deletions(-) create mode 100644 frontend/src/js/component/wb-process-listing.js create mode 100644 frontend/src/js/widget/wb-checkboxes.js diff --git a/frontend/src/js/component/wb-process-listing.js b/frontend/src/js/component/wb-process-listing.js new file mode 100644 index 0000000..2d1021c --- /dev/null +++ b/frontend/src/js/component/wb-process-listing.js @@ -0,0 +1,97 @@ +import { h, Component } from 'preact'; +import { route } from 'preact-router'; +import makeArvadosRequest from 'make-arvados-request'; +import WBTable from 'wb-table'; +import WBPagination from 'wb-pagination'; +import WBCheckboxes from 'wb-checkboxes'; + +class WBProcessListing extends Component { + + constructor(...args) { + super(...args); + this.state.rows = []; + this.state.numPages = 0; + this.state.requestStates = [ 'Uncommitted', 'Committed', 'Final' ]; + this.state.containerStates = [ 'Queued', 'Locked', 'Running', 'Cancelled', 'Complete' ]; + this.state.reqStateMask = [ true, true, true ]; + this.state.contStateMask = [ true, true, true, true, true ]; + } + + componentDidMount() { + this.fetchItems(); + } + + prepareRows(items) { + return items.map(item => [ + (), + item['state'], + item['owner_uuid'], + item['created_at'].replace('T', ' ').substr(0, item['created_at'].length - 11) + '', + item['output_uuid'], + (
    + +
    ) + ]); + } + + fetchItems() { + let { arvHost, arvToken } = this.props.appState; + let i = this.props.activePage; + let filters = [ + [ 'state', 'in', this.state.requestStates.filter((_, idx) => this.state.reqStateMask[idx]) ] + ]; + if (this.props.ownerUuid) + filters.push([ 'owner_uuid', '=', this.props.ownerUuid ]); + let prom = makeArvadosRequest(arvHost, arvToken, + '/arvados/v1/container_requests?filters=' + encodeURIComponent(JSON.stringify(filters)) + + '&limit=' + encodeURIComponent(this.props.itemsPerPage) + + '&offset=' + encodeURIComponent(this.props.itemsPerPage * i)); + prom = prom.then(xhr => + this.setState({ + 'numPages': Math.ceil(xhr.response['items_available'] / xhr.response['limit']), + 'rows': this.prepareRows(xhr.response['items']) + })); + } + + componentWillReceiveProps(nextProps, nextState) { + // this.setState({ 'rows': [] }); // .rows = []; + this.props = nextProps; + this.fetchItems(); + } + + render({ appState, ownerUuid, activePage, onPageChanged }, + { rows, numPages, requestStates, containerStates, + reqStateMask, contStateMask }) { + + return ( +
    + this.fetchItems() } /> + this.fetchItems() } /> + + + + onPageChanged(i) } /> +
    + ); + } +} + +WBProcessListing.defaultProps = { + 'itemsPerPage': 100, + 'ownerUuid': null +}; + +export default WBProcessListing; diff --git a/frontend/src/js/page/wb-app.js b/frontend/src/js/page/wb-app.js index 85c1138..6ebb0b6 100644 --- a/frontend/src/js/page/wb-app.js +++ b/frontend/src/js/page/wb-app.js @@ -9,7 +9,8 @@ class WBApp extends Component { super(...args); this.state.arvHost = window.localStorage['arvHost']; this.state.arvToken = window.localStorage['arvToken']; - this.state.currentUser = JSON.parse(window.localStorage['currentUser']); + if ('currentUser' in window.localStorage) + this.state.currentUser = JSON.parse(window.localStorage['currentUser']); this.appCallbacks = { 'navbarItemClicked': item => this.navbarItemClicked(item) }; @@ -43,7 +44,7 @@ class WBApp extends Component { - diff --git a/frontend/src/js/page/wb-browse.js b/frontend/src/js/page/wb-browse.js index 9d010b7..6e78c35 100644 --- a/frontend/src/js/page/wb-browse.js +++ b/frontend/src/js/page/wb-browse.js @@ -5,9 +5,22 @@ import WBProjectListing from 'wb-project-listing'; import WBInlineSearch from 'wb-inline-search'; import WBProjectCrumbs from 'wb-project-crumbs'; import WBTabs from 'wb-tabs'; +import WBProcessListing from 'wb-process-listing'; class WBBrowse extends Component { - render({ ownerUuid, activePage, appCallbacks, appState }) { + route(params) { + route('/browse/' + + ('ownerUuid' in params ? params.ownerUuid : (this.props.ownerUuid || '')) + '/' + + ('activePage' in params ? params.activePage : (this.props.activePage || '')) + '/' + + ('objTypeTab' in params ? params.objTypeTab : (this.props.objTypeTab || '')) + '/' + + ('collectionPage' in params ? params.collectionPage : (this.props.collectionPage || '')) + '/' + + ('processPage' in params ? params.processPage : (this.props.processPage || '')) + '/' + + ('workflowPage' in params ? params.workflowPage : (this.props.workflowPage || ''))); + } + + render({ ownerUuid, activePage, appCallbacks, appState, + objTypeTab, collectionPage, processPage, workflowPage }) { + return (
    route('/browse/' + (ownerUuid || '') + '/' + i)} /> - + this.route({ 'objTypeTab': tab['id'] }) } /> + + { + (!objTypeTab || objTypeTab === 'collection') ? ( + null + ) : (objTypeTab === 'process' ? ( + this.route({ 'processPage': i }) } /> + ) : (objTypeTab === 'workflow' ? ( + null + ) : null)) + }
    ); } diff --git a/frontend/src/js/widget/wb-checkboxes.js b/frontend/src/js/widget/wb-checkboxes.js new file mode 100644 index 0000000..48792ab --- /dev/null +++ b/frontend/src/js/widget/wb-checkboxes.js @@ -0,0 +1,30 @@ +import { h, Component } from 'preact'; + +class WBCheckboxes extends Component { + render({ items, checked, onChange, cssClass, title }) { + return ( +
    + { title } + { + items.map((name, idx) => ( + + )) + } +
    + ); + } +} + +WBCheckboxes.defaultProps = { + 'checked': [], + 'onChange': () => {} +} + +export default WBCheckboxes; diff --git a/frontend/src/js/widget/wb-tabs.js b/frontend/src/js/widget/wb-tabs.js index 140b6b7..4427df6 100644 --- a/frontend/src/js/widget/wb-tabs.js +++ b/frontend/src/js/widget/wb-tabs.js @@ -21,7 +21,7 @@ class WBTabs extends Component { cls = cls.join(' '); return ( ); }) } From a369d1e02e6d23f8abd2cd4b0fa5410621b3e7bd Mon Sep 17 00:00:00 2001 From: Stanislaw Adaszewski Date: Sat, 1 Feb 2020 14:51:04 +0100 Subject: [PATCH 017/170] Started implementing process view. --- .../src/js/component/wb-arvados-crumbs.js | 39 +++++++++++++++ frontend/src/js/component/wb-navbar-common.js | 24 +++++++++ frontend/src/js/misc/arvados-type-name.js | 13 +++++ frontend/src/js/misc/fetch-object-parents.js | 49 +++++++++++++++++++ frontend/src/js/page/wb-app.js | 14 ++++++ frontend/src/js/page/wb-process-view.js | 19 +++++++ frontend/src/js/widget/wb-navbar.js | 7 +-- 7 files changed, 162 insertions(+), 3 deletions(-) create mode 100644 frontend/src/js/component/wb-arvados-crumbs.js create mode 100644 frontend/src/js/component/wb-navbar-common.js create mode 100644 frontend/src/js/misc/arvados-type-name.js create mode 100644 frontend/src/js/misc/fetch-object-parents.js create mode 100644 frontend/src/js/page/wb-process-view.js diff --git a/frontend/src/js/component/wb-arvados-crumbs.js b/frontend/src/js/component/wb-arvados-crumbs.js new file mode 100644 index 0000000..f4612f1 --- /dev/null +++ b/frontend/src/js/component/wb-arvados-crumbs.js @@ -0,0 +1,39 @@ +import { h, Component } from 'preact'; +import WBBreadcrumbs from 'wb-breadcrumbs'; +import fetchObjectParents from 'fetch-object-parents'; + +class WBArvadosCrumbs extends Component { + constructor(...args) { + super(...args); + this.state.items = [ { 'name': 'All Projects' } ]; + } + + fetchCrumbs() { + if (!this.props.uuid) { + this.setState({ 'items': [ { 'name': 'All Projects' } ] }); + return; + } + + let { arvHost, arvToken } = this.props.app.state; + let prom = fetchObjectParents(arvHost, arvToken, this.props.uuid); + prom = prom.then(parents => this.setState({ 'items': parents })); + } + + componentDidMount() { + this.fetchCrumbs(); + } + + componentWillReceiveProps(nextProps) { + this.props = nextProps; + this.fetchCrumbs(); + } + + render({ app }, { items }) { + return ( + app.breadcrumbClicked(item) } /> + ); + } +} + +export default WBArvadosCrumbs; diff --git a/frontend/src/js/component/wb-navbar-common.js b/frontend/src/js/component/wb-navbar-common.js new file mode 100644 index 0000000..f07610d --- /dev/null +++ b/frontend/src/js/component/wb-navbar-common.js @@ -0,0 +1,24 @@ +import { h, Component } from 'preact'; +import WBNavbar from 'wb-navbar'; +import WBInlineSearch from 'wb-inline-search'; + +class WBNavbarCommon extends Component { + render({ app, items, activeItem }) { + return ( + + ) } onItemClicked={ item => app.navbarItemClicked(item) } + activeItem={ activeItem } /> + ); + } +} + +WBNavbarCommon.defaultProps = { + 'items': [] +}; + +export default WBNavbarCommon; diff --git a/frontend/src/js/misc/arvados-type-name.js b/frontend/src/js/misc/arvados-type-name.js new file mode 100644 index 0000000..59c486c --- /dev/null +++ b/frontend/src/js/misc/arvados-type-name.js @@ -0,0 +1,13 @@ +const typeIdToName = { + 'tpzed': 'user', + 'j7d0g': 'group', + 'xvhdp': 'container_request', + 'dz642': 'container', + '7fd4e': 'workflow' +}; + +function arvadosTypeName(id) { + return typeIdToName[id]; +} + +export default arvadosTypeName; diff --git a/frontend/src/js/misc/fetch-object-parents.js b/frontend/src/js/misc/fetch-object-parents.js new file mode 100644 index 0000000..65a06fe --- /dev/null +++ b/frontend/src/js/misc/fetch-object-parents.js @@ -0,0 +1,49 @@ +import makeArvadosRequest from 'make-arvados-request'; +import arvadosTypeName from 'arvados-type-name'; + +function fetchObjectParents(arvHost, arvToken, uuid) { + let parents = []; + + let cb = xhr => { + let objectType = arvadosTypeName(xhr.response['uuid'].split('-')[1]); + + let item = { + 'uuid': xhr.response['uuid'] + }; + + if (objectType === 'user') { + item['name'] = xhr.response['first_name'] + ' ' + xhr.response['last_name']; + + } else { + item['name'] = xhr.response['name']; + } + + if (objectType === 'group') { + item['group_class'] = xhr.response['group_class']; + } + + parents.push(item); + + if (!xhr.response['owner_uuid'] || + xhr.response['owner_uuid'].endsWith('-tpzed-000000000000000')) { + + return parents.reverse(); + } + + objectType = arvadosTypeName(xhr.response['owner_uuid'].split('-')[1]); + + return makeArvadosRequest(arvHost, arvToken, + '/arvados/v1/' + objectType + 's' + + '/' + xhr.response['owner_uuid']).then(cb); + }; + + let objectType = arvadosTypeName(uuid.split('-')[1]); + let prom = makeArvadosRequest(arvHost, arvToken, + '/arvados/v1/' + objectType + 's' + + '/' + uuid); + prom = prom.then(cb); + + return prom; +} + +export default fetchObjectParents; diff --git a/frontend/src/js/page/wb-app.js b/frontend/src/js/page/wb-app.js index 6ebb0b6..e4a71e7 100644 --- a/frontend/src/js/page/wb-app.js +++ b/frontend/src/js/page/wb-app.js @@ -3,6 +3,8 @@ import { Router, route } from 'preact-router'; import WBBrowse from 'wb-browse'; import WBSignIn from 'wb-sign-in'; import WBLandingPage from 'wb-landing-page'; +import WBProcessView from 'wb-process-view'; +import arvadosTypeName from 'arvados-type-name'; class WBApp extends Component { constructor(...args) { @@ -37,6 +39,16 @@ class WBApp extends Component { } } + breadcrumbClicked(item) { + let objectType = arvadosTypeName(item.uuid.split('-')[1]); + if (objectType === 'user') + route('/browse/' + item.uuid) + else if (objectType === 'group' && item.group_class === 'project') + route('/browse/' + item.uuid); + else if (objectType === 'container_request') + route('/process/' + item.uuid) + } + render() { return ( @@ -47,6 +59,8 @@ class WBApp extends Component { + + ); } diff --git a/frontend/src/js/page/wb-process-view.js b/frontend/src/js/page/wb-process-view.js new file mode 100644 index 0000000..655b11e --- /dev/null +++ b/frontend/src/js/page/wb-process-view.js @@ -0,0 +1,19 @@ +import { h, Component } from 'preact'; +import WBNavbarCommon from 'wb-navbar-common'; +import WBArvadosCrumbs from 'wb-arvados-crumbs'; + +class WBProcessView extends Component { + render({ app, uuid }) { + return ( +
    + + + + +
    This is the process view for { uuid }
    +
    + ); + } +} + +export default WBProcessView; diff --git a/frontend/src/js/widget/wb-navbar.js b/frontend/src/js/widget/wb-navbar.js index c5bbef5..b44686f 100644 --- a/frontend/src/js/widget/wb-navbar.js +++ b/frontend/src/js/widget/wb-navbar.js @@ -1,7 +1,7 @@ import { h, Component } from 'preact'; class WBNavbar extends Component { - render({ title, items, rhs, onItemClicked, onTitleClicked }) { + render({ title, items, rhs, onItemClicked, onTitleClicked, activeItem }) { return (
    ) ], [ 'Modified by Client', item.modified_by_client_uuid ], - [ 'API Url', 'https://' + app.state.arvHost + '/arvados/v1' + item.href ], + [ 'API Url', ( + + { 'https://' + app.state.arvHost + '/arvados/v1' + item.href } + + ) ], [ 'ETag', item.etag ] ]; this.setState({ 'rows': rows }); @@ -78,13 +82,12 @@ class WBCommonFields extends Component { render({}, { rows }) { return ( -
    -

    Common Fields

    - { rows ? ( - - ) :
    Loading...
    } -
    + rows ? ( + + ) : ( +
    Loading...
    + ) ); } } diff --git a/frontend/src/js/component/wb-container-request-fields.js b/frontend/src/js/component/wb-container-request-fields.js new file mode 100644 index 0000000..d2b500b --- /dev/null +++ b/frontend/src/js/component/wb-container-request-fields.js @@ -0,0 +1,124 @@ +import { h, Component } from 'preact'; +import WBTable from 'wb-table'; +import makeArvadosRequest from 'make-arvados-request'; +import arvadosTypeName from 'arvados-type-name'; +import arvadosObjectName from 'arvados-object-name'; +import urlForObject from 'url-for-object'; +import wbFormatDate from 'wb-format-date'; +import WBNameAndUuid from 'wb-name-and-uuid'; +import WBAccordion from 'wb-accordion'; + +class WBContainerRequestFields extends Component { + componentDidMount() { + this.prepareRows(); + } + + componentWillReceiveProps(nextProps) { + this.props = nextProps; + this.prepareRows(); + } + + prepareRows() { + let { uuid, app } = this.props; + let { arvHost, arvToken } = app.state; + + let item; + let prom = makeArvadosRequest(arvHost, arvToken, + '/arvados/v1/container_requests/' + uuid); + prom = prom.then(xhr => (item = xhr.response)); + + prom = prom.then(() => { + let rows = [ + [ 'Name', item.name ], + [ 'Description', item.description || ({ String(item.description) }) ], + [ 'Properties', ( + +
    { JSON.stringify(item.properties, null, 2) }
    +
    + ) ], + [ 'State', item.state ], + [ 'Requesting Container', ( + + ) ], + [ 'Container', ( + + ) ], + [ 'Container Count Max', item.container_count_max ], + [ 'Mounts', ( + + { Object.keys(item.mounts).map(k => ( +
    { JSON.stringify(item.mounts[k], null, 2) }
    + )) } +
    + ) ], + [ 'Runtime Constraints', ( + +
    { JSON.stringify(item.runtime_constraints, null, 2) }
    +
    + ) ], + [ 'Scheduling Parameters', ( + +
    { JSON.stringify(item.scheduling_parameters, null, 2) }
    +
    + ) ], + [ 'Container Image', ( + + ) ], + [ 'Environment', ( + +
    { JSON.stringify(item.environment, null, 2) }
    +
    + ) ], + [ 'Working Directory', item.cwd ], + [ 'Command', ( +
    { JSON.stringify(item.command) }
    + ) ], + [ 'Output Path', item.output_path ], + [ 'Output Name', item.output_name ], + [ 'Output TTL', item.output_ttl ], + [ 'Priority', item.priority ], + [ 'Expires At', wbFormatDate(item.expires_at) ], + [ 'Use Existing', String(item.use_existing) ], + [ 'Log', ( + + ) ], + [ 'Output', ( + + ) ], + [ 'Filters', ( + item.filters ? (
    { item.filters }
    ) : ({ String(item.filters) }) + ) ], + [ 'Runtime Token', item.runtime_token || ({ String(item.runtime_token) }) ], + [ 'Runtime User', ( + + ) ], + [ 'Runtime Auth Scopes', ( + item.runtime_auth_scopes ? ( +
    { JSON.stringify(item.runtime_auth_scopes, null, 2) }
    + ) : ( + { String(item.runtime_auth_scopes) } + ) + ) ] + ]; + this.setState({ 'rows': rows }); + }); + } + + render({}, { rows }) { + return ( + rows ? ( + + ) : ( +
    Loading...
    + ) + ); + } +} + +export default WBContainerRequestFields; diff --git a/frontend/src/js/component/wb-name-and-uuid.js b/frontend/src/js/component/wb-name-and-uuid.js new file mode 100644 index 0000000..d3d414d --- /dev/null +++ b/frontend/src/js/component/wb-name-and-uuid.js @@ -0,0 +1,78 @@ +import { h, Component } from 'preact'; +import makeArvadosRequest from 'make-arvados-request'; +import urlForObject from 'url-for-object'; +import arvadosObjectName from 'arvados-object-name'; +import arvadosTypeName from 'arvados-type-name'; + +class WBNameAndUuid extends Component { + componentDidMount() { + let { uuid, app } = this.props; + let { arvHost, arvToken } = app.state; + + if (!uuid) + return; + + let prom = new Promise(accept => accept()); + + if (/[0-9a-f]{32}\+[0-9]+/g.exec(uuid)) { + let filters = [ + ['portable_data_hash', '=', uuid] + ]; + prom = prom.then(() => makeArvadosRequest(arvHost, arvToken, + '/arvados/v1/collections?filters=' + + encodeURIComponent(JSON.stringify(filters)))); + prom = prom.then(xhr => { + if (xhr.response.items.length === 0) { + this.setState({ + 'item': { + 'uuid': uuid, + 'name': 'Collection with portable data hash ' + uuid + } + }); + return; + } + let item = xhr.response.items[0]; + if (xhr.response.items.length > 1) + item.name += ' +' + (xhr.response.items.length - 1) + ' others'; + this.setState({ item }); + }); + + } else if (/[a-z0-9]{5}-[a-z0-9]{5}-[a-z0-9]{15}/.exec(uuid)) { + let typeName = arvadosTypeName(uuid); + let prom = makeArvadosRequest(arvHost, arvToken, + '/arvados/v1/' + typeName + 's/' + uuid ); + prom = prom.then(xhr => this.setState({ + 'item': xhr.response + })); + + } else { + this.setState({ + 'item': { + 'uuid': uuid + } + }); + } + } + + render({ uuid }, { item }) { + if (!uuid) + return ( +
    { String(uuid) }
    + ); + + return ( +
    +
    + { item ? ( + { arvadosObjectName(item) } + ) : 'Loading...' } +
    +
    + { uuid } +
    +
    + ); + } +} + +export default WBNameAndUuid; diff --git a/frontend/src/js/misc/arvados-object-name.js b/frontend/src/js/misc/arvados-object-name.js index ba670ec..7aad7fd 100644 --- a/frontend/src/js/misc/arvados-object-name.js +++ b/frontend/src/js/misc/arvados-object-name.js @@ -4,6 +4,8 @@ function arvadosObjectName(item) { let typeName = arvadosTypeName(item['uuid']); if (typeName === 'user') return (item.first_name + ' ' + item.last_name); + else if (typeName === 'container') + return ('Container running image ' + item.container_image); else return item.name; } diff --git a/frontend/src/js/misc/arvados-type-name.js b/frontend/src/js/misc/arvados-type-name.js index af77515..f547dc1 100644 --- a/frontend/src/js/misc/arvados-type-name.js +++ b/frontend/src/js/misc/arvados-type-name.js @@ -9,6 +9,9 @@ const typeIdToName = { }; function arvadosTypeName(id) { + if (!id) + return; + if (id.length === 5) return typeIdToName[id]; else diff --git a/frontend/src/js/page/wb-process-view.js b/frontend/src/js/page/wb-process-view.js index 1c65a80..254635f 100644 --- a/frontend/src/js/page/wb-process-view.js +++ b/frontend/src/js/page/wb-process-view.js @@ -7,6 +7,7 @@ import arvadosTypeName from 'arvados-type-name'; import urlForObject from 'url-for-object'; import detectHashes from 'detect-hashes'; import WBCommonFields from 'wb-common-fields'; +import WBContainerRequestFields from 'wb-container-request-fields'; class WBProcessView extends Component { constructor(...args) { @@ -88,8 +89,12 @@ class WBProcessView extends Component { This is the process view for { uuid }
    +

    Common Fields

    +

    Container Request Fields

    + + diff --git a/frontend/src/js/widget/wb-accordion.js b/frontend/src/js/widget/wb-accordion.js new file mode 100644 index 0000000..ca44a0b --- /dev/null +++ b/frontend/src/js/widget/wb-accordion.js @@ -0,0 +1,40 @@ +import { h, Component } from 'preact'; + +class WBAccordion extends Component { + constructor(...args) { + super(...args); + this.state.domId = 'accordion-' + uuid.v4(); + this.state.headerDomIds = this.props.names.map(() => ('accordion-' + uuid.v4())); + this.state.collapseDomIds = this.props.names.map(() => ('accordion-' + uuid.v4())); + } + + render({ children, names, cardHeaderClass }, { domId, headerDomIds, collapseDomIds }) { + return ( +
    + { children.map((_, i) => ( +
    +
    +

    + +

    +
    + +
    +
    + { children[i] } +
    +
    +
    + )) } +
    + ); + } +}; + +WBAccordion.defaultProps = { + 'cardHeaderClass': 'card-header' +}; + +export default WBAccordion; diff --git a/frontend/src/js/widget/wb-table.js b/frontend/src/js/widget/wb-table.js index c698135..fda9bcf 100644 --- a/frontend/src/js/widget/wb-table.js +++ b/frontend/src/js/widget/wb-table.js @@ -1,12 +1,12 @@ import { h, Component } from 'preact'; class WBTable extends Component { - render({ columns, rows }) { + render({ columns, rows, headerClasses }) { return ( - { columns.map(c => ) } + { columns.map((c, i) => ) } @@ -23,4 +23,8 @@ class WBTable extends Component { } } +WBTable.defaultProps = { + 'headerClasses': [] +}; + export default WBTable; From 7b317f169dbc58d05c0fce7eca7cfd53d833e7e7 Mon Sep 17 00:00:00 2001 From: Stanislaw Adaszewski Date: Sun, 2 Feb 2020 15:44:43 +0100 Subject: [PATCH 022/170] Improvements to field tables. --- frontend/src/js/component/wb-common-fields.js | 2 ++ frontend/src/js/component/wb-container-request-fields.js | 3 ++- frontend/src/js/component/wb-name-and-uuid.js | 9 ++++++--- frontend/src/js/widget/wb-table.js | 8 ++++++-- 4 files changed, 16 insertions(+), 6 deletions(-) diff --git a/frontend/src/js/component/wb-common-fields.js b/frontend/src/js/component/wb-common-fields.js index 54a6486..10e2b70 100644 --- a/frontend/src/js/component/wb-common-fields.js +++ b/frontend/src/js/component/wb-common-fields.js @@ -84,6 +84,8 @@ class WBCommonFields extends Component { return ( rows ? ( ) : (
    Loading...
    diff --git a/frontend/src/js/component/wb-container-request-fields.js b/frontend/src/js/component/wb-container-request-fields.js index d2b500b..51f4826 100644 --- a/frontend/src/js/component/wb-container-request-fields.js +++ b/frontend/src/js/component/wb-container-request-fields.js @@ -113,7 +113,8 @@ class WBContainerRequestFields extends Component { rows ? ( + rows={ rows } + verticalHeader={ true } /> ) : (
    Loading...
    ) diff --git a/frontend/src/js/component/wb-name-and-uuid.js b/frontend/src/js/component/wb-name-and-uuid.js index d3d414d..fe0abad 100644 --- a/frontend/src/js/component/wb-name-and-uuid.js +++ b/frontend/src/js/component/wb-name-and-uuid.js @@ -44,6 +44,9 @@ class WBNameAndUuid extends Component { prom = prom.then(xhr => this.setState({ 'item': xhr.response })); + prom = prom.catch(xhr => this.setState({ + 'error': 'Unable to retrieve: ' + xhr.status + ' (' + xhr.statusText + ')' + })); } else { this.setState({ @@ -54,7 +57,7 @@ class WBNameAndUuid extends Component { } } - render({ uuid }, { item }) { + render({ uuid }, { error, item }) { if (!uuid) return (
    { String(uuid) }
    @@ -63,9 +66,9 @@ class WBNameAndUuid extends Component { return (
    - { item ? ( + { error ? error : (item ? ( { arvadosObjectName(item) } - ) : 'Loading...' } + ) : 'Loading...') }
    { uuid } diff --git a/frontend/src/js/widget/wb-table.js b/frontend/src/js/widget/wb-table.js index fda9bcf..f590278 100644 --- a/frontend/src/js/widget/wb-table.js +++ b/frontend/src/js/widget/wb-table.js @@ -1,7 +1,7 @@ import { h, Component } from 'preact'; class WBTable extends Component { - render({ columns, rows, headerClasses }) { + render({ columns, rows, headerClasses, verticalHeader }) { return (
    { c }{ c }
    @@ -13,7 +13,11 @@ class WBTable extends Component { { rows.map(r => ( { columns.map((_, idx) => ( - + (idx == 0 && verticalHeader) ? ( + + ) : ( + + ) )) } )) } From 7ffca1b48cbfe9a6f1235f94f902f43d0a300347 Mon Sep 17 00:00:00 2001 From: Stanislaw Adaszewski Date: Sun, 2 Feb 2020 17:10:05 +0100 Subject: [PATCH 023/170] Added WBCollectionListing, started implementing WBArvadosCollection. --- frontend/package.json | 1 + frontend/rollup.config.js | 1 + frontend/src/html/index.html | 1 + .../src/js/component/wb-collection-listing.js | 128 ++++++++++++++++++ frontend/src/js/misc/wb-arvados-collection.js | 50 +++++++ frontend/src/js/page/wb-app.js | 3 +- frontend/src/js/page/wb-browse.js | 23 +++- frontend/src/js/widget/wb-pagination.js | 32 +++-- 8 files changed, 221 insertions(+), 18 deletions(-) create mode 100644 frontend/src/js/component/wb-collection-listing.js create mode 100644 frontend/src/js/misc/wb-arvados-collection.js diff --git a/frontend/package.json b/frontend/package.json index 4abf4ac..199ef17 100755 --- a/frontend/package.json +++ b/frontend/package.json @@ -2,6 +2,7 @@ "dependencies": { "@fortawesome/fontawesome-free": "^5.12.0", "bootstrap": "^4.4.1", + "filesize": "^6.0.1", "jquery": "^3.4.1", "js-uuid": "0.0.6", "linkstate": "^1.1.1", diff --git a/frontend/rollup.config.js b/frontend/rollup.config.js index b250a14..e98dca8 100755 --- a/frontend/rollup.config.js +++ b/frontend/rollup.config.js @@ -42,6 +42,7 @@ export default { 'node_modules/@fortawesome/fontawesome-free/webfonts/fa-brands-400.woff': 'dist/webfonts/fa-brands-400.woff', 'node_modules/@fortawesome/fontawesome-free/webfonts/fa-brands-400.woff2': 'dist/webfonts/fa-brands-400.woff2', 'node_modules/js-uuid/js-uuid.js': 'dist/js/js-uuid.js', + 'node_modules/filesize/lib/filesize.js': 'dist/js/filesize.js', verbose: true }), buble({jsx: 'h'}), diff --git a/frontend/src/html/index.html b/frontend/src/html/index.html index 79f83d9..114c934 100755 --- a/frontend/src/html/index.html +++ b/frontend/src/html/index.html @@ -8,6 +8,7 @@ + diff --git a/frontend/src/js/component/wb-collection-listing.js b/frontend/src/js/component/wb-collection-listing.js new file mode 100644 index 0000000..993839c --- /dev/null +++ b/frontend/src/js/component/wb-collection-listing.js @@ -0,0 +1,128 @@ +import { h, Component } from 'preact'; +import { route } from 'preact-router'; +import makeArvadosRequest from 'make-arvados-request'; +import WBTable from 'wb-table'; +import WBPagination from 'wb-pagination'; +import urlForObject from 'url-for-object'; +import arvadosTypeName from 'arvados-type-name'; +import arvadosObjectName from 'arvados-object-name'; + +class WBCollectionListing extends Component { + + constructor(...args) { + super(...args); + this.state.rows = []; + this.state.numPages = 0; + } + + componentDidMount() { + this.fetchItems(); + } + + prepareRows(items, ownerLookup) { + return items.map(item => [ + (
    + +
    { item['uuid'] }
    +
    ), + item['description'], + (
    +
    + { ownerLookup[item.owner_uuid] ? ( + + { arvadosObjectName(ownerLookup[item.owner_uuid]) } + + ) : 'Not Found' } +
    +
    { item.owner_uuid }
    +
    ), + item['file_count'], + filesize(item['file_size_total']) + ]); + } + + fetchItems() { + let { arvHost, arvToken } = this.props.app.state; + + let { activePage, itemsPerPage, ownerUuid } = this.props; + + let filters = []; + + if (ownerUuid) + filters.push([ 'owner_uuid', '=', ownerUuid ]); + + let prom = makeArvadosRequest(arvHost, arvToken, + '/arvados/v1/collections?filters=' + encodeURIComponent(JSON.stringify(filters)) + + '&limit=' + encodeURIComponent(itemsPerPage) + + '&offset=' + encodeURIComponent(itemsPerPage * activePage)); + + let collections; + let numPages + + prom = prom.then(xhr => { + collections = xhr.response['items']; + numPages = Math.ceil(xhr.response['items_available'] / xhr.response['limit']); + + let owners = {}; + collections.map(c => { + let typeName = arvadosTypeName(c.owner_uuid); + if (!(typeName in owners)) + owners[typeName] = []; + owners[typeName].push(c.owner_uuid); + }); + + let lookup = {}; + let prom_1 = new Promise(accept => accept()); + for (let typeName in owners) { + let filters_1 = [ + ['uuid', 'in', owners[typeName]] + ]; + prom_1 = prom_1.then(() => makeArvadosRequest(arvHost, arvToken, + '/arvados/v1/' + typeName + 's?filters=' + + encodeURIComponent(JSON.stringify(filters_1)))); + prom_1 = prom_1.then(xhr => xhr.response.items.map(item => ( + lookup[item.uuid] = item))); + } + + prom_1 = prom_1.then(() => lookup); + + return prom_1; + }); + + //let ownerLookup = {}; + //prom = prom.then(lookup => (ownerLookup = lookup)); + + prom = prom.then(ownerLookup => + this.setState({ + 'numPages': numPages, + 'rows': this.prepareRows(collections, ownerLookup) + })); + } + + componentWillReceiveProps(nextProps, nextState) { + this.props = nextProps; + this.fetchItems(); + } + + render({ app, ownerUuid, activePage, getPageUrl }, { rows, numPages }) { + return ( +
    + + + +
    + ); + } +} + +WBCollectionListing.defaultProps = { + 'itemsPerPage': 100, + 'ownerUuid': null +}; + +export default WBCollectionListing; diff --git a/frontend/src/js/misc/wb-arvados-collection.js b/frontend/src/js/misc/wb-arvados-collection.js new file mode 100644 index 0000000..751a19a --- /dev/null +++ b/frontend/src/js/misc/wb-arvados-collection.js @@ -0,0 +1,50 @@ +import makeArvadosRequest from 'make-arvados-request'; + +class WBArvadosCollection { + constructor(arvHost, arvToken, uuid) { + this.arvHost = arvHost; + this.arvToken = arvToken; + this.uuid = uuid; + this.meta = null; + } + + fetchMeta() { + let prom = makeArvadosRequest(this.arvHost, this.arvToken, + '/arvados/v1/collections/' + this.uuid); + prom = prom.then(xhr => { + this.meta = xhr.response; + }); + return prom; + } + + parseManifest() { + if (this.meta === null) + throw Error('You must call fetchMeta() first and wait for the returned Promise.'); + + let manifest = this.meta.manifest_text; + let streams = manifest.split('\n'); + this.content = streams.map(s => { + let tokens = s.split(' '); + let streamName = tokens[0]; + let rx = /^[a-f0-9]{32}\+[0-9]+/; + let n = tokens.map(t => rx.exec(t)); + n = n.indexOf(null); + let locators = tokens.slice(1, n) + let fileTokens = tokens.slice(n); + let fileNames = fileTokens.map(t => t.split(':')[2]); + let fileSizes = {}; + fileTokens.map(t => { + let [ start, end, name ] = t.split(':'); + if (!(name in fileSizes)) + fileSizes[name] = 0; + fileSizes[name] += Number(end) - Number(start); + }); + fileSizes = fileNames.map(n => fileSizes[n]); + return [ streamName, fileNames, fileSizes ]; + }); + + return this.content; + } +} + +export WBArvadosCollection; diff --git a/frontend/src/js/page/wb-app.js b/frontend/src/js/page/wb-app.js index e4a71e7..47d3311 100644 --- a/frontend/src/js/page/wb-app.js +++ b/frontend/src/js/page/wb-app.js @@ -58,7 +58,8 @@ class WBApp extends Component { + appState={ this.appState } + app={ this } /> diff --git a/frontend/src/js/page/wb-browse.js b/frontend/src/js/page/wb-browse.js index 6e78c35..9efb555 100644 --- a/frontend/src/js/page/wb-browse.js +++ b/frontend/src/js/page/wb-browse.js @@ -6,19 +6,26 @@ import WBInlineSearch from 'wb-inline-search'; import WBProjectCrumbs from 'wb-project-crumbs'; import WBTabs from 'wb-tabs'; import WBProcessListing from 'wb-process-listing'; +import WBCollectionListing from 'wb-collection-listing'; class WBBrowse extends Component { - route(params) { - route('/browse/' + + getUrl(params) { + let res = '/browse/' + ('ownerUuid' in params ? params.ownerUuid : (this.props.ownerUuid || '')) + '/' + ('activePage' in params ? params.activePage : (this.props.activePage || '')) + '/' + ('objTypeTab' in params ? params.objTypeTab : (this.props.objTypeTab || '')) + '/' + ('collectionPage' in params ? params.collectionPage : (this.props.collectionPage || '')) + '/' + ('processPage' in params ? params.processPage : (this.props.processPage || '')) + '/' + - ('workflowPage' in params ? params.workflowPage : (this.props.workflowPage || ''))); + ('workflowPage' in params ? params.workflowPage : (this.props.workflowPage || '')); + + return res; + } + + route(params) { + route(this.getUrl(params)); } - render({ ownerUuid, activePage, appCallbacks, appState, + render({ ownerUuid, activePage, appCallbacks, appState, app, objTypeTab, collectionPage, processPage, workflowPage }) { return ( @@ -51,13 +58,19 @@ class WBBrowse extends Component { { (!objTypeTab || objTypeTab === 'collection') ? ( - null + this.getUrl({ 'collectionPage': i }) } /> + ) : (objTypeTab === 'process' ? ( this.route({ 'processPage': i }) } /> + ) : (objTypeTab === 'workflow' ? ( null ) : null)) diff --git a/frontend/src/js/widget/wb-pagination.js b/frontend/src/js/widget/wb-pagination.js index 8e45602..5d0ff89 100644 --- a/frontend/src/js/widget/wb-pagination.js +++ b/frontend/src/js/widget/wb-pagination.js @@ -1,7 +1,7 @@ import { h, Component } from 'preact'; class WBPagination extends Component { - renderVisiblePages(numPages, activePage, chunkSize, onPageChanged) { + renderVisiblePages(numPages, activePage, chunkSize, onPageChanged, getPageUrl) { let visible = {}; let begActChnk = activePage - Math.floor(chunkSize / 2); @@ -23,8 +23,8 @@ class WBPagination extends Component { res.push((
  • - { e.preventDefault(); onPageChanged(activePage - 1); } }>Previous + this.changePage(e, activePage - 1) }>Previous
  • )); @@ -33,35 +33,42 @@ class WBPagination extends Component { if (i > prev + 1) res.push((
  • - { e.preventDefault(); onPageChanged(i - 1); } }>... + this.changePage(e, i - 1) }>...
  • )); prev = i; res.push((
  • - { e.preventDefault(); onPageChanged(i); } }>{ i + 1 } + this.changePage(e, i) }>{ i + 1 }
  • )); } res.push((
  • = numPages - 1 ? "page-item disabled" : "page-item" }> - { e.preventDefault(); onPageChanged(activePage + 1); } }>Next + this.changePage(e, activePage + 1) }>Next
  • )); return res; } - render({ numPages, activePage, chunkSize, onPageChanged }) { + changePage(e, pageIdx) { + if (this.props.onPageChanged) { + e.preventDefault(); + this.props.onPageChanged(pageIdx); + } + } + + render({ numPages, activePage, chunkSize, onPageChanged, getPageUrl }) { return ( ); @@ -69,7 +76,8 @@ class WBPagination extends Component { } WBPagination.defaultProps = { - 'chunkSize': 5 + 'chunkSize': 5, + 'getPageUrl': () => ('#') }; export default WBPagination; From 7a13ab582c658dca155620f1f4f421910d7ecf1d Mon Sep 17 00:00:00 2001 From: Stanislaw Adaszewski Date: Sun, 2 Feb 2020 19:53:07 +0100 Subject: [PATCH 024/170] Added WBCollectionView, started using WBNameAndUuid in WBCommonFields. --- .../src/js/component/wb-collection-fields.js | 76 +++++++++++++++++++ frontend/src/js/component/wb-common-fields.js | 34 ++------- frontend/src/js/misc/url-for-object.js | 2 +- frontend/src/js/misc/wb-format-date.js | 6 ++ frontend/src/js/page/wb-app.js | 3 + frontend/src/js/page/wb-collection-view.js | 29 +++++++ 6 files changed, 121 insertions(+), 29 deletions(-) create mode 100644 frontend/src/js/component/wb-collection-fields.js create mode 100644 frontend/src/js/page/wb-collection-view.js diff --git a/frontend/src/js/component/wb-collection-fields.js b/frontend/src/js/component/wb-collection-fields.js new file mode 100644 index 0000000..7f2d93a --- /dev/null +++ b/frontend/src/js/component/wb-collection-fields.js @@ -0,0 +1,76 @@ +import { h, Component } from 'preact'; +import WBTable from 'wb-table'; +import makeArvadosRequest from 'make-arvados-request'; +import arvadosTypeName from 'arvados-type-name'; +import arvadosObjectName from 'arvados-object-name'; +import urlForObject from 'url-for-object'; +import wbFormatDate from 'wb-format-date'; +import WBNameAndUuid from 'wb-name-and-uuid'; +import WBAccordion from 'wb-accordion'; + +class WBCollectionFields extends Component { + componentDidMount() { + this.prepareRows(); + } + + componentWillReceiveProps(nextProps) { + this.props = nextProps; + this.prepareRows(); + } + + prepareRows() { + let { uuid, app } = this.props; + let { arvHost, arvToken } = app.state; + + let item; + let prom = makeArvadosRequest(arvHost, arvToken, + '/arvados/v1/collections/' + uuid); + prom = prom.then(xhr => (item = xhr.response)); + + prom = prom.then(() => { + let rows = [ + [ 'Name', item.name ], + [ 'Description', item.description || ({ String(item.description) }) ], + [ 'Properties', ( + +
    { JSON.stringify(item.properties, null, 2) }
    +
    + ) ], + [ 'Portable Data Hash', item.portable_data_hash ], + [ 'Replication Desired', item.replication_desired ? item.replication_desired : ( + { String(item.replication_desired) } + ) ], + [ 'Replication Confirmed', item.replication_confirmed ? item.replication_confirmed : ( + { String(item.replication_confirmed) } + ) ], + [ 'Replication Confirmed At', wbFormatDate(item.replication_confirmed_at) ], + [ 'Trash At', wbFormatDate(item.trash_at) ], + [ 'Delete At', wbFormatDate(item.delete_at) ], + [ 'Is Trashed', String(item.is_trashed) ], + [ 'Current Version UUID', ( + + ) ], + [ 'Version', item.version ], + [ 'Preserve Version', String(item.preserve_version) ], + [ 'File Count', item.file_count ], + [ 'Total Size', filesize(item.file_size_total) ] + ]; + this.setState({ 'rows': rows }); + }); + } + + render({}, { rows }) { + return ( + rows ? ( + + ) : ( +
    Loading...
    + ) + ); + } +} + +export default WBCollectionFields; diff --git a/frontend/src/js/component/wb-common-fields.js b/frontend/src/js/component/wb-common-fields.js index 10e2b70..65bf0ce 100644 --- a/frontend/src/js/component/wb-common-fields.js +++ b/frontend/src/js/component/wb-common-fields.js @@ -5,6 +5,7 @@ import arvadosTypeName from 'arvados-type-name'; import arvadosObjectName from 'arvados-object-name'; import urlForObject from 'url-for-object'; import wbFormatDate from 'wb-format-date'; +import WBNameAndUuid from 'wb-name-and-uuid'; class WBCommonFields extends Component { componentDidMount() { @@ -31,44 +32,21 @@ class WBCommonFields extends Component { prom = prom.then(xhr => (item = xhr.response)); - prom = prom.then(() => makeArvadosRequest(arvHost, arvToken, - '/arvados/v1/' + arvadosTypeName(item.owner_uuid) + - 's/' + item.owner_uuid)); - - prom = prom.then(xhr => (owner = xhr.response)); - - prom = prom.then(() => makeArvadosRequest(arvHost, arvToken, - '/arvados/v1/users/' + item.modified_by_user_uuid)); - - prom = prom.then(xhr => (modifiedByUser = xhr.response)); - prom = prom.then(() => { let rows = [ [ 'UUID', item.uuid ], [ 'Kind', item.kind ], [ 'Owner', ( -
    - -
    - { item.owner_uuid } -
    -
    + ) ], [ 'Created at', wbFormatDate(item.created_at) ], [ 'Modified at', wbFormatDate(item.modified_at) ], [ 'Modified by User', ( -
    - -
    - { item.modified_by_user_uuid } -
    -
    + + ) ], + [ 'Modified by Client', ( + ) ], - [ 'Modified by Client', item.modified_by_client_uuid ], [ 'API Url', ( { 'https://' + app.state.arvHost + '/arvados/v1' + item.href } diff --git a/frontend/src/js/misc/url-for-object.js b/frontend/src/js/misc/url-for-object.js index b2258e7..eaba75c 100644 --- a/frontend/src/js/misc/url-for-object.js +++ b/frontend/src/js/misc/url-for-object.js @@ -11,7 +11,7 @@ function urlForObject(item) { else if (objectType === 'workflow') return ('https://wb.arkau.roche.com/workflows/' + item.uuid); else if (objectType === 'collection') - return ('https://wb.arkau.roche.com/collections/' + item.uuid); + return ('/collection/' + item.uuid); else if (objectType === 'container') return ('https://wb.arkau.roche.com/containers/' + item.uuid); } diff --git a/frontend/src/js/misc/wb-format-date.js b/frontend/src/js/misc/wb-format-date.js index 5ef86a9..467b571 100644 --- a/frontend/src/js/misc/wb-format-date.js +++ b/frontend/src/js/misc/wb-format-date.js @@ -1,4 +1,10 @@ +import { h } from 'preact'; + function wbFormatDate(dateStr) { + if (!dateStr) + return ( + { String(dateStr) } + ); let date = new Date(dateStr); return date.toLocaleString(); } diff --git a/frontend/src/js/page/wb-app.js b/frontend/src/js/page/wb-app.js index 47d3311..75fda85 100644 --- a/frontend/src/js/page/wb-app.js +++ b/frontend/src/js/page/wb-app.js @@ -4,6 +4,7 @@ import WBBrowse from 'wb-browse'; import WBSignIn from 'wb-sign-in'; import WBLandingPage from 'wb-landing-page'; import WBProcessView from 'wb-process-view'; +import WBCollectionView from 'wb-collection-view'; import arvadosTypeName from 'arvados-type-name'; class WBApp extends Component { @@ -62,6 +63,8 @@ class WBApp extends Component { app={ this } /> + + ); } diff --git a/frontend/src/js/page/wb-collection-view.js b/frontend/src/js/page/wb-collection-view.js new file mode 100644 index 0000000..deedd14 --- /dev/null +++ b/frontend/src/js/page/wb-collection-view.js @@ -0,0 +1,29 @@ +import { h, Component } from 'preact'; +import WBNavbarCommon from 'wb-navbar-common'; +import WBArvadosCrumbs from 'wb-arvados-crumbs'; +import WBCommonFields from 'wb-common-fields'; +import WBCollectionFields from 'wb-collection-fields'; + +class WBCollectionView extends Component { + render({ app, uuid }, {}) { + return ( +
    + + + + +
    + This is the collection view for { uuid } +
    + +

    Common Fields

    + + +

    Collection Fields

    + +
    + ); + } +} + +export default WBCollectionView; From edf6882767e28b6c66709454faa412f7d48d876a Mon Sep 17 00:00:00 2001 From: Stanislaw Adaszewski Date: Sun, 2 Feb 2020 20:02:07 +0100 Subject: [PATCH 025/170] Small fix --- frontend/src/js/component/wb-container-request-fields.js | 1 + 1 file changed, 1 insertion(+) diff --git a/frontend/src/js/component/wb-container-request-fields.js b/frontend/src/js/component/wb-container-request-fields.js index 51f4826..a7d1b05 100644 --- a/frontend/src/js/component/wb-container-request-fields.js +++ b/frontend/src/js/component/wb-container-request-fields.js @@ -104,6 +104,7 @@ class WBContainerRequestFields extends Component { ) ) ] ]; + rows = rows.map(r => [r[0], r[1] ? r[1] : ({ String(r[1]) })]); this.setState({ 'rows': rows }); }); } From 1aa0e39e73f1ce08f72011ef05e5c330b390fa9e Mon Sep 17 00:00:00 2001 From: Stanislaw Adaszewski Date: Mon, 3 Feb 2020 07:24:36 +0100 Subject: [PATCH 026/170] Started implementing WBCollectionBrowse. --- .../src/js/component/wb-collection-content.js | 45 +++++++ frontend/src/js/misc/wb-arvados-collection.js | 2 +- .../src/js/misc/wb-collection-manifest.js | 114 ++++++++++++++++++ frontend/src/js/page/wb-app.js | 3 + frontend/src/js/page/wb-collection-browse.js | 24 ++++ 5 files changed, 187 insertions(+), 1 deletion(-) create mode 100644 frontend/src/js/component/wb-collection-content.js create mode 100644 frontend/src/js/misc/wb-collection-manifest.js create mode 100644 frontend/src/js/page/wb-collection-browse.js diff --git a/frontend/src/js/component/wb-collection-content.js b/frontend/src/js/component/wb-collection-content.js new file mode 100644 index 0000000..31f5816 --- /dev/null +++ b/frontend/src/js/component/wb-collection-content.js @@ -0,0 +1,45 @@ +import { h, Component } from 'preact'; +import WBTable from 'wb-table'; +import WBBreadcrumbs from 'wb-breadcrumbs'; +import { WBManifestReader } from 'wb-collection-manifest'; +import makeArvadosRequest from 'make-arvados-request'; + +class WBCollectionContent extends Component { + constructor(...args) { + super(...args); + this.state.path = '.'; + this.state.rows = []; + this.state.manifestReader = null; + } + + componentDidMount() { + let { arvHost, arvToken } = this.props.app.state; + let { uuid } = this.props; + + let select = [ 'manifest_text' ]; + let prom = makeArvadosRequest(arvHost, arvToken, + '/arvados/v1/collections/' + uuid + + '?select=' + encodeURIComponent(JSON.stringify(select))); + prom = prom.then(xhr => { + this.state.manifestReader = new WBManifestReader(xhr.response.manifest_text); + this.prepareRows(); + }); + } + + prepareRows() { + this.setState({}); + } + + render({}, { rows }) { + return ( +
    + + + +
    + ); + } +} + +export default WBCollectionContent; diff --git a/frontend/src/js/misc/wb-arvados-collection.js b/frontend/src/js/misc/wb-arvados-collection.js index 751a19a..430376c 100644 --- a/frontend/src/js/misc/wb-arvados-collection.js +++ b/frontend/src/js/misc/wb-arvados-collection.js @@ -42,7 +42,7 @@ class WBArvadosCollection { fileSizes = fileNames.map(n => fileSizes[n]); return [ streamName, fileNames, fileSizes ]; }); - + return this.content; } } diff --git a/frontend/src/js/misc/wb-collection-manifest.js b/frontend/src/js/misc/wb-collection-manifest.js new file mode 100644 index 0000000..704cc9d --- /dev/null +++ b/frontend/src/js/misc/wb-collection-manifest.js @@ -0,0 +1,114 @@ +class CMDirectory { + constructor() { + this.directories = {}; + this.files = {}; + } +} + +class CMFile { + constructor() { + this.blockRefs = []; + this.size = 0; + } + + append(locators, position, size) { + if (size === 0) + return; + + let cum = 0; + let locHashes = locators.map(loc => loc[0]); + let locSizes = locators.map(loc => loc[1]); + let locPositions = locators.map(loc => { + let res = cum; + cum += loc[1]; + return res; + }); + + let used = locators.map((_, i) => (locPositions[i] + locSizes[i] > position && + locPositions[i] < position + size)); + + let startBlock = used.indexOf(true); + let endBlock = used.lastIndexOf(true) + 1; + + console.log('startBlock: ' + startBlock + ', endBlock: ' + endBlock); + + if (startBlock === -1) + return; + + let blockRefs = []; + let runPos = position; + let runSize = size; + + for (let i = startBlock; i < endBlock; i++) { + let blockPos = runPos - locPositions[i]; + let blockSize = Math.min(runSize, locSizes[i] - blockPos); + blockRefs.push([ locHashes[i], blockPos, blockSize ]); + runPos += blockSize; + runSize -= blockSize; + } + + this.blockRefs = this.blockRefs.concat(blockRefs); + this.size += size; + } +} + +class WBManifestReader { + constructor(manifest_text) { + this.rootDir = new CMDirectory(); + + if (!manifest_text) + return; + + this.parse(manifest_text); + } + + makeDir(parent, name) { + if (!(name in parent.directories)) + parent.directories[name] = new CMDirectory(); + return parent.directories[name]; + } + + makePath(path) { + if (typeof(path) === 'string') + path = path.split('/'); + let dir = this.rootDir; + for (let i = 1; i < path.length; i++) + dir = this.makeDir(dir, path[i]); + return dir; + } + + appendFile(streamName, locators, position, size, fileName) { + let path = streamName + '/' + fileName; + path = path.split('/'); + let dir = this.makePath(path.slice(0, path.length - 1)); + if (!(fileName in dir.files)) + dir.files[fileName] = new CMFile(); + dir.files[fileName].append(locators, position, size); + } + + parse(manifest_text) { + let rx = /^[a-f0-9]{32}\+[0-9]+/; + + let streams = manifest_text.split('\n'); + + streams.map(s => { + let tokens = s.split(' '); + let streamName = tokens[0]; + + let n = tokens.map(t => rx.exec(t)); + n = n.indexOf(null, 1); + + let locators = tokens.slice(1, n); + locators = locators.map(loc => [ loc, Number(loc.split('+')[1]) ]); + + let fileTokens = tokens.slice(n); + fileTokens.map(t => { + let [ position, size, fileName ] = t.split(':'); + this.appendFile(streamName, locators, + Number(position), Number(size), fileName); + }); + }); + } +} + +export { WBManifestReader }; diff --git a/frontend/src/js/page/wb-app.js b/frontend/src/js/page/wb-app.js index 75fda85..ea554fb 100644 --- a/frontend/src/js/page/wb-app.js +++ b/frontend/src/js/page/wb-app.js @@ -5,6 +5,7 @@ import WBSignIn from 'wb-sign-in'; import WBLandingPage from 'wb-landing-page'; import WBProcessView from 'wb-process-view'; import WBCollectionView from 'wb-collection-view'; +import WBCollectionBrowse from 'wb-collection-browse'; import arvadosTypeName from 'arvados-type-name'; class WBApp extends Component { @@ -65,6 +66,8 @@ class WBApp extends Component { + + ); } diff --git a/frontend/src/js/page/wb-collection-browse.js b/frontend/src/js/page/wb-collection-browse.js new file mode 100644 index 0000000..e238702 --- /dev/null +++ b/frontend/src/js/page/wb-collection-browse.js @@ -0,0 +1,24 @@ +import { h, Component } from 'preact'; +import WBNavbarCommon from 'wb-navbar-common'; +import WBArvadosCrumbs from 'wb-arvados-crumbs'; +import WBCollectionContent from 'wb-collection-content'; + +class WBCollectionBrowse extends Component { + render({ app, uuid }, {}) { + return ( +
    + + + + +
    + This is the collection browser for { uuid } +
    + + +
    + ); + } +} + +export default WBCollectionBrowse; From a5cd564375891f947752f97fc8c3a49658b3cda2 Mon Sep 17 00:00:00 2001 From: Stanislaw Adaszewski Date: Mon, 3 Feb 2020 18:18:57 +0100 Subject: [PATCH 027/170] WBCollectionBrowse starting to work... --- .../src/js/component/wb-collection-content.js | 49 +++++- .../src/js/misc/wb-collection-manifest.js | 150 ++++++++++++------ frontend/src/js/page/wb-app.js | 2 +- frontend/src/js/page/wb-collection-browse.js | 5 +- 4 files changed, 149 insertions(+), 57 deletions(-) diff --git a/frontend/src/js/component/wb-collection-content.js b/frontend/src/js/component/wb-collection-content.js index 31f5816..49c876a 100644 --- a/frontend/src/js/component/wb-collection-content.js +++ b/frontend/src/js/component/wb-collection-content.js @@ -7,11 +7,18 @@ import makeArvadosRequest from 'make-arvados-request'; class WBCollectionContent extends Component { constructor(...args) { super(...args); - this.state.path = '.'; this.state.rows = []; this.state.manifestReader = null; } + getUrl(params) { + let res = '/collection-browse/' + + (params.uuid || this.props.uuid) + '/' + + encodeURIComponent(params.collectionPath || this.props.collectionPath) + '/' + + (params.page || this.props.page); + return res; + } + componentDidMount() { let { arvHost, arvToken } = this.props.app.state; let { uuid } = this.props; @@ -26,20 +33,52 @@ class WBCollectionContent extends Component { }); } + componentWillReceiveProps(nextProps) { + this.props = nextProps; + this.prepareRows(); + } + prepareRows() { - this.setState({}); + let { manifestReader } = this.state; + let { collectionPath } = this.props; + + //path = path.split('/'); + //path = [ '.' ].concat(path); + + let listing = manifestReader.listDirectory('.' + collectionPath); + + this.setState({ + 'rows': listing.map(item => ( + (item[0] === 'd') ? [ + (
    { item[1] }/), + 'Directory', + null, + (
    ) + ] : [ + ({ item[1] }), + 'File', + filesize(item[2]), + (
    ) + ] + )) + }); } - render({}, { rows }) { + render({ collectionPath }, { rows }) { return (
    - + -
    ); } } +WBCollectionContent.defaultProps = { + 'collectionPath': '', + 'page': 0 +}; + export default WBCollectionContent; diff --git a/frontend/src/js/misc/wb-collection-manifest.js b/frontend/src/js/misc/wb-collection-manifest.js index 704cc9d..904984f 100644 --- a/frontend/src/js/misc/wb-collection-manifest.js +++ b/frontend/src/js/misc/wb-collection-manifest.js @@ -1,17 +1,52 @@ -class CMDirectory { - constructor() { - this.directories = {}; - this.files = {}; +// +// Directory: Hash[string, [Directory, File]] +// File = [blockRefs, size] +// blockRefs: Array[blockRef] +// blockRef: [locator, position, size] +// locator: String +// position: Number +// size: Number +// + +class WBManifestReader { + constructor(manifest_text) { + this.rootDir = {}; + + if (!manifest_text) + return; + + this.parse(manifest_text); + } + + makeDir(parent, name) { + if (!(name in parent)) + parent[name] = {}; + if (parent[name] instanceof Array) + throw Error('Conflict trying to create a directory - a file with the same name already exists: ' + name); + return parent[name]; + } + + makePath(path) { + if (typeof(path) === 'string') + path = path.split('/'); + let dir = this.rootDir; + for (let i = 1; i < path.length; i++) + dir = this.makeDir(dir, path[i]); + return dir; } -} -class CMFile { - constructor() { - this.blockRefs = []; - this.size = 0; + appendFile(streamName, locators, position, size, fileName) { + let path = streamName + '/' + fileName; + path = path.split('/'); + let dir = this.makePath(path.slice(0, path.length - 1)); + if (!(fileName in dir)) + dir[fileName] = [[], 0]; + if (!(dir[fileName] instanceof Array)) + throw Error('Conflict trying to create a file - a directory with the same name already exists: ' + fileName); + this.appendReferences(dir[fileName], locators, position, size); } - append(locators, position, size) { + appendReferences(file, locators, position, size) { if (size === 0) return; @@ -30,7 +65,7 @@ class CMFile { let startBlock = used.indexOf(true); let endBlock = used.lastIndexOf(true) + 1; - console.log('startBlock: ' + startBlock + ', endBlock: ' + endBlock); + // console.log('startBlock: ' + startBlock + ', endBlock: ' + endBlock); if (startBlock === -1) return; @@ -47,53 +82,20 @@ class CMFile { runSize -= blockSize; } - this.blockRefs = this.blockRefs.concat(blockRefs); - this.size += size; - } -} - -class WBManifestReader { - constructor(manifest_text) { - this.rootDir = new CMDirectory(); - - if (!manifest_text) - return; - - this.parse(manifest_text); - } - - makeDir(parent, name) { - if (!(name in parent.directories)) - parent.directories[name] = new CMDirectory(); - return parent.directories[name]; - } - - makePath(path) { - if (typeof(path) === 'string') - path = path.split('/'); - let dir = this.rootDir; - for (let i = 1; i < path.length; i++) - dir = this.makeDir(dir, path[i]); - return dir; - } - - appendFile(streamName, locators, position, size, fileName) { - let path = streamName + '/' + fileName; - path = path.split('/'); - let dir = this.makePath(path.slice(0, path.length - 1)); - if (!(fileName in dir.files)) - dir.files[fileName] = new CMFile(); - dir.files[fileName].append(locators, position, size); + file[0] = file[0].concat(blockRefs); + file[1] += size; } parse(manifest_text) { let rx = /^[a-f0-9]{32}\+[0-9]+/; let streams = manifest_text.split('\n'); + if (!streams[streams.length - 1]) + streams = streams.slice(0, streams.length - 1); streams.map(s => { let tokens = s.split(' '); - let streamName = tokens[0]; + let streamName = this.unescapeName(tokens[0]); let n = tokens.map(t => rx.exec(t)); n = n.indexOf(null, 1); @@ -104,11 +106,61 @@ class WBManifestReader { let fileTokens = tokens.slice(n); fileTokens.map(t => { let [ position, size, fileName ] = t.split(':'); + fileName = this.unescapeName(fileName); this.appendFile(streamName, locators, Number(position), Number(size), fileName); }); }); } + + findDir(path) { + if (typeof(path) === 'string') + path = path.split('/'); + if (path[0] !== '.') + throw Error('Path must begin with a dot component'); + let dir = this.rootDir; + for (let i = 1; i < path.length; i++) { + if (!(path[i] in dir)) + throw Error('Directory not found'); + if (dir[path[i]] instanceof Array) + throw Error('Path is a file not directory'); + dir = dir[path[i]]; + } + return dir; + } + + listDirectory(path) { + let dir = this.findDir(path); + let keys = Object.keys(dir); + keys.sort(); + let subdirs = keys.filter(k => !(dir[k] instanceof Array)); + let files = keys.filter(k => (dir[k] instanceof Array)); + let res = subdirs.map(k => [ 'd', k, null ]); + res = res.concat(files.map(k => [ 'f', k, dir[k][1] ])); + return res; + } + + unescapeName(name) { + return name.replace(/(\\\\|\\040)/g, (_, $1) => ($1 === '\\\\' ? '\\' : ' ')); + } + + escapeName(name) { + return name.replace(/ /g, '\\040'); + } + /* let ids = { '\\': 1, '0': 2, '4': 3 }; + let transitions = [ + [ [0, 0], [1, ''], [0, 0], [0, 0] ], + [ [0, 0], [0, '\\'], [2, ''], [0, 0] ], + ]; + let mode = 0; + for (let i = 0; i < name.length; i++) { + let b = name[i]; + let tokenId = Number(ids[b]); + [ mode, out ] = transitions[mode][tokenId]; + if (out === 0) + out = b; + } + }*/ } export { WBManifestReader }; diff --git a/frontend/src/js/page/wb-app.js b/frontend/src/js/page/wb-app.js index ea554fb..4718dca 100644 --- a/frontend/src/js/page/wb-app.js +++ b/frontend/src/js/page/wb-app.js @@ -67,7 +67,7 @@ class WBApp extends Component { - + ); } diff --git a/frontend/src/js/page/wb-collection-browse.js b/frontend/src/js/page/wb-collection-browse.js index e238702..d92cd86 100644 --- a/frontend/src/js/page/wb-collection-browse.js +++ b/frontend/src/js/page/wb-collection-browse.js @@ -4,7 +4,7 @@ import WBArvadosCrumbs from 'wb-arvados-crumbs'; import WBCollectionContent from 'wb-collection-content'; class WBCollectionBrowse extends Component { - render({ app, uuid }, {}) { + render({ app, uuid, collectionPath }, {}) { return (
    @@ -15,7 +15,8 @@ class WBCollectionBrowse extends Component { This is the collection browser for { uuid }
    - + ); } From 694d8d5a52a2e68c5418231657552dd155cec7bb Mon Sep 17 00:00:00 2001 From: Stanislaw Adaszewski Date: Tue, 4 Feb 2020 12:39:59 +0100 Subject: [PATCH 028/170] Implemented file download but held back by CORS issues. --- frontend/package.json | 1 + frontend/rollup.config.js | 2 + frontend/src/html/index.html | 2 + .../src/js/component/wb-collection-content.js | 11 +++- frontend/src/js/misc/make-arvados-request.js | 22 +++++-- .../src/js/misc/wb-collection-manifest.js | 13 ++++ frontend/src/js/misc/wb-download-file.js | 65 +++++++++++++++++++ 7 files changed, 110 insertions(+), 6 deletions(-) create mode 100644 frontend/src/js/misc/wb-download-file.js diff --git a/frontend/package.json b/frontend/package.json index 199ef17..3707664 100755 --- a/frontend/package.json +++ b/frontend/package.json @@ -2,6 +2,7 @@ "dependencies": { "@fortawesome/fontawesome-free": "^5.12.0", "bootstrap": "^4.4.1", + "crypto-js": "^3.1.9-1", "filesize": "^6.0.1", "jquery": "^3.4.1", "js-uuid": "0.0.6", diff --git a/frontend/rollup.config.js b/frontend/rollup.config.js index e98dca8..7032717 100755 --- a/frontend/rollup.config.js +++ b/frontend/rollup.config.js @@ -43,6 +43,8 @@ export default { 'node_modules/@fortawesome/fontawesome-free/webfonts/fa-brands-400.woff2': 'dist/webfonts/fa-brands-400.woff2', 'node_modules/js-uuid/js-uuid.js': 'dist/js/js-uuid.js', 'node_modules/filesize/lib/filesize.js': 'dist/js/filesize.js', + 'node_modules/crypto-js/core.js': 'dist/js/crypto-js/core.js', + 'node_modules/crypto-js/md5.js': 'dist/js/crypto-js/md5.js', verbose: true }), buble({jsx: 'h'}), diff --git a/frontend/src/html/index.html b/frontend/src/html/index.html index 114c934..e22169d 100755 --- a/frontend/src/html/index.html +++ b/frontend/src/html/index.html @@ -9,6 +9,8 @@ + + diff --git a/frontend/src/js/component/wb-collection-content.js b/frontend/src/js/component/wb-collection-content.js index 49c876a..0da5890 100644 --- a/frontend/src/js/component/wb-collection-content.js +++ b/frontend/src/js/component/wb-collection-content.js @@ -3,6 +3,7 @@ import WBTable from 'wb-table'; import WBBreadcrumbs from 'wb-breadcrumbs'; import { WBManifestReader } from 'wb-collection-manifest'; import makeArvadosRequest from 'make-arvados-request'; +import wbDownloadFile from 'wb-download-file'; class WBCollectionContent extends Component { constructor(...args) { @@ -41,6 +42,7 @@ class WBCollectionContent extends Component { prepareRows() { let { manifestReader } = this.state; let { collectionPath } = this.props; + let { arvHost, arvToken } = this.props.app.state; //path = path.split('/'); //path = [ '.' ].concat(path); @@ -58,7 +60,14 @@ class WBCollectionContent extends Component { ({ item[1] }), 'File', filesize(item[2]), - (
    ) + (
    + + +
    ) ] )) }); diff --git a/frontend/src/js/misc/make-arvados-request.js b/frontend/src/js/misc/make-arvados-request.js index b9b1e74..7783872 100644 --- a/frontend/src/js/misc/make-arvados-request.js +++ b/frontend/src/js/misc/make-arvados-request.js @@ -1,13 +1,25 @@ -function makeArvadosRequest(arvHost, arvToken, endpoint, method='GET', data=null, - contentType='application/json;charset=utf-8', responseType='json') { +function makeArvadosRequest(arvHost, arvToken, endpoint, params={}) { + const defaultParams = { + 'method': 'GET', + 'data': null, + 'contentType': 'application/json;charset=utf-8', + 'responseType': 'json', + 'useSsl': true, + 'requireToken': true + }; - if (!arvHost || !arvToken) + Object.keys(defaultParams).map(k => (params[k] = + (k in params ? params[k] : defaultParams[k]))); + let { method, data, contentType, responseType, useSsl, requireToken } = params; + + if (!(arvHost && (arvToken || !requireToken))) return new Promise((accept, reject) => reject()); let xhr = new XMLHttpRequest(); - xhr.open(method, 'https://' + arvHost + endpoint); - xhr.setRequestHeader('Authorization', 'OAuth2 ' + arvToken); + xhr.open(method, (useSsl ? 'https://' : 'http://') + arvHost + endpoint); + if (arvToken) + xhr.setRequestHeader('Authorization', 'OAuth2 ' + arvToken); if (data !== null) xhr.setRequestHeader('Content-Type', contentType); xhr.responseType = responseType; diff --git a/frontend/src/js/misc/wb-collection-manifest.js b/frontend/src/js/misc/wb-collection-manifest.js index 904984f..68fb05e 100644 --- a/frontend/src/js/misc/wb-collection-manifest.js +++ b/frontend/src/js/misc/wb-collection-manifest.js @@ -161,6 +161,19 @@ class WBManifestReader { out = b; } }*/ + getFile(path) { + if (typeof(path) === 'string') + path = path.split('/'); + if (path.length < 2) + throw Error('Invalid file path'); + let name = path[path.length - 1]; + let dir = this.findDir(path.slice(0, path.length - 1)); + if (!(name in dir)) + throw Error('File not found'); + if (!(dir[name] instanceof Array)) + throw Error('Path points to a directory not a file'); + return dir[name]; + } } export { WBManifestReader }; diff --git a/frontend/src/js/misc/wb-download-file.js b/frontend/src/js/misc/wb-download-file.js new file mode 100644 index 0000000..c64cfca --- /dev/null +++ b/frontend/src/js/misc/wb-download-file.js @@ -0,0 +1,65 @@ +import makeArvadosRequest from 'make-arvados-request'; + +function rdvHash(serviceId, locator) { + let blockHash = /^[0-9a-f]{32}/.exec(locator); + if (!blockHash) + throw Error('Invalid locator'); + if (typeof(serviceId) !== 'string') + throw Error('Invalid service ID'); + let res = CryptoJS.MD5(serviceId + blockHash).toString(); + return res; +} + +function wbDownloadFile(arvHost, arvToken, + manifestReader, path) { + + const file = manifestReader.getFile(path); + const name = path.split('/').reverse()[0]; + const blockRefs = file[0]; + let services; + + let prom = makeArvadosRequest(arvHost, arvToken, + '/arvados/v1/keep_services/accessible'); + prom = prom.then(xhr => (services = xhr.response['items'])); + + const blocks = []; + for (let i = 0; i < blockRefs.length; i++) { + prom = prom.then(() => { + const [ locator, position, size ] = blockRefs[i]; + const weights = services.map(s => rdvHash(s['uuid'], locator)); + const order = Object.keys(services).sort((a, b) => weights[b].localeCompare(weights[a])); + const orderedServices = order.map(i => services[i]); + + let k = 0; + const cb = () => { + if (k >= orderedServices.length) + throw Error('Block not found'); + + const svc = orderedServices[k]; + k++; + + let prom_1 = makeArvadosRequest(svc.service_host + + ':' + svc.service_port, arvToken, + '/' + locator, { 'useSsl': svc.service_ssl_flag, + 'responseType': 'arraybuffer' }); + //prom_1 = prom_1.then(xhr => xhr.response); + prom_1 = prom_1.catch(cb); + + return prom_1; + }; + + return cb().then(xhr => (blocks.append(xhr.response.slice(position, size)))); + }); + } + + prom = prom.then(() => { + const blob = new Blob(blocks); + const url = window.URL.createObjectURL(blob); + const a = document.createElement('a'); + a.href = url; + a.download = name; + + }); +} + +export default wbDownloadFile; From d2081a08a3cf6c3be2522d885319a8dd56c52e0f Mon Sep 17 00:00:00 2001 From: Stanislaw Adaszewski Date: Tue, 4 Feb 2020 13:47:38 +0100 Subject: [PATCH 029/170] Added user listing view. --- frontend/src/js/component/wb-navbar-common.js | 3 +- frontend/src/js/component/wb-user-listing.js | 63 +++++++++++++++++++ frontend/src/js/page/wb-app.js | 6 ++ frontend/src/js/page/wb-users-page.js | 22 +++++++ 4 files changed, 93 insertions(+), 1 deletion(-) create mode 100644 frontend/src/js/component/wb-user-listing.js create mode 100644 frontend/src/js/page/wb-users-page.js diff --git a/frontend/src/js/component/wb-navbar-common.js b/frontend/src/js/component/wb-navbar-common.js index f07610d..c4b4cf3 100644 --- a/frontend/src/js/component/wb-navbar-common.js +++ b/frontend/src/js/component/wb-navbar-common.js @@ -8,7 +8,8 @@ class WBNavbarCommon extends Component { ) } onItemClicked={ item => app.navbarItemClicked(item) } diff --git a/frontend/src/js/component/wb-user-listing.js b/frontend/src/js/component/wb-user-listing.js new file mode 100644 index 0000000..88e7970 --- /dev/null +++ b/frontend/src/js/component/wb-user-listing.js @@ -0,0 +1,63 @@ +import { h, Component } from 'preact'; +import WBPagination from 'wb-pagination'; +import makeArvadosRequest from 'make-arvados-request'; +import urlForObject from 'url-for-object'; + +class WBUserListing extends Component { + componentDidMount() { + this.preparePage(); + } + + componentWillReceiveProps(nextProps) { + this.props = nextProps; + this.preparePage(); + } + + preparePage() { + const { arvHost, arvToken } = this.props.app.state; + const { itemsPerPage, page } = this.props; + + const order = ['last_name asc']; + + let prom = makeArvadosRequest(arvHost, arvToken, + '/arvados/v1/users?order=' + encodeURIComponent(JSON.stringify(order)) + + '&limit=' + itemsPerPage + '&offset=' + (itemsPerPage * page)); + prom = prom.then(xhr => { + this.setState({ + 'items': xhr.response['items'], + 'numPages': Math.ceil(xhr.response['items_available'] / itemsPerPage) + }); + }); + } + + render({ app, page, getPageUrl }, { items, numPages }) { + return ( +
    +

    Users

    +
    + { items ? items.map(it => ( + + )) : 'Loading...' } +
    + + +
    + ); + } +} + +WBUserListing.defaultProps = { + 'itemsPerPage': 20, + 'page': 0 +}; + +export default WBUserListing; diff --git a/frontend/src/js/page/wb-app.js b/frontend/src/js/page/wb-app.js index 4718dca..f229443 100644 --- a/frontend/src/js/page/wb-app.js +++ b/frontend/src/js/page/wb-app.js @@ -6,6 +6,7 @@ import WBLandingPage from 'wb-landing-page'; import WBProcessView from 'wb-process-view'; import WBCollectionView from 'wb-collection-view'; import WBCollectionBrowse from 'wb-collection-browse'; +import WBUsersPage from 'wb-users-page'; import arvadosTypeName from 'arvados-type-name'; class WBApp extends Component { @@ -38,6 +39,9 @@ class WBApp extends Component { } else if (item['id'] === 'all-projects') { route('/browse'); + } else if (item['id'] === 'all-users') { + route('/users'); + } } @@ -68,6 +72,8 @@ class WBApp extends Component { + + ); } diff --git a/frontend/src/js/page/wb-users-page.js b/frontend/src/js/page/wb-users-page.js new file mode 100644 index 0000000..5e79335 --- /dev/null +++ b/frontend/src/js/page/wb-users-page.js @@ -0,0 +1,22 @@ +import { h, Component } from 'preact'; +import WBNavbarCommon from 'wb-navbar-common'; +import WBUserListing from 'wb-user-listing'; + +class WBUsersPage extends Component { + getUrl(page) { + return ('/users/' + page); + } + + render({ app, page }) { + return ( +
    + + + this.getUrl(page) } /> +
    + ); + } +}; + +export default WBUsersPage; From d104d5702745d1f709712eb62ec6e2f427a1f4a0 Mon Sep 17 00:00:00 2001 From: Stanislaw Adaszewski Date: Tue, 4 Feb 2020 15:07:48 +0100 Subject: [PATCH 030/170] Adapted WBNavbar to handle URLs in addition to onclick callbacks. --- frontend/src/js/component/wb-navbar-common.js | 22 ++++++----- frontend/src/js/page/wb-app.js | 14 +++---- frontend/src/js/widget/wb-navbar.js | 37 +++++++++++++++---- 3 files changed, 47 insertions(+), 26 deletions(-) diff --git a/frontend/src/js/component/wb-navbar-common.js b/frontend/src/js/component/wb-navbar-common.js index c4b4cf3..3b6a09b 100644 --- a/frontend/src/js/component/wb-navbar-common.js +++ b/frontend/src/js/component/wb-navbar-common.js @@ -5,15 +5,19 @@ import WBInlineSearch from 'wb-inline-search'; class WBNavbarCommon extends Component { render({ app, items, activeItem }) { return ( - - ) } onItemClicked={ item => app.navbarItemClicked(item) } - activeItem={ activeItem } /> + + ) } + titleUrl = { '/browse/' + app.state.currentUser.uuid } + getItemUrl={ item => app.navbarItemUrl(item) } + activeItem={ activeItem } /> ); } } diff --git a/frontend/src/js/page/wb-app.js b/frontend/src/js/page/wb-app.js index f229443..a24eb7f 100644 --- a/frontend/src/js/page/wb-app.js +++ b/frontend/src/js/page/wb-app.js @@ -26,22 +26,18 @@ class WBApp extends Component { }; } - navbarItemClicked(item) { + navbarItemUrl(item) { if (item['id'] === 'sign-out') { delete window.localStorage['arvHost']; delete window.localStorage['arvToken']; delete window.localStorage['currentUser']; - route('/sign-in'); - + return ('/sign-in'); } else if (item['id'] === 'home') { - route('/browse/' + this.appState.currentUser.uuid); - + return ('/browse/' + this.appState.currentUser.uuid); } else if (item['id'] === 'all-projects') { - route('/browse'); - + return ('/browse'); } else if (item['id'] === 'all-users') { - route('/users'); - + return ('/users'); } } diff --git a/frontend/src/js/widget/wb-navbar.js b/frontend/src/js/widget/wb-navbar.js index b44686f..cbdef5c 100644 --- a/frontend/src/js/widget/wb-navbar.js +++ b/frontend/src/js/widget/wb-navbar.js @@ -1,10 +1,12 @@ import { h, Component } from 'preact'; class WBNavbar extends Component { - render({ title, items, rhs, onItemClicked, onTitleClicked, activeItem }) { + render({ title, items, rhs, onItemClicked, onTitleClicked, + activeItem, titleUrl, getItemUrl }) { + return ( ); } + + titleClicked(e) { + let { onTitleClicked } = this.props; + if (!onTitleClicked) + return; + e.preventDefault(); + onTitleClicked(); + } + + itemClicked(e, i) { + let { onItemClicked } = this.props; + if (!onItemClicked) + return; + e.preventDefault(); + onItemClicked(i); + } } WBNavbar.defaultProps = { 'title': 'Workbench Advanced', 'items': [], 'form': null, - 'onItemClicked': () => {}, - 'onTitleClicked': () => {}, - 'activeItem': null + 'activeItem': null, + 'titleUrl': '#', + 'getItemUrl': () => ('#') } export default WBNavbar; From 1f9d923958db129cae29fb6ee14ccb7b28212115 Mon Sep 17 00:00:00 2001 From: Stanislaw Adaszewski Date: Tue, 4 Feb 2020 15:31:57 +0100 Subject: [PATCH 031/170] Transitioning to WBNavbarCommon and fixing regressions on the way. --- frontend/src/js/page/wb-app.js | 23 +++++++---------------- frontend/src/js/page/wb-browse.js | 13 ++++--------- frontend/src/js/page/wb-sign-out.js | 19 +++++++++++++++++++ frontend/src/js/page/wb-users-page.js | 2 +- 4 files changed, 31 insertions(+), 26 deletions(-) create mode 100644 frontend/src/js/page/wb-sign-out.js diff --git a/frontend/src/js/page/wb-app.js b/frontend/src/js/page/wb-app.js index a24eb7f..91e5c5c 100644 --- a/frontend/src/js/page/wb-app.js +++ b/frontend/src/js/page/wb-app.js @@ -2,6 +2,7 @@ import { h, Component } from 'preact'; import { Router, route } from 'preact-router'; import WBBrowse from 'wb-browse'; import WBSignIn from 'wb-sign-in'; +import WBSignOut from 'wb-sign-out'; import WBLandingPage from 'wb-landing-page'; import WBProcessView from 'wb-process-view'; import WBCollectionView from 'wb-collection-view'; @@ -16,24 +17,13 @@ class WBApp extends Component { this.state.arvToken = window.localStorage['arvToken']; if ('currentUser' in window.localStorage) this.state.currentUser = JSON.parse(window.localStorage['currentUser']); - this.appCallbacks = { - 'navbarItemClicked': item => this.navbarItemClicked(item) - }; - this.appState = { - 'arvHost': this.state.arvHost, - 'arvToken': this.state.arvToken, - 'currentUser': this.state.currentUser - }; } navbarItemUrl(item) { if (item['id'] === 'sign-out') { - delete window.localStorage['arvHost']; - delete window.localStorage['arvToken']; - delete window.localStorage['currentUser']; - return ('/sign-in'); + return ('/sign-out'); } else if (item['id'] === 'home') { - return ('/browse/' + this.appState.currentUser.uuid); + return ('/browse/' + this.state.currentUser.uuid); } else if (item['id'] === 'all-projects') { return ('/browse'); } else if (item['id'] === 'all-users') { @@ -56,11 +46,12 @@ class WBApp extends Component { - + + + diff --git a/frontend/src/js/page/wb-browse.js b/frontend/src/js/page/wb-browse.js index 9efb555..c67af04 100644 --- a/frontend/src/js/page/wb-browse.js +++ b/frontend/src/js/page/wb-browse.js @@ -1,6 +1,6 @@ import { h, Component } from 'preact'; import { route } from 'preact-router'; -import WBNavbar from 'wb-navbar'; +import WBNavbarCommon from 'wb-navbar-common'; import WBProjectListing from 'wb-project-listing'; import WBInlineSearch from 'wb-inline-search'; import WBProjectCrumbs from 'wb-project-crumbs'; @@ -25,18 +25,13 @@ class WBBrowse extends Component { route(this.getUrl(params)); } - render({ ownerUuid, activePage, appCallbacks, appState, app, + render({ ownerUuid, activePage, appState, app, objTypeTab, collectionPage, processPage, workflowPage }) { return (
    - - ) } onItemClicked={ appCallbacks.navbarItemClicked } /> + route('/browse/' + item['uuid']) } /> diff --git a/frontend/src/js/page/wb-sign-out.js b/frontend/src/js/page/wb-sign-out.js new file mode 100644 index 0000000..04e98c8 --- /dev/null +++ b/frontend/src/js/page/wb-sign-out.js @@ -0,0 +1,19 @@ +import { h, Component } from 'preact'; +import { route } from 'preact-router'; + +class WBSignOut extends Component { + componentDidMount() { + delete window.localStorage['arvHost']; + delete window.localStorage['arvToken']; + delete window.localStorage['currentUser']; + route('/sign-in'); + } + + render() { + return ( +
    Signing out...
    + ); + } +} + +export default WBSignOut; diff --git a/frontend/src/js/page/wb-users-page.js b/frontend/src/js/page/wb-users-page.js index 5e79335..5388a0f 100644 --- a/frontend/src/js/page/wb-users-page.js +++ b/frontend/src/js/page/wb-users-page.js @@ -10,7 +10,7 @@ class WBUsersPage extends Component { render({ app, page }) { return (
    - + this.getUrl(page) } /> From d682eea9ac81d0c418c02b744554a1268f6a0a6c Mon Sep 17 00:00:00 2001 From: Stanislaw Adaszewski Date: Tue, 4 Feb 2020 15:41:00 +0100 Subject: [PATCH 032/170] Better sign out. --- frontend/src/js/page/wb-app.js | 2 +- frontend/src/js/page/wb-sign-out.js | 9 ++++++--- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/frontend/src/js/page/wb-app.js b/frontend/src/js/page/wb-app.js index 91e5c5c..c2993fc 100644 --- a/frontend/src/js/page/wb-app.js +++ b/frontend/src/js/page/wb-app.js @@ -48,7 +48,7 @@ class WBApp extends Component { - + Date: Wed, 5 Feb 2020 17:59:14 +0100 Subject: [PATCH 033/170] Added WBWorkflowListing. --- frontend/src/js/component/wb-name-and-uuid.js | 10 ++- .../src/js/component/wb-workflow-listing.js | 84 +++++++++++++++++++ frontend/src/js/misc/wb-fetch-objects.js | 33 ++++++++ frontend/src/js/page/wb-browse.js | 8 +- 4 files changed, 132 insertions(+), 3 deletions(-) create mode 100644 frontend/src/js/component/wb-workflow-listing.js create mode 100644 frontend/src/js/misc/wb-fetch-objects.js diff --git a/frontend/src/js/component/wb-name-and-uuid.js b/frontend/src/js/component/wb-name-and-uuid.js index fe0abad..9f8aefc 100644 --- a/frontend/src/js/component/wb-name-and-uuid.js +++ b/frontend/src/js/component/wb-name-and-uuid.js @@ -6,12 +6,18 @@ import arvadosTypeName from 'arvados-type-name'; class WBNameAndUuid extends Component { componentDidMount() { - let { uuid, app } = this.props; - let { arvHost, arvToken } = app.state; + const { uuid, app, lookup } = this.props; if (!uuid) return; + if (uuid in lookup) { + this.setState({ 'item': lookup[uuid]}); + return; + } + + const { arvHost, arvToken } = app.state; + let prom = new Promise(accept => accept()); if (/[0-9a-f]{32}\+[0-9]+/g.exec(uuid)) { diff --git a/frontend/src/js/component/wb-workflow-listing.js b/frontend/src/js/component/wb-workflow-listing.js new file mode 100644 index 0000000..6329cd1 --- /dev/null +++ b/frontend/src/js/component/wb-workflow-listing.js @@ -0,0 +1,84 @@ +import { h, Component } from 'preact'; +import makeArvadosRequest from 'make-arvados-request'; +import WBTable from 'wb-table'; +import WBPagination from 'wb-pagination'; +import WBNameAndUuid from 'wb-name-and-uuid'; +import wbFetchObjects from 'wb-fetch-objects'; +import wbFormatDate from 'wb-format-date'; + +class WBWorkflowListing extends Component { + + constructor(...args) { + super(...args); + this.state.rows = []; + this.state.numPages = 0; + } + + componentDidMount() { + this.fetchItems(); + } + + prepareRows(items, ownerLookup) { + return items.map(item => [ + ( ), + item.description, + ( ), + wbFormatDate(item.created_at), + (
    + + + +
    ) + ]); + } + + fetchItems() { + const { arvHost, arvToken } = this.props.app.state; + const { page, itemsPerPage, ownerUuid } = this.props; + const filters = []; + if (ownerUuid) + filters.push([ 'owner_uuid', '=', ownerUuid ]); + const select = ['uuid', 'name', 'description', 'owner_uuid', 'created_at']; + let prom = makeArvadosRequest(arvHost, arvToken, + '/arvados/v1/workflows?filters=' + encodeURIComponent(JSON.stringify(filters)) + + '&select=' + encodeURIComponent(JSON.stringify(select)) + + '&limit=' + encodeURIComponent(itemsPerPage) + + '&offset=' + encodeURIComponent(itemsPerPage * page)); + let workflowResp; + prom = prom.then(xhr => (workflowResp = xhr.response)); + prom = prom.then(() => wbFetchObjects(arvHost, arvToken, + workflowResp.items.map(it => it.owner_uuid))); + let ownerLookup; + prom = prom.then(lookup => (ownerLookup = lookup)); + prom = prom.then(() => + this.setState({ + 'numPages': Math.ceil(workflowResp['items_available'] / workflowResp['limit']), + 'rows': this.prepareRows(workflowResp.items, ownerLookup) + })); + } + + componentWillReceiveProps(nextProps, nextState) { + this.props = nextProps; + this.fetchItems(); + } + + render({ app, ownerUuid, page, getPageUrl }, { rows, numPages }) { + return ( +
    + + + +
    + ); + } +} + +WBWorkflowListing.defaultProps = { + 'itemsPerPage': 100, + 'ownerUuid': null +}; + +export default WBWorkflowListing; diff --git a/frontend/src/js/misc/wb-fetch-objects.js b/frontend/src/js/misc/wb-fetch-objects.js new file mode 100644 index 0000000..bcc47e2 --- /dev/null +++ b/frontend/src/js/misc/wb-fetch-objects.js @@ -0,0 +1,33 @@ +import arvadosTypeName from 'arvados-type-name'; +import makeArvadosRequest from 'make-arvados-request'; + +function wbFetchObjects(arvHost, arvToken, uuids) { + const unique = {}; + uuids.map(u => (unique[u] = true)); + uuids = {}; + Object.keys(unique).map(u => { + let typeName = arvadosTypeName(u); + if (!(typeName in uuids)) + uuids[typeName] = []; + uuids[typeName].push(u); + }); + + const lookup = {}; + let prom = new Promise(accept => accept()); + for (let typeName in uuids) { + let filters = [ + ['uuid', 'in', uuids[typeName]] + ]; + prom = prom.then(() => makeArvadosRequest(arvHost, arvToken, + '/arvados/v1/' + typeName + 's?filters=' + + encodeURIComponent(JSON.stringify(filters)))); + prom = prom.then(xhr => xhr.response.items.map(it => ( + lookup[it.uuid] = it))); + } + + prom = prom.then(() => lookup); + + return prom; +} + +export default wbFetchObjects; diff --git a/frontend/src/js/page/wb-browse.js b/frontend/src/js/page/wb-browse.js index c67af04..a552abe 100644 --- a/frontend/src/js/page/wb-browse.js +++ b/frontend/src/js/page/wb-browse.js @@ -7,6 +7,7 @@ import WBProjectCrumbs from 'wb-project-crumbs'; import WBTabs from 'wb-tabs'; import WBProcessListing from 'wb-process-listing'; import WBCollectionListing from 'wb-collection-listing'; +import WBWorkflowListing from 'wb-workflow-listing'; class WBBrowse extends Component { getUrl(params) { @@ -67,7 +68,12 @@ class WBBrowse extends Component { onPageChanged={ i => this.route({ 'processPage': i }) } /> ) : (objTypeTab === 'workflow' ? ( - null + this.getUrl({ 'workflowPage': i }) } /> + ) : null)) }
    From dc1c6c282f5ce2dd2cefbd30fcd357dfc8ffb9ea Mon Sep 17 00:00:00 2001 From: Stanislaw Adaszewski Date: Wed, 5 Feb 2020 18:13:50 +0100 Subject: [PATCH 034/170] Changed default collection url to content browser, added properties as optional action in the listing. --- frontend/src/js/component/wb-collection-listing.js | 13 +++++++++++-- frontend/src/js/component/wb-name-and-uuid.js | 2 +- frontend/src/js/misc/url-for-object.js | 11 +++++++---- 3 files changed, 19 insertions(+), 7 deletions(-) diff --git a/frontend/src/js/component/wb-collection-listing.js b/frontend/src/js/component/wb-collection-listing.js index 993839c..90caaf1 100644 --- a/frontend/src/js/component/wb-collection-listing.js +++ b/frontend/src/js/component/wb-collection-listing.js @@ -39,7 +39,16 @@ class WBCollectionListing extends Component {
    { item.owner_uuid }
    ), item['file_count'], - filesize(item['file_size_total']) + filesize(item['file_size_total']), + (
    + + + + +
    ) ]); } @@ -109,7 +118,7 @@ class WBCollectionListing extends Component { render({ app, ownerUuid, activePage, getPageUrl }, { rows, numPages }) { return (
    - Date: Wed, 5 Feb 2020 18:43:06 +0100 Subject: [PATCH 035/170] Started implementing request ordering. --- .../src/js/component/wb-collection-listing.js | 18 +++++++++++++++--- frontend/src/js/misc/make-arvados-request.js | 15 +++++++++++++-- 2 files changed, 28 insertions(+), 5 deletions(-) diff --git a/frontend/src/js/component/wb-collection-listing.js b/frontend/src/js/component/wb-collection-listing.js index 90caaf1..a008878 100644 --- a/frontend/src/js/component/wb-collection-listing.js +++ b/frontend/src/js/component/wb-collection-listing.js @@ -13,6 +13,7 @@ class WBCollectionListing extends Component { super(...args); this.state.rows = []; this.state.numPages = 0; + this.state.orderStream = uuid.v4(); } componentDidMount() { @@ -65,7 +66,8 @@ class WBCollectionListing extends Component { let prom = makeArvadosRequest(arvHost, arvToken, '/arvados/v1/collections?filters=' + encodeURIComponent(JSON.stringify(filters)) + '&limit=' + encodeURIComponent(itemsPerPage) + - '&offset=' + encodeURIComponent(itemsPerPage * activePage)); + '&offset=' + encodeURIComponent(itemsPerPage * activePage), + { 'orderStream': this.state.orderStream }); let collections; let numPages @@ -106,8 +108,14 @@ class WBCollectionListing extends Component { prom = prom.then(ownerLookup => this.setState({ 'numPages': numPages, - 'rows': this.prepareRows(collections, ownerLookup) + 'rows': this.prepareRows(collections, ownerLookup), + 'error': null })); + + prom = prom.catch(() => this.setState({ + 'error': 'An error occured querying the Arvados API', + 'rows': [] + })); } componentWillReceiveProps(nextProps, nextState) { @@ -115,9 +123,13 @@ class WBCollectionListing extends Component { this.fetchItems(); } - render({ app, ownerUuid, activePage, getPageUrl }, { rows, numPages }) { + render({ app, ownerUuid, activePage, getPageUrl }, { rows, numPages, error }) { return (
    + { error ? () : null } + diff --git a/frontend/src/js/misc/make-arvados-request.js b/frontend/src/js/misc/make-arvados-request.js index 7783872..a285f08 100644 --- a/frontend/src/js/misc/make-arvados-request.js +++ b/frontend/src/js/misc/make-arvados-request.js @@ -1,3 +1,4 @@ +const requestOrdering = {}; function makeArvadosRequest(arvHost, arvToken, endpoint, params={}) { const defaultParams = { @@ -6,12 +7,22 @@ function makeArvadosRequest(arvHost, arvToken, endpoint, params={}) { 'contentType': 'application/json;charset=utf-8', 'responseType': 'json', 'useSsl': true, - 'requireToken': true + 'requireToken': true, + 'orderStream': null }; Object.keys(defaultParams).map(k => (params[k] = (k in params ? params[k] : defaultParams[k]))); - let { method, data, contentType, responseType, useSsl, requireToken } = params; + let { method, data, contentType, responseType, useSsl, requireToken, orderStream } = params; + + let orderId; + if (orderStream) { + if (!(orderStream in requestOrdering)) + requestOrdering[orderStream] = { 'counter': 0, 'started': [], 'completed': [] }; + requestOrdering[orderStream].counter += 1; + orderId = requestOrdering[orderStream].counter; + requestOrdering[orderStream].started.push(orderId); + } if (!(arvHost && (arvToken || !requireToken))) return new Promise((accept, reject) => reject()); From d955e5b1ee5cf93858d95160f9ee473193459df6 Mon Sep 17 00:00:00 2001 From: Stanislaw Adaszewski Date: Thu, 6 Feb 2020 07:37:23 +0100 Subject: [PATCH 036/170] Request ordering implemented and seems to be working but decreases responsiveness, perhaps better to manage ordering within components. --- .../src/js/component/wb-collection-listing.js | 3 +- frontend/src/js/misc/make-arvados-request.js | 22 ++----- .../src/js/misc/wb-apply-request-ordering.js | 66 +++++++++++++++++++ 3 files changed, 74 insertions(+), 17 deletions(-) create mode 100644 frontend/src/js/misc/wb-apply-request-ordering.js diff --git a/frontend/src/js/component/wb-collection-listing.js b/frontend/src/js/component/wb-collection-listing.js index a008878..0eeb5e6 100644 --- a/frontend/src/js/component/wb-collection-listing.js +++ b/frontend/src/js/component/wb-collection-listing.js @@ -66,8 +66,7 @@ class WBCollectionListing extends Component { let prom = makeArvadosRequest(arvHost, arvToken, '/arvados/v1/collections?filters=' + encodeURIComponent(JSON.stringify(filters)) + '&limit=' + encodeURIComponent(itemsPerPage) + - '&offset=' + encodeURIComponent(itemsPerPage * activePage), - { 'orderStream': this.state.orderStream }); + '&offset=' + encodeURIComponent(itemsPerPage * activePage)); let collections; let numPages diff --git a/frontend/src/js/misc/make-arvados-request.js b/frontend/src/js/misc/make-arvados-request.js index a285f08..0ce66a0 100644 --- a/frontend/src/js/misc/make-arvados-request.js +++ b/frontend/src/js/misc/make-arvados-request.js @@ -1,4 +1,4 @@ -const requestOrdering = {}; +import wbApplyRequestOrdering from 'wb-apply-request-ordering'; function makeArvadosRequest(arvHost, arvToken, endpoint, params={}) { const defaultParams = { @@ -7,22 +7,12 @@ function makeArvadosRequest(arvHost, arvToken, endpoint, params={}) { 'contentType': 'application/json;charset=utf-8', 'responseType': 'json', 'useSsl': true, - 'requireToken': true, - 'orderStream': null + 'requireToken': true }; Object.keys(defaultParams).map(k => (params[k] = (k in params ? params[k] : defaultParams[k]))); - let { method, data, contentType, responseType, useSsl, requireToken, orderStream } = params; - - let orderId; - if (orderStream) { - if (!(orderStream in requestOrdering)) - requestOrdering[orderStream] = { 'counter': 0, 'started': [], 'completed': [] }; - requestOrdering[orderStream].counter += 1; - orderId = requestOrdering[orderStream].counter; - requestOrdering[orderStream].started.push(orderId); - } + let { method, data, contentType, responseType, useSsl, requireToken } = params; if (!(arvHost && (arvToken || !requireToken))) return new Promise((accept, reject) => reject()); @@ -35,7 +25,7 @@ function makeArvadosRequest(arvHost, arvToken, endpoint, params={}) { xhr.setRequestHeader('Content-Type', contentType); xhr.responseType = responseType; - let res = new Promise((accept, reject) => { + let prom = new Promise((accept, reject) => { xhr.onreadystatechange = () => { if (xhr.readyState !== 4) return; @@ -47,7 +37,9 @@ function makeArvadosRequest(arvHost, arvToken, endpoint, params={}) { xhr.send(data); }); - return res; + // prom = wbApplyRequestOrdering(prom); + + return prom; } export default makeArvadosRequest; diff --git a/frontend/src/js/misc/wb-apply-request-ordering.js b/frontend/src/js/misc/wb-apply-request-ordering.js new file mode 100644 index 0000000..860fd77 --- /dev/null +++ b/frontend/src/js/misc/wb-apply-request-ordering.js @@ -0,0 +1,66 @@ +const defaultOrderRegistry = {}; + +function wbApplyRequestOrdering(prom, orderRegistry) { + let orderId; + + if (!orderRegistry) + orderRegistry = defaultOrderRegistry; + + if (Object.keys(orderRegistry).length === 0) { + orderRegistry.started = 0; + orderRegistry.pendingCompletion = {}; + orderRegistry.completed = { 0: true }; + } + + orderRegistry.started += 1; + orderId = orderRegistry.started; + // console.log('New orderId: ' + orderId); + + const orderCallback = ((isCatch, payload) => { + // console.log('orderId: ' + orderId + + // ', pendingCompletion: ' + Object.keys(orderRegistry.pendingCompletion) + + // ', completed: ' + Object.keys(orderRegistry.completed)); + + if ((orderId - 1) in orderRegistry.completed) { + // console.log('Running: ' + orderId); + orderRegistry.completed[orderId] = true; + delete orderRegistry.pendingCompletion[orderId]; + + const keys = Object.keys(orderRegistry.pendingCompletion); + keys.sort((a, b) => (a - b)); + keys.map(k => { + if ((k - 1) in orderRegistry.completed) { + // console.log('Running: ' + k); + orderRegistry.pendingCompletion[k](); + orderRegistry.completed[k] = true; + delete orderRegistry.pendingCompletion[k]; + } + }); + + if (orderRegistry.started in orderRegistry.completed) { + // console.log('Garbage collect'); + orderRegistry.started = 0; + orderRegistry.completed = { 0: true }; + } + + if (isCatch) + throw payload; + else + return payload; + } + + const prom_1 = new Promise((accept, reject) => { + orderRegistry.pendingCompletion[orderId] = (() => + (isCatch ? reject(payload) : accept(payload))); + }); + + return prom_1; + }); + + prom = prom.then(xhr => orderCallback(false, xhr)); + prom = prom.catch(e => orderCallback(true, e)); + + return prom; +} + +export default wbApplyRequestOrdering; From e19c17708ad766c9da48413f6eb07febca4b3432 Mon Sep 17 00:00:00 2001 From: Stanislaw Adaszewski Date: Thu, 6 Feb 2020 08:01:22 +0100 Subject: [PATCH 037/170] Renamed wbApplyRequestOrdering to more universal wpApplyPromiseOrdering. --- frontend/src/js/component/wb-arvados-crumbs.js | 4 +++- frontend/src/js/misc/make-arvados-request.js | 6 ++++-- ...ply-request-ordering.js => wb-apply-promise-ordering.js} | 4 ++-- 3 files changed, 9 insertions(+), 5 deletions(-) rename frontend/src/js/misc/{wb-apply-request-ordering.js => wb-apply-promise-ordering.js} (91%) diff --git a/frontend/src/js/component/wb-arvados-crumbs.js b/frontend/src/js/component/wb-arvados-crumbs.js index f4612f1..233be8e 100644 --- a/frontend/src/js/component/wb-arvados-crumbs.js +++ b/frontend/src/js/component/wb-arvados-crumbs.js @@ -16,7 +16,9 @@ class WBArvadosCrumbs extends Component { let { arvHost, arvToken } = this.props.app.state; let prom = fetchObjectParents(arvHost, arvToken, this.props.uuid); - prom = prom.then(parents => this.setState({ 'items': parents })); + prom = prom.then(parents => { + this.setState({ 'items': parents }); + }); } componentDidMount() { diff --git a/frontend/src/js/misc/make-arvados-request.js b/frontend/src/js/misc/make-arvados-request.js index 0ce66a0..7ed69a5 100644 --- a/frontend/src/js/misc/make-arvados-request.js +++ b/frontend/src/js/misc/make-arvados-request.js @@ -1,4 +1,6 @@ -import wbApplyRequestOrdering from 'wb-apply-request-ordering'; +import wbApplyPromiseOrdering from 'wb-apply-promise-ordering'; + +const requestPromiseOrdering = {}; function makeArvadosRequest(arvHost, arvToken, endpoint, params={}) { const defaultParams = { @@ -37,7 +39,7 @@ function makeArvadosRequest(arvHost, arvToken, endpoint, params={}) { xhr.send(data); }); - // prom = wbApplyRequestOrdering(prom); + prom = wbApplyPromiseOrdering(prom, requestPromiseOrdering); return prom; } diff --git a/frontend/src/js/misc/wb-apply-request-ordering.js b/frontend/src/js/misc/wb-apply-promise-ordering.js similarity index 91% rename from frontend/src/js/misc/wb-apply-request-ordering.js rename to frontend/src/js/misc/wb-apply-promise-ordering.js index 860fd77..161b0db 100644 --- a/frontend/src/js/misc/wb-apply-request-ordering.js +++ b/frontend/src/js/misc/wb-apply-promise-ordering.js @@ -1,6 +1,6 @@ const defaultOrderRegistry = {}; -function wbApplyRequestOrdering(prom, orderRegistry) { +function wbApplyPromiseOrdering(prom, orderRegistry) { let orderId; if (!orderRegistry) @@ -63,4 +63,4 @@ function wbApplyRequestOrdering(prom, orderRegistry) { return prom; } -export default wbApplyRequestOrdering; +export default wbApplyPromiseOrdering; From cacfe20b80eecdb0b995743bf57b101459b2f61c Mon Sep 17 00:00:00 2001 From: Stanislaw Adaszewski Date: Thu, 6 Feb 2020 08:03:39 +0100 Subject: [PATCH 038/170] Fix for colon parsing in manifests. --- frontend/src/js/misc/wb-collection-manifest.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/frontend/src/js/misc/wb-collection-manifest.js b/frontend/src/js/misc/wb-collection-manifest.js index 68fb05e..1f455ad 100644 --- a/frontend/src/js/misc/wb-collection-manifest.js +++ b/frontend/src/js/misc/wb-collection-manifest.js @@ -105,7 +105,8 @@ class WBManifestReader { let fileTokens = tokens.slice(n); fileTokens.map(t => { - let [ position, size, fileName ] = t.split(':'); + let [ position, size, ...fileName ] = t.split(':'); + fileName = fileName.join(':'); fileName = this.unescapeName(fileName); this.appendFile(streamName, locators, Number(position), Number(size), fileName); From e92f70b6ad488be1d0f754669a7bf606a718fe67 Mon Sep 17 00:00:00 2001 From: Stanislaw Adaszewski Date: Thu, 6 Feb 2020 08:55:24 +0100 Subject: [PATCH 039/170] Added WBWorkflowView. --- .../src/js/component/wb-workflow-fields.js | 99 +++++++++++++++++++ frontend/src/js/misc/url-for-object.js | 2 +- frontend/src/js/page/wb-app.js | 3 + frontend/src/js/page/wb-workflow-view.js | 29 ++++++ 4 files changed, 132 insertions(+), 1 deletion(-) create mode 100644 frontend/src/js/component/wb-workflow-fields.js create mode 100644 frontend/src/js/page/wb-workflow-view.js diff --git a/frontend/src/js/component/wb-workflow-fields.js b/frontend/src/js/component/wb-workflow-fields.js new file mode 100644 index 0000000..b84f786 --- /dev/null +++ b/frontend/src/js/component/wb-workflow-fields.js @@ -0,0 +1,99 @@ +import { h, Component } from 'preact'; +import WBTable from 'wb-table'; +import makeArvadosRequest from 'make-arvados-request'; +import WBAccordion from 'wb-accordion'; + +class WBWorkflowFields extends Component { + componentDidMount() { + this.prepareRows(); + } + + componentWillReceiveProps(nextProps) { + this.props = nextProps; + this.prepareRows(); + } + + prepareRows() { + let { uuid, app } = this.props; + let { arvHost, arvToken } = app.state; + + let prom = makeArvadosRequest(arvHost, arvToken, + '/arvados/v1/workflows/' + uuid); + + prom = prom.then(xhr => { + const item = xhr.response; + const definition = JSON.parse(item.definition); + const graph = definition['$graph']; + + let rows = [ + [ 'Name', item.name ], + [ 'Description', item.description || ({ String(item.description) }) ], + [ 'CWL Version', definition.cwlVersion ], + ]; + + let keys = graph.map(it => it.id); + keys.sort(); + keys = keys.splice(keys.indexOf('#main'), 1).concat(keys); + + keys.map(k => { + const it = graph.find(it => (it.id === k)); + rows.push([ + it.id, ( +
    +
    Class: { it['class'] }
    + { it.label ?
    Label: { it.label }
    : null } + { it.doc ?
    Doc: { it.doc }
    : null } + + +
    { JSON.stringify(it.inputs, null, 2) }
    + +
    { JSON.stringify(it.outputs, null, 2) }
    + + { (() => { + delete it['inputs']; + delete it['outputs']; + delete it['class']; + delete it['label']; + delete it['doc']; + delete it['id']; + return ( +
    { JSON.stringify(it, null, 2) }
    + ); + })() } + +
    +
    + )]); + }); + + /* [ 'Graph', ( + it.id) } + cardHeaderClass="card-header-sm"> + + { graph.map(it => ( +
    { JSON.stringify(it, null, 2) }
    + )) } + +
    + ) ] + ];*/ + this.setState({ 'rows': rows }); + }); + } + + render({}, { rows }) { + return ( + rows ? ( + + ) : ( +
    Loading...
    + ) + ); + } +} + +export default WBWorkflowFields; diff --git a/frontend/src/js/misc/url-for-object.js b/frontend/src/js/misc/url-for-object.js index 31c17e1..9212278 100644 --- a/frontend/src/js/misc/url-for-object.js +++ b/frontend/src/js/misc/url-for-object.js @@ -9,7 +9,7 @@ function urlForObject(item, mode='primary') { else if (objectType === 'container_request') return ('/process/' + item.uuid); else if (objectType === 'workflow') - return ('https://wb.arkau.roche.com/workflows/' + item.uuid); + return ('/workflow/' + item.uuid); else if (objectType === 'collection') { if (mode === 'primary' || mode === 'browse') return ('/collection-browse/' + item.uuid); diff --git a/frontend/src/js/page/wb-app.js b/frontend/src/js/page/wb-app.js index c2993fc..c2e214b 100644 --- a/frontend/src/js/page/wb-app.js +++ b/frontend/src/js/page/wb-app.js @@ -8,6 +8,7 @@ import WBProcessView from 'wb-process-view'; import WBCollectionView from 'wb-collection-view'; import WBCollectionBrowse from 'wb-collection-browse'; import WBUsersPage from 'wb-users-page'; +import WBWorkflowView from 'wb-workflow-view'; import arvadosTypeName from 'arvados-type-name'; class WBApp extends Component { @@ -61,6 +62,8 @@ class WBApp extends Component { + + ); } diff --git a/frontend/src/js/page/wb-workflow-view.js b/frontend/src/js/page/wb-workflow-view.js new file mode 100644 index 0000000..a3a4aab --- /dev/null +++ b/frontend/src/js/page/wb-workflow-view.js @@ -0,0 +1,29 @@ +import { h, Component } from 'preact'; +import WBNavbarCommon from 'wb-navbar-common'; +import WBArvadosCrumbs from 'wb-arvados-crumbs'; +import WBCommonFields from 'wb-common-fields'; +import WBWorkflowFields from 'wb-workflow-fields'; + +class WBWorkflowView extends Component { + render({ app, uuid }, {}) { + return ( +
    + + + + +
    + This is the workflow view for { uuid } +
    + +

    Common Fields

    + + +

    Workflow Fields

    + +
    + ); + } +} + +export default WBWorkflowView; From 7a49cdce1e086270bd6f7dda1a9e27e4c2cde2af Mon Sep 17 00:00:00 2001 From: Stanislaw Adaszewski Date: Thu, 6 Feb 2020 10:11:06 +0100 Subject: [PATCH 040/170] Fixes not to fetch manifest_text when not needed. --- .../src/js/component/wb-collection-fields.js | 13 +++++-- frontend/src/js/component/wb-common-fields.js | 15 +++---- frontend/src/js/component/wb-name-and-uuid.js | 26 +++++++++---- .../fetch-project-parents.js | 0 .../wb-project-crumbs.js | 0 frontend/src/js/misc/fetch-object-parents.js | 39 ++++++++----------- frontend/src/js/page/wb-browse.js | 7 ++-- 7 files changed, 56 insertions(+), 44 deletions(-) rename frontend/src/js/{misc => deprecated}/fetch-project-parents.js (100%) rename frontend/src/js/{component => deprecated}/wb-project-crumbs.js (100%) diff --git a/frontend/src/js/component/wb-collection-fields.js b/frontend/src/js/component/wb-collection-fields.js index 7f2d93a..e660eb5 100644 --- a/frontend/src/js/component/wb-collection-fields.js +++ b/frontend/src/js/component/wb-collection-fields.js @@ -22,12 +22,17 @@ class WBCollectionFields extends Component { let { uuid, app } = this.props; let { arvHost, arvToken } = app.state; - let item; + const filters = [ + ['uuid', '=', uuid] + ]; + let prom = makeArvadosRequest(arvHost, arvToken, - '/arvados/v1/collections/' + uuid); - prom = prom.then(xhr => (item = xhr.response)); + '/arvados/v1/collections?filters=' + encodeURIComponent(JSON.stringify(filters))); - prom = prom.then(() => { + prom = prom.then(xhr => { + const item = xhr.response.items[0]; + if (!item) + throw Error('Item not found'); let rows = [ [ 'Name', item.name ], [ 'Description', item.description || ({ String(item.description) }) ], diff --git a/frontend/src/js/component/wb-common-fields.js b/frontend/src/js/component/wb-common-fields.js index 65bf0ce..d895bdc 100644 --- a/frontend/src/js/component/wb-common-fields.js +++ b/frontend/src/js/component/wb-common-fields.js @@ -22,17 +22,18 @@ class WBCommonFields extends Component { let { uuid, app } = this.props; let { arvHost, arvToken } = app.state; - let item; - let owner; - let modifiedByUser; + const filters = [ + ['uuid', '=', uuid] + ]; let prom = makeArvadosRequest(arvHost, arvToken, '/arvados/v1/' + arvadosTypeName(uuid) + - 's/' + uuid); + 's?filters=' + encodeURIComponent(JSON.stringify(filters))); - prom = prom.then(xhr => (item = xhr.response)); - - prom = prom.then(() => { + prom = prom.then(xhr => { + const item = xhr.response.items[0]; + if (!item) + throw Error('Item not found'); let rows = [ [ 'UUID', item.uuid ], [ 'Kind', item.kind ], diff --git a/frontend/src/js/component/wb-name-and-uuid.js b/frontend/src/js/component/wb-name-and-uuid.js index 4f1eaf8..4d02be5 100644 --- a/frontend/src/js/component/wb-name-and-uuid.js +++ b/frontend/src/js/component/wb-name-and-uuid.js @@ -45,14 +45,26 @@ class WBNameAndUuid extends Component { } else if (/[a-z0-9]{5}-[a-z0-9]{5}-[a-z0-9]{15}/.exec(uuid)) { let typeName = arvadosTypeName(uuid); + const filters = [ + ['uuid', '=', uuid] + ]; let prom = makeArvadosRequest(arvHost, arvToken, - '/arvados/v1/' + typeName + 's/' + uuid ); - prom = prom.then(xhr => this.setState({ - 'item': xhr.response - })); - prom = prom.catch(xhr => this.setState({ - 'error': 'Unable to retrieve: ' + xhr.status + ' (' + xhr.statusText + ')' - })); + '/arvados/v1/' + typeName + + 's?filters=' + encodeURIComponent(JSON.stringify(filters))); + prom = prom.then(xhr => { + const item = xhr.response.items[0]; + if (!item) + this.setState({ 'error': 'Item not found' }); + else + this.setState({ + 'item': item + }); + }); + prom = prom.catch(xhr => { + this.setState({ + 'error': 'Unable to retrieve: ' + xhr.status + ' (' + xhr.statusText + ')' + }); + }); } else { this.setState({ diff --git a/frontend/src/js/misc/fetch-project-parents.js b/frontend/src/js/deprecated/fetch-project-parents.js similarity index 100% rename from frontend/src/js/misc/fetch-project-parents.js rename to frontend/src/js/deprecated/fetch-project-parents.js diff --git a/frontend/src/js/component/wb-project-crumbs.js b/frontend/src/js/deprecated/wb-project-crumbs.js similarity index 100% rename from frontend/src/js/component/wb-project-crumbs.js rename to frontend/src/js/deprecated/wb-project-crumbs.js diff --git a/frontend/src/js/misc/fetch-object-parents.js b/frontend/src/js/misc/fetch-object-parents.js index 65a06fe..ea1d1c9 100644 --- a/frontend/src/js/misc/fetch-object-parents.js +++ b/frontend/src/js/misc/fetch-object-parents.js @@ -1,46 +1,41 @@ import makeArvadosRequest from 'make-arvados-request'; import arvadosTypeName from 'arvados-type-name'; +import arvadosObjectName from 'arvados-object-name'; function fetchObjectParents(arvHost, arvToken, uuid) { let parents = []; let cb = xhr => { - let objectType = arvadosTypeName(xhr.response['uuid'].split('-')[1]); - - let item = { - 'uuid': xhr.response['uuid'] - }; - - if (objectType === 'user') { - item['name'] = xhr.response['first_name'] + ' ' + xhr.response['last_name']; - - } else { - item['name'] = xhr.response['name']; - } - - if (objectType === 'group') { - item['group_class'] = xhr.response['group_class']; - } + const item = xhr.response.items[0]; + if (!item) + return parents.reverse(); + item.name = arvadosObjectName(item); parents.push(item); - if (!xhr.response['owner_uuid'] || - xhr.response['owner_uuid'].endsWith('-tpzed-000000000000000')) { + if (!item.owner_uuid || + item.owner_uuid.endsWith('-tpzed-000000000000000')) { return parents.reverse(); } - objectType = arvadosTypeName(xhr.response['owner_uuid'].split('-')[1]); + const objectType = arvadosTypeName(item.owner_uuid); + const filters = [ + ['uuid', '=', item.owner_uuid] + ]; return makeArvadosRequest(arvHost, arvToken, '/arvados/v1/' + objectType + 's' + - '/' + xhr.response['owner_uuid']).then(cb); + '?filters=' + encodeURIComponent(JSON.stringify(filters))).then(cb); }; - let objectType = arvadosTypeName(uuid.split('-')[1]); + const objectType = arvadosTypeName(uuid); + const filters = [ + ['uuid', '=', uuid] + ]; let prom = makeArvadosRequest(arvHost, arvToken, '/arvados/v1/' + objectType + 's' + - '/' + uuid); + '?filters=' + encodeURIComponent(JSON.stringify(filters))); prom = prom.then(cb); return prom; diff --git a/frontend/src/js/page/wb-browse.js b/frontend/src/js/page/wb-browse.js index a552abe..4a8506c 100644 --- a/frontend/src/js/page/wb-browse.js +++ b/frontend/src/js/page/wb-browse.js @@ -3,7 +3,7 @@ import { route } from 'preact-router'; import WBNavbarCommon from 'wb-navbar-common'; import WBProjectListing from 'wb-project-listing'; import WBInlineSearch from 'wb-inline-search'; -import WBProjectCrumbs from 'wb-project-crumbs'; +import WBArvadosCrumbs from 'wb-arvados-crumbs'; import WBTabs from 'wb-tabs'; import WBProcessListing from 'wb-process-listing'; import WBCollectionListing from 'wb-collection-listing'; @@ -34,8 +34,7 @@ class WBBrowse extends Component { - route('/browse/' + item['uuid']) } /> + @@ -73,7 +72,7 @@ class WBBrowse extends Component { itemsPerPage="20" page={ Number(workflowPage || 0) } getPageUrl={ i => this.getUrl({ 'workflowPage': i }) } /> - + ) : null)) }
    From 56c58b325db80c66fe4b9b7822f6889661863f5b Mon Sep 17 00:00:00 2001 From: Stanislaw Adaszewski Date: Thu, 6 Feb 2020 14:27:43 +0100 Subject: [PATCH 041/170] Working on workflow launcher --- frontend/src/js/page/wb-app.js | 3 + .../src/js/page/wb-launch-workflow-page.js | 97 +++++++++++++++++++ 2 files changed, 100 insertions(+) create mode 100644 frontend/src/js/page/wb-launch-workflow-page.js diff --git a/frontend/src/js/page/wb-app.js b/frontend/src/js/page/wb-app.js index c2e214b..5ec264b 100644 --- a/frontend/src/js/page/wb-app.js +++ b/frontend/src/js/page/wb-app.js @@ -9,6 +9,7 @@ import WBCollectionView from 'wb-collection-view'; import WBCollectionBrowse from 'wb-collection-browse'; import WBUsersPage from 'wb-users-page'; import WBWorkflowView from 'wb-workflow-view'; +import WBLaunchWorkflowPage from 'wb-launch-workflow-page'; import arvadosTypeName from 'arvados-type-name'; class WBApp extends Component { @@ -64,6 +65,8 @@ class WBApp extends Component { + + ); } diff --git a/frontend/src/js/page/wb-launch-workflow-page.js b/frontend/src/js/page/wb-launch-workflow-page.js new file mode 100644 index 0000000..db2ca9e --- /dev/null +++ b/frontend/src/js/page/wb-launch-workflow-page.js @@ -0,0 +1,97 @@ +import { h, Component } from 'preact'; +import WBNavbarCommon from 'wb-navbar-common'; +import WBArvadosCrumbs from 'wb-arvados-crumbs'; +import makeArvadosRequest from 'make-arvados-request'; +import linkState from 'linkstate'; + +function createInputsTemplate(workflow) { + const g = JSON.parse(workflow.definition)['$graph']; + const main = g.find(it => (it.id === '#main')); + /* let res = ''; + main.inputs.map(it => { + let id = it.id.split('/'); + id = id[id.length - 1]; + if (it.label) res += ' // ' + it.label + '\n'; + if (it.doc) res += ' //' + it.doc + '\n'; + res += ' ' + it.type + res += '\'' + it.id + '\': null' + }); */ + let res = main.inputs.map(it => { it.value = null; return it; }); + res = JSON.stringify(res, null, 2); + res = res.split('\n'); + res = res.map((ln, i) => (i == 0 ? ln : ' ' + ln)); + res = res.join('\n'); + return res; +} + +class WBLaunchWorkflowPage extends Component { + componentDidMount() { + let { app, workflowUuid } = this.props; + let { arvHost, arvToken } = app.state; + + let prom = makeArvadosRequest(arvHost, arvToken, + '/arvados/v1/workflows/' + workflowUuid); + prom = prom.then(xhr => this.setState({ + 'workflow': xhr.response, + 'processName': xhr.response.name, + 'processDescription': xhr.response.description, + 'inputsFunctionText': '(() => {\n return ' + + createInputsTemplate(xhr.response) + + ';\n})()' + })); + } + + render({ app, projectUuid, workflowUuid }, + { workflow, processName, processDescription, + inputsFunctionText }) { + + return ( +
    + + + { workflow ? + (
    +

    Launch Workflow

    + +
    + + +
    + +
    + + +
    + +
    + + +
    + +
    + + +
    + +
    + + +
    + +
    + + +
    + ) :
    Loading...
    } +
    + ); + } +} + +export default WBLaunchWorkflowPage; From 236dc12fcad0d48c46750204b274896c5a58d054 Mon Sep 17 00:00:00 2001 From: Stanislaw Adaszewski Date: Fri, 7 Feb 2020 12:40:39 +0100 Subject: [PATCH 042/170] Continuing work on workflow launcher --- .gitignore | 2 +- frontend/rollup.config.js | 2 +- .../src/js/component/wb-collection-listing.js | 8 ++ .../src/js/component/wb-project-listing.js | 13 +++- .../src/js/component/wb-workflow-listing.js | 4 +- .../src/js/deprecated/wb-browse-dialog.js | 35 +++++++++ frontend/src/js/dialog/wb-toolbox-dialog.js | 77 +++++++++++++++++++ frontend/src/js/misc/url-for-object.js | 9 ++- frontend/src/js/page/wb-app.js | 8 ++ frontend/src/js/page/wb-browse.js | 3 +- .../src/js/page/wb-launch-workflow-page.js | 12 ++- 11 files changed, 163 insertions(+), 10 deletions(-) create mode 100644 frontend/src/js/deprecated/wb-browse-dialog.js create mode 100644 frontend/src/js/dialog/wb-toolbox-dialog.js diff --git a/.gitignore b/.gitignore index 3ca30a8..f8d63e5 100644 --- a/.gitignore +++ b/.gitignore @@ -4,4 +4,4 @@ node_modules package-lock.json /frontend/dist/ /backend/server.pem - +/testdata/ diff --git a/frontend/rollup.config.js b/frontend/rollup.config.js index 7032717..1b9adb6 100755 --- a/frontend/rollup.config.js +++ b/frontend/rollup.config.js @@ -16,7 +16,7 @@ export default { }, plugins: [ includePaths({ - paths: ['src/js', 'src/js/widget', 'src/js/misc', 'src/js/component', 'src/js/page'] + paths: ['src/js', 'src/js/widget', 'src/js/misc', 'src/js/component', 'src/js/page', 'src/js/dialog'] }), copy({ 'src/html/index.html': 'dist/index.html', diff --git a/frontend/src/js/component/wb-collection-listing.js b/frontend/src/js/component/wb-collection-listing.js index 0eeb5e6..9523399 100644 --- a/frontend/src/js/component/wb-collection-listing.js +++ b/frontend/src/js/component/wb-collection-listing.js @@ -21,6 +21,8 @@ class WBCollectionListing extends Component { } prepareRows(items, ownerLookup) { + let { app } = this.props; + return items.map(item => [ (
    @@ -42,10 +44,16 @@ class WBCollectionListing extends Component { item['file_count'], filesize(item['file_size_total']), (
    + + + diff --git a/frontend/src/js/component/wb-project-listing.js b/frontend/src/js/component/wb-project-listing.js index cb107d1..9d37885 100644 --- a/frontend/src/js/component/wb-project-listing.js +++ b/frontend/src/js/component/wb-project-listing.js @@ -17,6 +17,8 @@ class WBProjectListing extends Component { } prepareRows(items) { + let { app } = this.props; + return items.map(item => [ (
    @@ -26,7 +28,13 @@ class WBProjectListing extends Component {
    { item['uuid'] }
    ), item['description'], - item['owner_uuid'] + item['owner_uuid'], + (
    + +
    ) ]); } @@ -57,8 +65,9 @@ class WBProjectListing extends Component { render({ arvHost, arvToken, ownerUuid, activePage, onPageChanged }, { rows, numPages }) { return (
    - + onPageChanged(i) } /> diff --git a/frontend/src/js/component/wb-workflow-listing.js b/frontend/src/js/component/wb-workflow-listing.js index 6329cd1..f7bb05b 100644 --- a/frontend/src/js/component/wb-workflow-listing.js +++ b/frontend/src/js/component/wb-workflow-listing.js @@ -5,6 +5,7 @@ import WBPagination from 'wb-pagination'; import WBNameAndUuid from 'wb-name-and-uuid'; import wbFetchObjects from 'wb-fetch-objects'; import wbFormatDate from 'wb-format-date'; +import urlForObject from 'url-for-object'; class WBWorkflowListing extends Component { @@ -25,7 +26,8 @@ class WBWorkflowListing extends Component { ( ), wbFormatDate(item.created_at), (
    - +
    ) diff --git a/frontend/src/js/deprecated/wb-browse-dialog.js b/frontend/src/js/deprecated/wb-browse-dialog.js new file mode 100644 index 0000000..7e97ce6 --- /dev/null +++ b/frontend/src/js/deprecated/wb-browse-dialog.js @@ -0,0 +1,35 @@ +import { h, Component } from 'preact'; + +class WBBrowseDialog extends Component { + constructor(...args) { + super(...args); + } + + render({ id }) { + return ( +
    { r[idx] }{ r[idx] }{ r[idx] }