From f3e0d5e987562a718dcd0ca3e00059640567f7de Mon Sep 17 00:00:00 2001 From: Charles Overbeck Date: Mon, 20 May 2019 09:35:44 -0700 Subject: [PATCH] Feature/2386/config (#628) dockstore/dockstore#2386 All api calls go to /api on same domain (host/port) as UI is served from endpoint -- see app.module.ts In the APP_INITIALIZER, UI calls /api/metadata/config.json and sets dockstore.model.ts based on the response. When running locally, use Angular proxy. Just use `npm run start`. When running on Circle-CI, use nginx. Also: In checkerWorkflowFromWorkflow.ts, it was checking if a button was not visible, then if it was visible. Removed that, as I think it was relying on a delay for the button to appear. --- .circleci/config.yml | 14 +++- .circleci/nginx.conf.tmpl | 26 +++++++ .../group1/checkerWorkflowFromWorkflow.ts | 3 +- cypress/integration/group1/curator.ts | 2 +- cypress/integration/group1/dropdown.ts | 22 +++--- cypress/integration/group2/searchTable.ts | 6 +- cypress/integration/group3/organizations.ts | 11 +-- cypress/integration/group3/sharedWorkflows.ts | 6 +- .../integration/group3/starErrorMessage.ts | 12 +-- package.json | 2 +- proxy.conf.json | 11 +++ scripts/run-travis-script.sh | 2 +- src/app/app.module.ts | 23 ++++-- src/app/configuration.service.spec.ts | 19 +++++ src/app/configuration.service.ts | 76 +++++++++++++++++++ src/app/footer/footer.component.ts | 3 +- src/app/shared/dockstore.model.ts | 60 +++++++-------- .../state/descriptors.service.spec.ts | 3 +- travisci/web.yml | 2 +- 19 files changed, 226 insertions(+), 77 deletions(-) create mode 100644 .circleci/nginx.conf.tmpl create mode 100644 proxy.conf.json create mode 100644 src/app/configuration.service.spec.ts create mode 100644 src/app/configuration.service.ts diff --git a/.circleci/config.yml b/.circleci/config.yml index d4cc9df90a..0652c92261 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -107,14 +107,20 @@ commands: name: Install cypress dependencies command: sudo apt install libgtk2.0-0 -yq - run: - name: Install angular http server (replace with nginx) - command: bash -i -c 'npm i angular-http-server@1.8.1 cypress@3.2.0 --no-save' + name: Install cypress + command: bash -i -c 'npm i cypress@3.2.0 --no-save' - run: name: Prepare webservice command: bash -i -c 'npm run webservice' - run: - name: Serve - command: bash -i -c 'npx angular-http-server --path ./dist -p 4200' + name: Install nginx + command: sudo apt install -y nginx || true + - run: + name: Prepapre nginx config + command: sed "s%REPLACEME%`pwd`%" .circleci/nginx.conf.tmpl > .circleci/nginx.conf + - run: + name: Run nginx + command: sudo nginx -c `pwd`/.circleci/nginx.conf background: true - run: name: Run webservice diff --git a/.circleci/nginx.conf.tmpl b/.circleci/nginx.conf.tmpl new file mode 100644 index 0000000000..c6a41ed43e --- /dev/null +++ b/.circleci/nginx.conf.tmpl @@ -0,0 +1,26 @@ +events { +} + +http { + include /etc/nginx/mime.types; + server { + listen 4200; + + location = /swagger.json { + proxy_pass http://localhost:8080/swagger.json; + } + + location /api/ { + rewrite ^ $request_uri; + rewrite ^/api/(.*) $1 break; + return 400; + proxy_pass http://localhost:8080/$uri; + } + + location / { + root REPLACEME/dist; + index index.html index.htm; + try_files $uri $uri/ /index.html =404; + } + } +} diff --git a/cypress/integration/group1/checkerWorkflowFromWorkflow.ts b/cypress/integration/group1/checkerWorkflowFromWorkflow.ts index f41aba68e7..ded02134b6 100644 --- a/cypress/integration/group1/checkerWorkflowFromWorkflow.ts +++ b/cypress/integration/group1/checkerWorkflowFromWorkflow.ts @@ -55,14 +55,13 @@ describe('Checker workflow test from my-workflows', () => { cy.fixture('refreshedChecker').then((json) => { cy.route({ method: 'GET', - url: '/workflows/*/refresh', + url: '/api/workflows/*/refresh', response: json }); cy.get('#submitButton').click(); }); // Actions should be possible right after registering checker workflow - cy.get('#viewCheckerWorkflowButton').should('not.be.visible'); cy.get('#viewCheckerWorkflowButton').should('be.visible'); cy.get('#viewParentEntryButton').should('not.be.visible'); cy.get('#viewCheckerWorkflowButton').should('not.be.disabled').click(); diff --git a/cypress/integration/group1/curator.ts b/cypress/integration/group1/curator.ts index 7bf348a461..cac9e47609 100644 --- a/cypress/integration/group1/curator.ts +++ b/cypress/integration/group1/curator.ts @@ -21,7 +21,7 @@ describe('Curator UI', () => { cy.server(); const userObject = { id: 1, username: 'user_A', curator: true, isAdmin: false, name: 'user_A', setupComplete: false}; cy.route({ - url: '/users/user', + url: '*/users/user', method: 'GET', status: 200, response: userObject diff --git a/cypress/integration/group1/dropdown.ts b/cypress/integration/group1/dropdown.ts index d1acb9c9c1..45c6de7a00 100644 --- a/cypress/integration/group1/dropdown.ts +++ b/cypress/integration/group1/dropdown.ts @@ -32,7 +32,7 @@ describe('Dropdown test', () => { .server() .route({ method: 'GET', - url: '/users/user', + url: '*/users/user', response: { id: 4, username: 'user_curator', name: 'user_curator', curator: true, isAdmin: false } }); @@ -92,7 +92,7 @@ describe('Dropdown test', () => { .server() .route({ method: 'GET', - url: '/organizations/all?type=pending', + url: '*/organizations/all?type=pending', response: pendingOrganizations }); @@ -106,7 +106,7 @@ describe('Dropdown test', () => { .server() .route({ method: 'GET', - url: '/users/user/memberships', + url: '*/users/user/memberships', response: memberships }); // Choose dropdown @@ -122,7 +122,7 @@ describe('Dropdown test', () => { .server() .route({ method: 'POST', - url: '/organizations/1002/request', + url: '*/organizations/1002/request', response: { id: 1002, name: 'OrgThree', status: 'PENDING' } }); @@ -137,7 +137,7 @@ describe('Dropdown test', () => { .server() .route({ method: 'GET', - url: '/organizations/all?type=pending', + url: '*/organizations/all?type=pending', response: pendingOrganizations }); @@ -151,7 +151,7 @@ describe('Dropdown test', () => { .server() .route({ method: 'GET', - url: '/users/user/memberships', + url: '*/users/user/memberships', response: memberships }); @@ -180,7 +180,7 @@ describe('Dropdown test', () => { .server() .route({ method: 'GET', - url: '/organizations/all?type=pending', + url: '*/organizations/all?type=pending', response: pendingOrganizations }); @@ -189,7 +189,7 @@ describe('Dropdown test', () => { .server() .route({ method: 'POST', - url: '/organizations/1000/approve', + url: '*/organizations/1000/approve', response: [] }); @@ -213,7 +213,7 @@ describe('Dropdown test', () => { .server() .route({ method: 'POST', - url: '/organizations/1000/invitation?accept=true', + url: '*/organizations/1000/invitation?accept=true', response: [] }); @@ -226,7 +226,7 @@ describe('Dropdown test', () => { .server() .route({ method: 'GET', - url: '/users/user/memberships', + url: '*/users/user/memberships', response: memberships }); @@ -258,7 +258,7 @@ describe('Dropdown test', () => { .server() .route({ method: 'DELETE', - url: '/users/user', + url: '*/users/user', response: 'true' }); cy diff --git a/cypress/integration/group2/searchTable.ts b/cypress/integration/group2/searchTable.ts index 606d9bc634..6033ad6787 100644 --- a/cypress/integration/group2/searchTable.ts +++ b/cypress/integration/group2/searchTable.ts @@ -24,7 +24,7 @@ describe('Dockstore tool/workflow search table', () => { cy.server(); // Tools/worflows not starred in this response. cy.route({ - url: '/api/ga4gh/v2/extended/tools/entry/_search', + url: '*/api/ga4gh/v2/extended/tools/entry/_search', method: 'POST', status: 200, response: {"took":18,"timed_out":false,"_shards":{"total":5,"successful":5,"skipped":0,"failed":0},"hits":{"total":4,"max_score":1.0,"hits":[{"_index":"entry","_type":"tool","_id":"52","_score":1.0,"_source":{"tool_maintainer_email":"","aliases":{},"default_dockerfile_path":"/Dockerfile","is_published":true,"toolname":"cgpmap-cramOut","last_modified_date":null,"checker_id":null,"private_access":false,"descriptorType":["cwl"],"mode":"MANUAL_IMAGE_PATH","lastBuild":1518478819000,"lastUpdated":1518479742691,"path":"quay.io/garyluu/dockstore-cgpmap","defaultCWLTestParameterFile":"/examples/cgpmap/cramOut/fastq_gz_input.json","has_checker":false,"id":52,"last_modified":null,"email":null,"default_wdl_path":"/Dockstore.wdl","tool_path":"quay.io/garyluu/dockstore-cgpmap/cgpmap-cramOut","registry":"QUAY_IO","dbUpdateDate":null,"author":null,"registry_string":"quay.io","tags":[{"doiURL":null,"hidden":false,"workingDirectory":"cwls","versionEditor":null,"verifiedSource":null,"verified":false,"referenceType":"UNSET","commitID":null,"dockerfile_path":"/Dockerfile","doiStatus":"NOT_REQUESTED","reference":"3.0.0-rc8","valid":true,"wdl_path":"/Dockstore.wdl","automated":true,"size":138844180,"cwl_path":"/cwls/cgpmap-cramOut.cwl","name":"3.0.0-rc8","id":52,"image_id":"c387f22e65f066c42ccaf11392fdbd640aa2b7627eb40ac06a0dbaca2ca323cb","dirtyBit":false,"last_modified":1518479368000}],"dbCreateDate":null,"custom_docker_registry_path":"quay.io","default_cwl_path":"/cwls/cgpmap-cramOut.cwl","name":"dockstore-cgpmap","namespace":"garyluu","gitUrl":"git@github.com:garyluu/dockstore-cgpmap.git","defaultWDLTestParameterFile":"/test.wdl.json","defaultVersion":null}},{"_index":"entry","_type":"tool","_id":"5","_score":1.0,"_source":{"tool_maintainer_email":"","aliases":{},"default_dockerfile_path":"/Dockerfile","is_published":true,"toolname":null,"last_modified_date":null,"checker_id":null,"private_access":false,"descriptorType":["cwl"],"mode":"AUTO_DETECT_QUAY_TAGS_AUTOMATED_BUILDS","lastBuild":1465419996000,"lastUpdated":1480374043873,"path":"quay.io/A2/a","defaultCWLTestParameterFile":null,"has_checker":false,"id":5,"last_modified":null,"email":null,"default_wdl_path":"/Dockstore.wdl","tool_path":"quay.io/A2/a","registry":"QUAY_IO","dbUpdateDate":null,"author":null,"registry_string":"quay.io","tags":[{"doiURL":null,"hidden":false,"workingDirectory":"","versionEditor":null,"verifiedSource":null,"verified":false,"referenceType":"UNSET","commitID":null,"dockerfile_path":"/Dockerfile","doiStatus":"NOT_REQUESTED","reference":"master","valid":true,"wdl_path":"/Dockstore.wdl","automated":true,"size":44363874,"cwl_path":"/Dockstore.cwl","name":"latest","id":11,"image_id":"9227b87c1304b9ce746d06d0eb8144ec17a253f5b8e00a3922d86b538c8296c0","dirtyBit":false,"last_modified":1465420088000},{"doiURL":null,"hidden":false,"workingDirectory":"","versionEditor":null,"verifiedSource":null,"verified":false,"referenceType":"UNSET","commitID":null,"dockerfile_path":"/Dockerfile","doiStatus":"NOT_REQUESTED","reference":"master","valid":true,"wdl_path":"/Dockstore.wdl","automated":true,"size":44363874,"cwl_path":"/Dockstore.cwl","name":"master","id":10,"image_id":"9227b87c1304b9ce746d06d0eb8144ec17a253f5b8e00a3922d86b538c8296c0","dirtyBit":false,"last_modified":1465420088000}],"dbCreateDate":null,"custom_docker_registry_path":"quay.io","default_cwl_path":"/Dockstore.cwl","name":"a","namespace":"A2","gitUrl":"git@github.com:A2/a.git","defaultWDLTestParameterFile":null,"defaultVersion":null}},{"_index":"entry","_type":"tool","_id":"4","_score":1.0,"_source":{"tool_maintainer_email":"","aliases":{},"default_dockerfile_path":"/Dockerfile","is_published":true,"toolname":null,"last_modified_date":null,"checker_id":null,"private_access":false,"descriptorType":["wdl"],"mode":"AUTO_DETECT_QUAY_TAGS_AUTOMATED_BUILDS","lastBuild":1458081382000,"lastUpdated":1480374043873,"path":"quay.io/A2/b3","defaultCWLTestParameterFile":null,"has_checker":false,"id":4,"last_modified":null,"email":null,"default_wdl_path":"/Dockstore.wdl","tool_path":"quay.io/A2/b3","registry":"QUAY_IO","dbUpdateDate":null,"author":null,"registry_string":"quay.io","tags":[{"doiURL":null,"hidden":false,"workingDirectory":"","versionEditor":null,"verifiedSource":null,"verified":false,"referenceType":"UNSET","commitID":null,"dockerfile_path":"/Dockerfile","doiStatus":"NOT_REQUESTED","reference":"master","valid":true,"wdl_path":"/Dockstore.wdl","automated":true,"size":108722128,"cwl_path":"/Dockstore.cwl","name":"latest","id":9,"image_id":"f92aa8edcc265e4d5faabf7f89157008d52d514f8f6d7c1b833024f58f126e9d","dirtyBit":false,"last_modified":1458081725000},{"doiURL":null,"hidden":true,"workingDirectory":"","versionEditor":null,"verifiedSource":null,"verified":false,"referenceType":"UNSET","commitID":null,"dockerfile_path":"/Dockerfile","doiStatus":"NOT_REQUESTED","reference":"master","valid":true,"wdl_path":"/Dockstore.wdl","automated":true,"size":108722128,"cwl_path":"/Dockstore.cwl","name":"master","id":8,"image_id":"f92aa8edcc265e4d5faabf7f89157008d52d514f8f6d7c1b833024f58f126e9d","dirtyBit":false,"last_modified":1458081724000}],"dbCreateDate":null,"custom_docker_registry_path":"quay.io","default_cwl_path":"/Dockstore.cwl","name":"b3","namespace":"A2","gitUrl":"git@github.com:A2/b3.git","defaultWDLTestParameterFile":null,"defaultVersion":null}},{"_index":"entry","_type":"workflow","_id":"11","_score":1.0,"_source":{"aliases":{},"is_published":true,"last_modified_date":null,"is_checker":false,"checker_id":null,"repository":"l","source_control_provider":"GITHUB","descriptorType":"cwl","full_workflow_path":"github.com/A/l","mode":"FULL","lastUpdated":1480374057688,"path":"github.com/A/l","workflowVersions":[{"doiURL":null,"verifiedSource":null,"versionEditor":null,"verified":false,"referenceType":"UNSET","commitID":null,"id":13,"doiStatus":"NOT_REQUESTED"},{"doiURL":null,"verifiedSource":null,"versionEditor":null,"verified":false,"referenceType":"UNSET","commitID":null,"id":14,"doiStatus":"NOT_REQUESTED"}],"sourceControl":"github.com","has_checker":false,"id":11,"last_modified":null,"email":null,"dbUpdateDate":null,"author":null,"defaultTestParameterFilePath":null,"workflowName":null,"workflow_path":"/1st-workflow.cwl","dbCreateDate":null,"parent_id":null,"organization":"A","gitUrl":"git@github.com:A/l.git","defaultVersion":null}}]}} @@ -44,7 +44,7 @@ describe('Dockstore tool/workflow search table', () => { // First tool and workflow starred cy.fixture('searchTableResponse').then((json) => { cy.route({ - url: '/api/ga4gh/v2/extended/tools/entry/_search', + url: '*/api/ga4gh/v2/extended/tools/entry/_search', method: 'POST', response: json }); @@ -79,7 +79,7 @@ describe('search table items per page', () => { cy.server(); cy.fixture('searchTableResponse').then((json) => { cy.route({ - url: '/api/ga4gh/v2/extended/tools/entry/_search', + url: '*/api/ga4gh/v2/extended/tools/entry/_search', method: 'POST', response: json }); diff --git a/cypress/integration/group3/organizations.ts b/cypress/integration/group3/organizations.ts index 9b96ed6796..3e8b96ec2e 100644 --- a/cypress/integration/group3/organizations.ts +++ b/cypress/integration/group3/organizations.ts @@ -159,7 +159,7 @@ describe('Dockstore Organizations', () => { .server() .route({ method: 'GET', - url: '/users/user/memberships', + url: '*/users/user/memberships', response: memberships }); }); @@ -226,6 +226,7 @@ describe('Dockstore Organizations', () => { cy.get('#accept-remove-entry-from-org').click(); cy.contains('This collection has no associated entries'); cy.visit('/organizations/Potatoe'); + cy.contains('Members').should('be.visible'); }); }); @@ -291,7 +292,7 @@ describe('Dockstore Organizations', () => { it('organization alias', () => { cy.server(); cy.route({ - url: '/organizations/fakeAlias/aliases', + url: '*/organizations/fakeAlias/aliases', method: 'GET', status: 200, response: { 'name': 'Potatoe' } @@ -303,7 +304,7 @@ describe('Dockstore Organizations', () => { it('collection alias', () => { cy.server(); cy.route({ - url: '/organizations/collections/fakeAlias/aliases', + url: '*/organizations/collections/fakeAlias/aliases', method: 'GET', status: 200, response: { 'organizationName': 'Potatoe', 'name': 'veryFakeCollectionName' } @@ -322,7 +323,7 @@ describe('Dockstore Organizations', () => { it('organization alias incorrect', () => { cy.server(); cy.route({ - url: '/organizations/incorrectAlias/aliases', + url: '*/organizations/incorrectAlias/aliases', method: 'GET', status: 404, response: {} @@ -335,7 +336,7 @@ describe('Dockstore Organizations', () => { it('collection alias incorrect', () => { cy.server(); cy.route({ - url: '/organizations/collections/incorrectAlias/aliases', + url: '*/organizations/collections/incorrectAlias/aliases', method: 'GET', status: 404, response: {} diff --git a/cypress/integration/group3/sharedWorkflows.ts b/cypress/integration/group3/sharedWorkflows.ts index daa1dbf612..1e73c6a41e 100644 --- a/cypress/integration/group3/sharedWorkflows.ts +++ b/cypress/integration/group3/sharedWorkflows.ts @@ -61,21 +61,21 @@ describe('Shared with me workflow test from my-workflows', () => { cy .route({ method: 'GET', - url: '*workflows/200*', + url: '*/workflows/200*', response: readerWorkflow }).as('getReaderWorkflow'); cy .route({ method: 'GET', - url: '*workflows/201*', + url: '*/workflows/201*', response: writerWorkflow }).as('getWriterWorkflow'); cy .route({ method: 'GET', - url: '*workflows/202*', + url: '*/workflows/202*', response: ownerWorkflow }).as('getOwnerWorkflow'); diff --git a/cypress/integration/group3/starErrorMessage.ts b/cypress/integration/group3/starErrorMessage.ts index 08c4885bd3..d423289c30 100644 --- a/cypress/integration/group3/starErrorMessage.ts +++ b/cypress/integration/group3/starErrorMessage.ts @@ -99,28 +99,28 @@ describe('Tool and workflow starring error messages', () => { describe('Workflow starring error message', () => { it('Workflow server error message', () => { - starringServerError('/workflows/github.com/A/l', '/workflows/11/star'); + starringServerError('/workflows/github.com/A/l', '*/workflows/11/star'); }) it('Workflow cannot be starred if not already unstarred.', () => { - starringError('/workflows/github.com/A/l', 'workflow', '/workflows/11/star', 'github.com/A/l'); + starringError('/workflows/github.com/A/l', 'workflow', '*/workflows/11/star', 'github.com/A/l'); }); it('Workflow cannot be unstarred if not already starred.', () => { - unstarringError('/workflows/github.com/A/l', 'workflow', '/workflows/11/unstar', 'github.com/A/l'); + unstarringError('/workflows/github.com/A/l', 'workflow', '*/workflows/11/unstar', 'github.com/A/l'); }); }); describe('Tool starring error message', () =>{ it('Tool server error message', () => { - starringServerError('/containers/quay.io/A2/a:latest?tab=info', '/containers/5/star'); + starringServerError('/containers/quay.io/A2/a:latest?tab=info', '*/containers/5/star'); }) it('Tool cannot be starred if not already unstarred.', () => { - starringError('/containers/quay.io/A2/a:latest?tab=info', 'tool', '/containers/5/star', 'quay.io/A2/a'); + starringError('/containers/quay.io/A2/a:latest?tab=info', 'tool', '*/containers/5/star', 'quay.io/A2/a'); }) it('Tool cannot be unstarred if not already starred.', () => { - unstarringError('/containers/quay.io/A2/a:latest?tab=info', 'tool', '/containers/5/unstar', 'quay.io/A2/a'); + unstarringError('/containers/quay.io/A2/a:latest?tab=info', 'tool', '*/containers/5/unstar', 'quay.io/A2/a'); }); }) }); diff --git a/package.json b/package.json index 80f7e3c8f7..9adf802c55 100644 --- a/package.json +++ b/package.json @@ -7,7 +7,7 @@ }, "scripts": { "ng": "npx ng", - "start": "npx ng serve --progress=false", + "start": "npx ng serve --progress=false --proxy-config=proxy.conf.json", "webservice": "./scripts/run-webservice-script.sh", "prebuild": "ts-node git.version.ts && ./scripts/generate-openapi-script.sh", "prebuild.prod": "npm run prebuild", diff --git a/proxy.conf.json b/proxy.conf.json new file mode 100644 index 0000000000..4cb3affde6 --- /dev/null +++ b/proxy.conf.json @@ -0,0 +1,11 @@ +{ + "/api": { + "target": "http://localhost:8080", + "pathRewrite": { + "^/api" : "" + } + }, + "/swagger.json": { + "target": "http://localhost:8080" + } +} diff --git a/scripts/run-travis-script.sh b/scripts/run-travis-script.sh index b0287452af..24306e4cf9 100755 --- a/scripts/run-travis-script.sh +++ b/scripts/run-travis-script.sh @@ -8,4 +8,4 @@ npm run webservice npm run build npm run start & ./scripts/wait-for.sh -cypress run --record --config defaultCommandTimeout=10000 --spec ${TEST} +npx cypress run --record --config defaultCommandTimeout=10000 --spec ${TEST} diff --git a/src/app/app.module.ts b/src/app/app.module.ts index bc85c207c6..34a4853389 100644 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { NgModule } from '@angular/core'; +import { APP_INITIALIZER, NgModule } from '@angular/core'; import { FlexLayoutModule } from '@angular/flex-layout'; import { FormsModule, ReactiveFormsModule } from '@angular/forms'; import { @@ -46,9 +46,7 @@ import { LoginComponent } from './login/login.component'; import { LoginService } from './login/login.service'; import { AccountsComponent } from './loginComponents/accounts/accounts.component'; import { ControlsComponent } from './loginComponents/accounts/controls/controls.component'; -import { - DeleteAccountDialogComponent, -} from './loginComponents/accounts/controls/delete-account-dialog/delete-account-dialog.component'; +import { DeleteAccountDialogComponent, } from './loginComponents/accounts/controls/delete-account-dialog/delete-account-dialog.component'; import { AccountsExternalComponent } from './loginComponents/accounts/external/accounts.component'; import { AccountsService } from './loginComponents/accounts/external/accounts.service'; import { GetTokenContentPipe } from './loginComponents/accounts/external/getTokenContent.pipe'; @@ -68,7 +66,6 @@ import { RefreshAlertModule } from './shared/alert/alert.module'; import { AuthConfig } from './shared/auth.model'; import { ContainerService } from './shared/container.service'; import { DateService } from './shared/date.service'; -import { Dockstore } from './shared/dockstore.model'; import { DockstoreService } from './shared/dockstore.service'; import { DescriptorLanguageService } from './shared/entry/descriptor-language.service'; import { RegisterCheckerWorkflowService } from './shared/entry/register-checker-workflow/register-checker-workflow.service'; @@ -101,6 +98,8 @@ import { SitemapComponent } from './sitemap/sitemap.component'; import { RequestsModule } from './loginComponents/requests.module'; import {OrganizationStarringModule} from './organizations/organization/organization-starring/organization-starring.module'; import {OrganizationStargazersModule} from './organizations/organization/organization-stargazers/organization-stargazers.module'; +import { ConfigurationService } from './configuration.service'; +import { HttpClient } from '@angular/common/http'; export const myCustomTooltipDefaults: MatTooltipDefaultOptions = { showDelay: 500, @@ -114,6 +113,10 @@ export const myCustomSnackbarDefaults: MatSnackBarConfig = { verticalPosition: 'bottom' }; +export function configurationServiceFactory(configurationService: ConfigurationService): Function { + return () => configurationService.load(); +} + @NgModule({ declarations: [ AppComponent, @@ -186,6 +189,7 @@ export const myCustomSnackbarDefaults: MatSnackBarConfig = { ProviderService, ContainerService, ImageProviderService, + HttpClient, CLIENT_ROUTER_PROVIDERS, RegisterCheckerWorkflowService, RefreshService, @@ -199,6 +203,13 @@ export const myCustomSnackbarDefaults: MatSnackBarConfig = { ExtendedToolsService, VerifiedByService, Title, + ConfigurationService, + { + provide: APP_INITIALIZER, + useFactory: configurationServiceFactory, + deps: [ ConfigurationService ], + multi: true + }, { provide: MAT_TOOLTIP_DEFAULT_OPTIONS, useValue: myCustomTooltipDefaults}, { provide: MAT_SNACK_BAR_DEFAULT_OPTIONS, useValue: myCustomSnackbarDefaults} ], @@ -210,7 +221,7 @@ export class AppModule { export const apiConfig = new Configuration({ apiKeys: {}, - basePath: Dockstore.API_URI + basePath: window.location.protocol + '//' + window.location.host + '/api' }); export function getApiConfig() { diff --git a/src/app/configuration.service.spec.ts b/src/app/configuration.service.spec.ts new file mode 100644 index 0000000000..255351d26a --- /dev/null +++ b/src/app/configuration.service.spec.ts @@ -0,0 +1,19 @@ +import { inject, TestBed } from '@angular/core/testing'; + +import { ConfigurationService } from './configuration.service'; +import { HttpClientTestingModule } from '@angular/common/http/testing'; +import { ConfigService } from 'ng2-ui-auth'; + +describe('ConfigurationService', () => { + beforeEach(() => TestBed.configureTestingModule({ + imports: [HttpClientTestingModule], + providers: [ + ConfigurationService, + { provide: ConfigService, useValue: {}} + ] + })); + + it('should be created', inject([ConfigurationService], (service: ConfigurationService) => { + expect(service).toBeTruthy(); + })); +}); diff --git a/src/app/configuration.service.ts b/src/app/configuration.service.ts new file mode 100644 index 0000000000..dde9e6185c --- /dev/null +++ b/src/app/configuration.service.ts @@ -0,0 +1,76 @@ +import { Injectable } from '@angular/core'; +import { Config, MetadataService } from './shared/swagger'; +import { Dockstore } from './shared/dockstore.model'; +import { ConfigService } from 'ng2-ui-auth'; +import { AuthConfig } from './shared/auth.model'; + +@Injectable({ + providedIn: 'root' +}) +export class ConfigurationService { + + constructor(private metadataService: MetadataService, private configService: ConfigService) { + } + + load(): Promise { + + return this.metadataService.getConfig().toPromise().then((config: Config) => { + + this.updateDockstoreModel(config); + + this.updateAuthProviders(); + }, + (e) => { + console.error('Error downloading config.json', e); + // Less than ideal, but just let the normal error handling in footer.component.ts kick in later. + Promise.resolve(); + }); + } + + private updateDockstoreModel(config: Config) { + Dockstore.DISCOURSE_URL = config.discourseUrl; + + Dockstore.DNASTACK_IMPORT_URL = config.dnaStackImportUrl; + Dockstore.FIRECLOUD_IMPORT_URL = config.fireCloudImportUrl; + Dockstore.DNANEXUS_IMPORT_URL = config.dnaNexusImportUrl; + Dockstore.TERRA_IMPORT_URL = config.terraImportUrl; + + Dockstore.GITHUB_CLIENT_ID = config.githubClientId; + Dockstore.GITHUB_AUTH_URL = config.gitHubAuthUrl; + + Dockstore.GITHUB_REDIRECT_URI = Dockstore.HOSTNAME + config.gitHubRedirectPath; + Dockstore.GITHUB_SCOPE = config.gitHubScope; + + Dockstore.QUAYIO_AUTH_URL = config.quayIoAuthUrl; + Dockstore.QUAYIO_REDIRECT_URI = Dockstore.HOSTNAME + config.quayIoRedirectPath; + Dockstore.QUAYIO_SCOPE = config.quayIoScope; + Dockstore.QUAYIO_CLIENT_ID = config.quayIoClientId; + + Dockstore.BITBUCKET_AUTH_URL = config.bitBucketAuthUrl; + Dockstore.BITBUCKET_CLIENT_ID = config.bitBucketClientId; + + Dockstore.GITLAB_AUTH_URL = config.gitlabAuthUrl; + Dockstore.GITLAB_CLIENT_ID = config.gitlabClientId; + Dockstore.GITLAB_REDIRECT_URI = Dockstore.HOSTNAME + config.gitlabRedirectPath; + Dockstore.GITLAB_SCOPE = config.gitlabScope; + + Dockstore.GOOGLE_CLIENT_ID = config.googleClientId; + Dockstore.GOOGLE_SCOPE = config.googleScope; + + Dockstore.CWL_VISUALIZER_URI = config.cwlVisualizerUri; + + Dockstore.FEATURES.enableLaunchWithFireCloud = config.enableLaunchWithFireCloud; + } + + + /** + * In app.module.ts, the line `Ng2UiAuthModule.forRoot(AuthConfig)` in the imports section, initializes + * the auth providers before the code in this file to fetch and set the configuration information has + * executed. Update the providers here. + */ + private updateAuthProviders() { + AuthConfig.providers.github.clientId = Dockstore.GITHUB_CLIENT_ID; + AuthConfig.providers.google.clientId = Dockstore.GOOGLE_CLIENT_ID; + this.configService.updateProviders(AuthConfig.providers); + } +} diff --git a/src/app/footer/footer.component.ts b/src/app/footer/footer.component.ts index 1451a9d88f..7dbe1ddc1b 100644 --- a/src/app/footer/footer.component.ts +++ b/src/app/footer/footer.component.ts @@ -51,7 +51,8 @@ export class FooterComponent extends Base implements OnInit { } }, (error: HttpErrorResponse) => { console.log(error); - if (error.status === 0 && window.location.pathname !== '/maintenance') { + // Angular proxy returns 504 + if ((error.status === 0 || error.status === 504) && window.location.pathname !== '/maintenance') { window.location.href = '/maintenance'; } } diff --git a/src/app/shared/dockstore.model.ts b/src/app/shared/dockstore.model.ts index f740150a02..59d1d01f50 100644 --- a/src/app/shared/dockstore.model.ts +++ b/src/app/shared/dockstore.model.ts @@ -20,49 +20,47 @@ import { Provider } from './enum/provider.enum'; // dockstore.model.ts.template at https://github.com/dockstore/compose_setup export class Dockstore { - // Please fill in HOSTNAME with your address - static readonly HOSTNAME = 'http://localhost'; - static readonly API_PORT = '8080'; - static readonly UI_PORT = '4200'; + static readonly HOSTNAME = window.location.protocol + '//' + window.location.host; + static readonly API_URI = Dockstore.HOSTNAME + '/api'; + + // All the following properties will get updated by configuration.service.ts. You do not + // need to update them here. Set them in your dockstore.yml for the web service. // Discourse URL MUST end with a slash (/) - static readonly DISCOURSE_URL = 'http://localhost/'; + static DISCOURSE_URL = 'http://localhost/'; - static readonly LOCAL_URI = Dockstore.HOSTNAME + ':' + Dockstore.UI_PORT; - // @ts-ignore - static readonly API_URI = Dockstore.HOSTNAME + (Dockstore.API_PORT !== '443' ? (':' + Dockstore.API_PORT) : ''); - static readonly DNASTACK_IMPORT_URL = 'https://app.dnastack.com/#/app/workflow/import/dockstore'; - static readonly FIRECLOUD_IMPORT_URL = 'https://portal.firecloud.org/#import/dockstore'; - static readonly DNANEXUS_IMPORT_URL = 'https://platform.dnanexus.com/panx/tools/import-workflow'; - static readonly TERRA_IMPORT_URL = 'https://app.terra.bio/#import-tool/dockstore'; + static DNASTACK_IMPORT_URL = 'https://app.dnastack.com/#/app/workflow/import/dockstore'; + static FIRECLOUD_IMPORT_URL = 'https://portal.firecloud.org/#import/dockstore'; + static DNANEXUS_IMPORT_URL = 'https://platform.dnanexus.com/panx/tools/import-workflow'; + static TERRA_IMPORT_URL = 'https://app.terra.bio/#import-tool/dockstore'; - static readonly GITHUB_CLIENT_ID = 'fill_this_in'; - static readonly GITHUB_AUTH_URL = 'https://github.com/login/oauth/authorize'; + static GITHUB_CLIENT_ID = 'fill_this_in'; + static GITHUB_AUTH_URL = 'https://github.com/login/oauth/authorize'; - static readonly GITHUB_REDIRECT_URI = Dockstore.LOCAL_URI + '/auth/' + Provider.GITHUB; - static readonly GITHUB_SCOPE = 'read:org,user:email'; + static GITHUB_REDIRECT_URI = Dockstore.HOSTNAME + '/auth/' + Provider.GITHUB; + static GITHUB_SCOPE = 'read:org,user:email'; - static readonly QUAYIO_AUTH_URL = 'https://quay.io/oauth/authorize'; - static readonly QUAYIO_REDIRECT_URI = Dockstore.LOCAL_URI + '/auth/' + Provider.QUAY; - static readonly QUAYIO_SCOPE = 'repo:read,user:read'; - static readonly QUAYIO_CLIENT_ID = 'fill_this_in'; + static QUAYIO_AUTH_URL = 'https://quay.io/oauth/authorize'; + static QUAYIO_REDIRECT_URI = Dockstore.HOSTNAME + '/auth/' + Provider.QUAY; + static QUAYIO_SCOPE = 'repo:read,user:read'; + static QUAYIO_CLIENT_ID = 'fill_this_in'; - static readonly BITBUCKET_AUTH_URL = 'https://bitbucket.org/site/oauth2/authorize'; - static readonly BITBUCKET_CLIENT_ID = 'fill_this_in'; + static BITBUCKET_AUTH_URL = 'https://bitbucket.org/site/oauth2/authorize'; + static BITBUCKET_CLIENT_ID = 'fill_this_in'; - static readonly GITLAB_AUTH_URL = 'https://gitlab.com/oauth/authorize'; - static readonly GITLAB_CLIENT_ID = 'fill_this_in'; - static readonly GITLAB_REDIRECT_URI = Dockstore.LOCAL_URI + '/auth/' + Provider.GITLAB; + static GITLAB_AUTH_URL = 'https://gitlab.com/oauth/authorize'; + static GITLAB_CLIENT_ID = 'fill_this_in'; + static GITLAB_REDIRECT_URI = Dockstore.HOSTNAME + '/auth/' + Provider.GITLAB; // getting ready for gitlab scopes, this seems to request a token with the correct scope but it doesn't work to retrieve membership - // static readonly GITLAB_SCOPE = 'read_user openid'; - static readonly GITLAB_SCOPE = 'api'; + // static GITLAB_SCOPE = 'read_user openid'; + static GITLAB_SCOPE = 'api'; - static readonly GOOGLE_CLIENT_ID = 'fill_this_in'; - static readonly GOOGLE_SCOPE = 'profile email'; + static GOOGLE_CLIENT_ID = 'fill_this_in'; + static GOOGLE_SCOPE = 'profile email'; - static readonly CWL_VISUALIZER_URI = 'https://view.commonwl.org'; + static CWL_VISUALIZER_URI = 'https://view.commonwl.org'; - static readonly FEATURES = { + static FEATURES = { enableCwlViewer: true, enableLaunchWithFireCloud: true }; diff --git a/src/app/workflow/launch-third-party/state/descriptors.service.spec.ts b/src/app/workflow/launch-third-party/state/descriptors.service.spec.ts index 9f119c10e5..6ffed8aa9d 100644 --- a/src/app/workflow/launch-third-party/state/descriptors.service.spec.ts +++ b/src/app/workflow/launch-third-party/state/descriptors.service.spec.ts @@ -2,6 +2,7 @@ import { inject, TestBed } from '@angular/core/testing'; import { DescriptorsService } from './descriptors.service'; import { DescriptorsStore } from './descriptors-store.'; +import { Dockstore } from '../../../shared/dockstore.model'; describe('DescriptorsService', () => { beforeEach(() => { @@ -22,6 +23,6 @@ describe('DescriptorsService', () => { it('should generate correct TRS url', inject([DescriptorsService], (service: DescriptorsService) => { expect(service.trsUrl(path, version)) // tslint:disable:max-line-length - .toEqual('http://localhost:8080/api/ga4gh/v2/tools/%23workflow%2Fgithub.com%2Fgatk-workflows%2Fgatk4-germline-snps-indels/versions/1.0.1'); + .toEqual(`${Dockstore.API_URI}/api/ga4gh/v2/tools/%23workflow%2Fgithub.com%2Fgatk-workflows%2Fgatk4-germline-snps-indels/versions/1.0.1`); })); }); diff --git a/travisci/web.yml b/travisci/web.yml index 9af9f42673..6b4b39af85 100644 --- a/travisci/web.yml +++ b/travisci/web.yml @@ -18,7 +18,7 @@ discourseUrl: authorizerType: inmemory externalConfig: - basePath: / + basePath: /api/ hostname: localhost scheme: http port: 8080