Merge pull request #22 from sartography/dev

Dev
This commit is contained in:
Aaron Louie 2020-05-17 13:34:22 -04:00 committed by GitHub
commit 6e88159814
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
25 changed files with 172 additions and 65 deletions

View File

@ -14,6 +14,15 @@ services:
- docker
- xvfb
before_install:
- |
branches=('dev' 'testing' 'demo' 'training' 'staging')
if [[ " ${arr[@]} " =~ " ${TRAVIS_BRANCH} " ]]; then
export E2E_TAG="$TRAVIS_BRANCH"
else
export E2E_TAG="latest"
fi
install:
- npm install
@ -29,7 +38,7 @@ deploy:
skip_cleanup: true
on:
all_branches: true
condition: $TRAVIS_BRANCH =~ ^(testing|staging|master)$
condition: $TRAVIS_BRANCH =~ ^(dev|testing|demo|training|staging|master|rrt\/.*)$
notifications:
email:

View File

@ -17,9 +17,13 @@ RUN npm install && \
FROM nginx
COPY --from=builder /crc-bpmn/dist/* /usr/share/nginx/html/
COPY --from=builder /crc-bpmn/nginx.conf /etc/nginx/conf.d/default.conf
# Script for substituting environment variables
COPY ./docker/substitute-env-variables.sh ./entrypoint.sh
RUN chmod +x ./entrypoint.sh
ENTRYPOINT ["./entrypoint.sh", "/usr/share/nginx/html/index.html"]
# Substitute environment variables in nginx configuration and index.html
ENTRYPOINT ["./entrypoint.sh", "/usr/share/nginx/html/index.html,/etc/nginx/conf.d/default.conf"]
### STAGE 3: Profit! ###
CMD ["nginx", "-g", "daemon off;"]

View File

@ -4,23 +4,45 @@
pip install --user awscli;
export PATH=$PATH:$HOME/.local/bin;
function branch_to_tag () {
if [ "$1" == "latest" ]; then echo "production"; else echo "$1" ; fi
}
function branch_to_deploy_group() {
if [[ $1 =~ ^(rrt\/.*)$ ]]; then echo "rrt"; else echo "crconnect" ; fi
}
function branch_to_deploy_stage () {
if [ "$1" == "master" ]; then echo "production"; else echo "$1" ; fi
}
REPO="sartography/cr-connect-bpmn"
TAG=$(branch_to_tag "$TRAVIS_BRANCH")
DEPLOY_APP="bpmn"
DEPLOY_GROUP=$(branch_to_deploy_group "$TRAVIS_BRANCH")
DEPLOY_STAGE=$(branch_to_deploy_stage "$TRAVIS_BRANCH")
if [ "$DEPLOY_GROUP" == "rrt" ]; then
IFS='/' read -ra ARR <<< "$TRAVIS_BRANCH" # Split branch on '/' character
TAG=$(branch_to_tag "rrt_${ARR[1]}")
DEPLOY_STAGE=$(branch_to_deploy_stage "${ARR[1]}")
fi
DEPLOY_PATH="$DEPLOY_GROUP/$DEPLOY_STAGE/$DEPLOY_APP"
echo "REPO = $REPO"
echo "TAG = $TAG"
echo "DEPLOY_PATH = $DEPLOY_PATH"
# Build and push Docker image to Docker Hub
echo "$DOCKER_TOKEN" | docker login -u "$DOCKER_USERNAME" --password-stdin || exit 1
REPO="sartography/cr-connect-bpmn"
TAG=$(if [ "$TRAVIS_BRANCH" == "master" ]; then echo "latest"; else echo "$TRAVIS_BRANCH" ; fi)
COMMIT=${TRAVIS_COMMIT::8}
docker build -f Dockerfile -t "$REPO:$COMMIT" . || exit 1
docker tag "$REPO:$COMMIT" "$REPO:$TAG" || exit 1
docker tag "$REPO:$COMMIT" "$REPO:travis-$TRAVIS_BUILD_NUMBER" || exit 1
docker build -f Dockerfile -t "$REPO:$TAG" . || exit 1
docker push "$REPO" || exit 1
# Wait for Docker Hub
echo "Publishing to Docker Hub..."
sleep 30
# Notify DC/OS that Docker image has been updated
# Notify UVA DCOS that Docker image has been updated
echo "Refreshing DC/OS..."
STAGE=$(if [ "$TRAVIS_BRANCH" == "master" ]; then echo "production"; else echo "$TRAVIS_BRANCH" ; fi)
echo "STAGE = $STAGE"
aws sqs send-message --region "$AWS_DEFAULT_REGION" --queue-url "$AWS_SQS_URL" --message-body "crconnect/$STAGE/bpmn" || exit 1
aws sqs send-message --region "$AWS_DEFAULT_REGION" --queue-url "$AWS_SQS_URL" --message-body "$DEPLOY_PATH" || exit 1

View File

@ -20,19 +20,20 @@ services:
container_name: backend
depends_on:
- db
image: sartography/cr-connect-workflow
image: sartography/cr-connect-workflow:$E2E_TAG
volumes:
- ./flask-config/config.py:/crc-workflow/instance/config.py
environment:
- FLASK_APP=./crc/__init__.py
- RESET_DB=true
- LDAP_URL=ldap
ports:
- "5000:5000"
command: ./wait-for-it.sh db:5432 -t 0 -- ./docker_run.sh
pb:
container_name: pb
image: sartography/protocol-builder-mock
image: sartography/protocol-builder-mock:$E2E_TAG
volumes:
- ./flask-config/config_pb.py:/protocol-builder-mock/instance/config.py
environment:
@ -41,3 +42,8 @@ services:
- "5001:5001"
command: ./wait-for-it.sh db:5432 -t 0 -- ./docker_run.sh
ldap:
container_name: ldap
image: tuxmonteiro/ldap-mock
ports:
- "3890"

View File

@ -2,8 +2,13 @@ import os
basedir = os.path.abspath(os.path.dirname(__file__))
NAME = "CR Connect Workflow"
CORS_ENABLED = False
DEVELOPMENT = True
SQLALCHEMY_DATABASE_URI = "postgresql://crc_user:crc_pass@db:5432/crc_dev"
# %s/%i placeholders expected for uva_id and study_id in various calls.
PB_USER_STUDIES_URL = "http://pb:5001/pb/user_studies?uva_id=%s"
PB_INVESTIGATORS_URL = "http://pb:5001/pb/investigators?studyid=%i"
PB_REQUIRED_DOCS_URL = "http://pb:5001/pb/required_docs?studyid=%i"
PB_STUDY_DETAILS_URL = "http://pb:5001/pb/study?studyid=%i"
print('\n\n*** USING INSTANCE CONFIG ***\n\n')

23
docker/substitute-env-variables.sh Normal file → Executable file
View File

@ -1,18 +1,25 @@
#!/bin/bash
# The first parameter is the path to the file which should be substituted
echo 'Substituting environment variables...'
# The first parameter is a comma-delimited list of paths to files which should be substituted
if [[ -z $1 ]]; then
echo 'ERROR: No target file given.'
echo 'ERROR: No target files given.'
exit 1
fi
# Replace strings in the given file that have the format ${ENV_VAR}
envsubst '\$PRODUCTION \$API_URL \$IRB_URL' < "$1" > "$1".tmp && mv "$1".tmp "$1"
env_list='\$PRODUCTION \$API_URL \$IRB_URL \$HOME_ROUTE \$PORT0'
for file_path in $(echo $1 | sed "s/,/ /g")
do
echo "replacing $env_list in $file_path"
# Set DEBUG=true in order to log the replaced file
if [ "$DEBUG" = true ] ; then
exec cat $1
fi
# Replace strings in the given file(s) in env_list
envsubst "$env_list" < "$file_path" > "$file_path".tmp && mv "$file_path".tmp "$file_path"
echo '...'
done
echo 'Finished substituting environment variables.'
# Execute all other commands with parameters
exec "${@:2}"

View File

@ -13,7 +13,7 @@ describe('workspace-project App', () => {
});
it('should click sign-in and navigate to home screen', () => {
page.clickAndExpectRoute('#sign_in', '/');
page.clickAndExpectRoute('#sign_in', '/home');
expect(page.getElements('app-workflow-spec-list').count()).toBeGreaterThan(0);
expect(page.getElements('app-file-list').count()).toBeGreaterThan(0);
});

View File

@ -1,5 +1,5 @@
server {
listen 80;
listen $PORT0;
location / {
root /usr/share/nginx/html;

6
package-lock.json generated
View File

@ -12291,9 +12291,9 @@
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="
},
"sartography-workflow-lib": {
"version": "0.0.147",
"resolved": "https://registry.npmjs.org/sartography-workflow-lib/-/sartography-workflow-lib-0.0.147.tgz",
"integrity": "sha512-CaueuOhiZgYmp4NjPsKt9zad3yAqzT/ibnIqC+sIQHt99UKmSzsaFuuDN2l5ux4E/Xe4D33Ew6djXtrgcGROTw=="
"version": "0.0.164",
"resolved": "https://registry.npmjs.org/sartography-workflow-lib/-/sartography-workflow-lib-0.0.164.tgz",
"integrity": "sha512-0sbIR8gkXfPOJOJzHQyzzo7mIS9W8bLiQW/pzp/RWATj0j+MGn9z6aPKQBs6PKUP0KMzAOXfXywkR3e3aPJSoA=="
},
"sass": {
"version": "1.23.3",

View File

@ -13,11 +13,14 @@
"lint": "ng lint",
"e2e": "./node_modules/protractor/bin/webdriver-manager update && ng e2e",
"e2e:with-wf": "npm run e2e-wf && ng e2e && npm run e2e-wf:stop && npm run e2e-wf:clean",
"e2e-wf:stop": "docker stop db || true && docker stop backend || true",
"e2e-wf:stop": "docker stop db || true && docker stop backend || true && docker stop pb || true",
"e2e-wf:clean": "docker system prune -f && cd docker && docker-compose rm -f -v -s && cd ..",
"e2e-wf:build": "cd docker && docker-compose pull && docker-compose build --no-cache && cd ..",
"e2e-wf:start": "cd docker && docker-compose up -d && cd ..",
"e2e-wf": "npm run e2e-wf:stop && npm run e2e-wf:clean && npm run e2e-wf:build && npm run e2e-wf:start",
"e2e-wf:db-upgrade": "docker exec -it backend pipenv run flask db upgrade",
"e2e-wf:db-setup": "docker exec -it backend pipenv run flask load-example-data",
"e2e-wf:pb-setup": "docker exec -it pb pipenv run flask db upgrade",
"e2e-wf": "npm run e2e-wf:stop && npm run e2e-wf:clean && npm run e2e-wf:build && npm run e2e-wf:start && npm run e2e-wf:db-upgrade && npm run e2e-wf:db-setup && npm run e2e-wf:pb-setup",
"ci": "npm run lint && npm run test:coverage && npm run e2e:with-wf && sonar-scanner"
},
"private": true,
@ -49,7 +52,7 @@
"ngx-file-drop": "^8.0.8",
"ngx-markdown": "^9.0.0",
"rxjs": "~6.5.4",
"sartography-workflow-lib": "^0.0.147",
"sartography-workflow-lib": "^0.0.164",
"tslib": "^1.11.1",
"uuid": "^7.0.2",
"zone.js": "^0.10.3"

View File

@ -1,6 +1,7 @@
import {NgModule} from '@angular/core';
import {RouterModule, Routes} from '@angular/router';
import {SessionRedirectComponent} from 'sartography-workflow-lib';
import {environment} from '../environments/environment.runtime';
import {HomeComponent} from './home/home.component';
import {ModelerComponent} from './modeler/modeler.component';
import {SignInComponent} from './sign-in/sign-in.component';
@ -10,6 +11,11 @@ import {SignOutComponent} from './sign-out/sign-out.component';
const routes: Routes = [
{
path: '',
pathMatch: 'full',
redirectTo: environment.homeRoute,
},
{
path: 'home',
component: HomeComponent
},
{

View File

@ -1,5 +1,5 @@
<div class="mat-typography">
<app-navbar *ngIf="isSignedIn()" class="mat-elevation-z6" id="globalHeader"></app-navbar>
<app-navbar *ngIf="isSignedIn" class="mat-elevation-z6" id="globalHeader"></app-navbar>
<router-outlet></router-outlet>
<app-footer></app-footer>
</div>

View File

@ -1,8 +1,10 @@
import {HttpClient} from '@angular/common/http';
import {HttpClientTestingModule} from '@angular/common/http/testing';
import {Component} from '@angular/core';
import {async, ComponentFixture, TestBed} from '@angular/core/testing';
import {BrowserAnimationsModule} from '@angular/platform-browser/animations';
import {RouterTestingModule} from '@angular/router/testing';
import {MockEnvironment} from 'sartography-workflow-lib';
import {ApiService, MockEnvironment} from 'sartography-workflow-lib';
import {AppComponent} from './app.component';
@ -32,10 +34,13 @@ describe('AppComponent', () => {
MockFooterComponent
],
imports: [
HttpClientTestingModule,
BrowserAnimationsModule,
RouterTestingModule,
],
providers: [
HttpClient,
ApiService,
{provide: 'APP_ENVIRONMENT', useClass: MockEnvironment},
],
})

View File

@ -1,5 +1,5 @@
import {Component} from '@angular/core';
import {isSignedIn} from 'sartography-workflow-lib';
import {ApiService} from 'sartography-workflow-lib';
@Component({
selector: 'app-root',
@ -8,5 +8,11 @@ import {isSignedIn} from 'sartography-workflow-lib';
})
export class AppComponent {
title = 'CR Connect Configuration';
isSignedIn = isSignedIn;
constructor(private apiService: ApiService) {
}
get isSignedIn() {
return this.apiService.isSignedIn();
}
}

View File

@ -52,6 +52,7 @@ import {WorkflowSpecListComponent} from './workflow-spec-list/workflow-spec-list
@Injectable()
export class ThisEnvironment implements AppEnvironment {
homeRoute = environment.homeRoute;
production = environment.production;
api = environment.api;
irbUrl = environment.irbUrl;

View File

@ -31,6 +31,7 @@ export class DiagramComponent implements ControlValueAccessor, AfterViewInit {
private diagramType: FileType;
private modeler: BpmnModeler | DmnModeler;
private xml = '';
private svg;
private disabled = false;
// Hack so we can spy on this function
@ -46,12 +47,16 @@ export class DiagramComponent implements ControlValueAccessor, AfterViewInit {
return this.xml;
}
get svgValue(): string {
return this.svg;
}
ngAfterViewInit() {
this.initializeModeler(this.diagramType);
this.openDiagram(this.xml);
}
onChange(value: any) {
onChange(newValue: string, svgValue: string) {
}
onTouched() {
@ -74,12 +79,12 @@ export class DiagramComponent implements ControlValueAccessor, AfterViewInit {
this.openDiagram(value);
}
this.xml = value;
this.onChange(this.xml);
this.onChange(this.xml, this.svg);
}
// Allows Angular to register a function to call when the model changes.
// Save the function as a property to call later here.
registerOnChange(fn: (newXmlValue: string) => void): void {
registerOnChange(fn: (newXmlValue: string, newSvgValue: string) => void): void {
this.onChange = fn;
}
@ -118,9 +123,12 @@ export class DiagramComponent implements ControlValueAccessor, AfterViewInit {
}
saveDiagram() {
this.modeler.saveXML({format: true}, (err, xml) => {
this.xml = xml;
this.writeValue(xml);
this.modeler.saveSVG((svgErr, svg) => {
this.svg = svg;
this.modeler.saveXML({format: true}, (xmlErr, xml) => {
this.xml = xml;
this.writeValue(xml);
});
});
}

View File

@ -1,2 +1,2 @@
<app-sign-in *ngIf="!isSignedIn()"></app-sign-in>
<app-workflow-spec-list *ngIf="isSignedIn()"></app-workflow-spec-list>
<app-sign-in *ngIf="!isSignedIn"></app-sign-in>
<app-workflow-spec-list *ngIf="isSignedIn"></app-workflow-spec-list>

View File

@ -1,6 +1,8 @@
import {Component, NO_ERRORS_SCHEMA} from '@angular/core';
import {HttpClient} from '@angular/common/http';
import {HttpClientTestingModule} from '@angular/common/http/testing';
import {Component} from '@angular/core';
import {async, ComponentFixture, TestBed} from '@angular/core/testing';
import {MockEnvironment} from 'sartography-workflow-lib';
import {ApiService, MockEnvironment} from 'sartography-workflow-lib';
import {HomeComponent} from './home.component';
@ -9,13 +11,15 @@ import {HomeComponent} from './home.component';
selector: 'app-sign-in',
template: ''
})
class MockSignInComponent {}
class MockSignInComponent {
}
@Component({
selector: 'app-workflow-spec-list',
template: ''
})
class MockWorkflowSpecListComponent {}
class MockWorkflowSpecListComponent {
}
describe('HomeComponent', () => {
let component: HomeComponent;
@ -28,8 +32,16 @@ describe('HomeComponent', () => {
MockSignInComponent,
MockWorkflowSpecListComponent,
],
imports: [
HttpClientTestingModule
],
providers: [
HttpClient,
ApiService,
{provide: 'APP_ENVIRONMENT', useClass: MockEnvironment},
]
})
.compileComponents();
.compileComponents();
}));
beforeEach(() => {
@ -43,7 +55,7 @@ describe('HomeComponent', () => {
});
it('should check signed-in state', () => {
const result = component.isSignedIn();
const result = component.isSignedIn;
expect(result).toBeDefined();
expect(typeof result).toEqual('boolean');
});

View File

@ -1,5 +1,5 @@
import {Component} from '@angular/core';
import {isSignedIn} from 'sartography-workflow-lib';
import {ApiService, isSignedIn} from 'sartography-workflow-lib';
@Component({
selector: 'app-home',
@ -7,9 +7,11 @@ import {isSignedIn} from 'sartography-workflow-lib';
styleUrls: ['./home.component.scss']
})
export class HomeComponent {
isSignedIn = isSignedIn;
constructor() {
constructor(private apiService: ApiService) {
}
get isSignedIn(): boolean {
return this.apiService.isSignedIn();
}
}

View File

@ -42,6 +42,7 @@ export class ModelerComponent implements AfterViewInit {
fileTypes = FileType;
private xml = '';
private draftXml = '';
private svg = '';
@ViewChild(DiagramComponent) private diagramComponent: DiagramComponent;
private diagramType: FileType;
private workflowSpecId: string;
@ -67,8 +68,9 @@ export class ModelerComponent implements AfterViewInit {
}
ngAfterViewInit(): void {
this.diagramComponent.registerOnChange((newXmlValue: string) => {
this.diagramComponent.registerOnChange((newXmlValue: string, newSvgValue: string) => {
this.draftXml = newXmlValue;
this.svg = newSvgValue;
});
}
@ -158,6 +160,7 @@ export class ModelerComponent implements AfterViewInit {
newDiagram(diagramType?: FileType) {
this.xml = '';
this.draftXml = '';
this.svg = '';
this.fileName = '';
this.diagramFileMeta = undefined;
this.diagramFile = undefined;
@ -306,6 +309,11 @@ export class ModelerComponent implements AfterViewInit {
this.xml = this.draftXml;
this.diagramFileMeta.file = new File([this.xml], this.diagramFileMeta.name, {type: 'text/xml'});
if (this.svg && this.svg !== '') {
const svgFile = new File([this.svg], this.diagramFileMeta.name, {type: 'text/xml'});
// this.api.updateFileData();
}
this.api.updateFileData(this.diagramFileMeta).subscribe(newFileMeta => {
this.diagramFileMeta = newFileMeta;
this.snackBar.open(`Saved changes to file metadata ${this.diagramFileMeta.name}.`, 'Ok', {duration: 5000});

View File

@ -1,4 +1,4 @@
<nav *ngIf="isSignedIn() && user">
<nav *ngIf="user">
<a
[routerLink]="['/']"
class="site-title mat-display-1"

View File

@ -16,7 +16,7 @@ interface NavItem {
templateUrl: './navbar.component.html',
styleUrls: ['./navbar.component.scss']
})
export class NavbarComponent implements OnInit {
export class NavbarComponent {
navLinks: NavItem[];
user: User;
isSignedIn = isSignedIn;
@ -28,9 +28,6 @@ export class NavbarComponent implements OnInit {
this._loadUser();
}
ngOnInit() {
}
isLinkActive(path: string) {
return path === this.router.url;
}

View File

@ -57,8 +57,7 @@ export class SignInComponent implements OnInit {
constructor(
@Inject('APP_ENVIRONMENT') private environment: AppEnvironment,
private router: Router,
private api: ApiService,
private platformLocation: PlatformLocation
private api: ApiService
) {
}
@ -72,7 +71,7 @@ export class SignInComponent implements OnInit {
// For testing purposes, create a user to simulate login.
if (!this.environment.production) {
this.model.redirect_url = this.platformLocation.href + 'session';
this.model.redirect_url = location.origin + '/session';
this.api.openSession(this.model);
} else {
this.error = new Error('This feature does not work in production.');

View File

@ -2,8 +2,14 @@ import {AppEnvironment} from 'sartography-workflow-lib';
declare var ENV;
const envHas = (key, temp): boolean => (ENV && (ENV[key] !== null) && (ENV[key] !== undefined) && (ENV[key] !== temp));
export const environment: AppEnvironment = {
production: ENV && ENV.production === '$PRODUCTION' ? false : (ENV.production === 'true'),
api: ENV && ENV.api === '$API_URL' ? 'http://localhost:5000/v1.0' : ENV.api,
irbUrl: ENV && ENV.irbUrl === '$IRB_URL' ? 'http://localhost:5001' : ENV.irbUrl,
homeRoute: envHas('homeRoute', '$HOME_ROUTE') ? ENV.homeRoute : 'home',
production: envHas('production', '$PRODUCTION') ? (ENV.production === 'true') : false,
api: envHas('api', '$API_URL') ? ENV.api : 'http://localhost:5000/v1.0',
irbUrl: envHas('irbUrl', '$IRB_URL') ? ENV.irbUrl : 'http://localhost:5001',
};

View File

@ -6,6 +6,7 @@
<base href="/">
<script>
const ENV = {
homeRoute: '$HOME_ROUTE',
production: '$PRODUCTION',
api: '$API_URL',
irbUrl: '$IRB_URL',