Skip to content
This repository has been archived by the owner on Nov 15, 2019. It is now read-only.

Commit

Permalink
Various Improvements to Export to FireCloud (#8)
Browse files Browse the repository at this point in the history
#6
DataBiosphere/dcc-dashboard-service#12

* While exporting, change label of "Export" button to "Exporting..."
* Don't use ngrx for the export to FireCloud dialog, since application state is not being changed.
This simplifies the code, although we may want to revisit this decision.
* Noticed that 401 with FC causes a text/html response to be returned,
so add some safeguards for that.
* Display existing workspaces in UI. As part of that, need to not only display your namespaces/billing projects, but also the namespaces/billing projects of workspaces that have been shared with you.
* Dialog now makes clear whether you are creating a new workspace or exporting to an existing workspace (although the new workspace case should check that it is new).
* Add launch icon
* Show DOS URI instead of File Id.
* Retry up to 5 times in the calls to FireCloud API to fetch workspace and namespaces, as we have sometimes random failures.
  • Loading branch information
coverbeck authored Apr 6, 2018
1 parent 748bc58 commit d769483
Show file tree
Hide file tree
Showing 18 changed files with 209 additions and 191 deletions.
25 changes: 16 additions & 9 deletions spa/src/app/cc-http/shared/cc-base.dao.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import * as _ from "lodash";
// App dependencies
import { CONFLICT, NO_CONTENT } from "./http-response-status";
import { ConflictError } from "./error";
import "rxjs/add/operator/retry";

/**
* Base DAO (service) for handing, formatting and parsing HTTP requests/responses.
Expand Down Expand Up @@ -56,13 +57,8 @@ export class CCBaseDAO {
* @param queryStringParams {any}
* @returns {Observable<T>}
*/
public get<T>(url: string, queryStringParams?: any): Observable<T> {
public get<T>(url: string, queryStringParams?: any, retry = 0): Observable<T> {

return this.getNoCatch(url, queryStringParams)
.catch(response => this.handleError(response));
}

public getNoCatch<T>(url: string, queryStringParams?: any): Observable<T> {
// Build up GET headers
let headers = new Headers();
this.addAcceptHeader(headers);
Expand All @@ -84,7 +80,9 @@ export class CCBaseDAO {

return <Observable<T>>this.http // TODO revisit typing here...
.get(url, requestOptions)
.map(response => this.toJSON(response));
.map(response => this.toJSON(response))
.retry(retry)
.catch(response => this.handleError(response));
}

/**
Expand Down Expand Up @@ -128,11 +126,20 @@ export class CCBaseDAO {
// TODO conflict here with session timeout vs invalid login - disabled this to enable handling of 401 on
// invalid login in LoginComponent if ( response.status === UNAUTHORIZED ) { window.location.reload(); }

let body;
try {
body = response.json();
}
catch (ex) {
// If you get a 401 with FireCloud API, response body is text/html, regardless of the
// request's Accept header value.
body = response.statusText;
}
if (response.status === CONFLICT) {
return Observable.throw(new ConflictError(response.json()));
return Observable.throw(new ConflictError(body));
}

return Observable.throw(response.json());
return Observable.throw(body);
}

/**
Expand Down
27 changes: 0 additions & 27 deletions spa/src/app/files/_ngrx/file-export/file-export.actions.ts

This file was deleted.

23 changes: 0 additions & 23 deletions spa/src/app/files/_ngrx/file-export/file-export.reducer.ts

This file was deleted.

19 changes: 0 additions & 19 deletions spa/src/app/files/_ngrx/file-export/file-export.state.ts

This file was deleted.

25 changes: 2 additions & 23 deletions spa/src/app/files/_ngrx/file.effects.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,9 @@ import "rxjs/add/observable/concat";
import "rxjs/add/observable/of";
import "rxjs/add/operator/withLatestFrom";
import * as _ from "lodash";

// App dependencies
import { FilesService } from "../shared/files.service";
import { FileSummary } from "../file-summary/file-summary";
import { FileFacetMetadata } from "../file-facet-metadata/file-facet-metadata.model";
import {
FetchFileFacetsRequestAction,
FetchFileFacetsSuccessAction,
Expand All @@ -43,15 +41,12 @@ import {
} from "app/files/_ngrx/file.selectors";
import { AppState } from "../../_ngrx/app.state";
import {
FetchInitialTableDataRequestAction, FetchPagedOrSortedTableDataRequestAction,
FetchInitialTableDataRequestAction,
FetchPagedOrSortedTableDataRequestAction,
FetchTableDataSuccessAction
} from "./table/table.actions";
import { TableModel } from "../table/table.model";
import { DEFAULT_TABLE_PARAMS } from "../table/table-params.model";
import {
FileExportManifestRequestAction, FileExportManifestErrorAction,
FileExportManifestSuccessAction
} from "./file-export/file-export.actions";

@Injectable()
export class FileEffects {
Expand Down Expand Up @@ -194,22 +189,6 @@ export class FileEffects {
return this.fileService.downloadFileManifest(query);
});

@Effect()
exportToFireCloud$: Observable<Action> = this.actions$
.ofType<FileExportManifestRequestAction>(FileExportManifestRequestAction.ACTION_TYPE)
.map(action => action.payload)
.withLatestFrom(this.store.select(selectSelectedFileFacets))
.switchMap((results) => {
const [payload, selectedFacets] = results;
return this.fileService.exportToFireCloud(selectedFacets, payload.name, payload.namespace);
})
.map((fcUrl) => {
if (fcUrl.startsWith("Error")) {
return new FileExportManifestErrorAction(fcUrl);
}
return new FileExportManifestSuccessAction(fcUrl);
});

private colorWheel: Map<string, string>;

/**
Expand Down
4 changes: 1 addition & 3 deletions spa/src/app/files/_ngrx/file.reducer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,12 @@ import * as fileSummaryReducer from "./file-summary/file-summary.reducer";
import * as fileFacetListReducer from "./file-facet-list/file-facet-list.reducer";
import * as fileManifestSummaryReducer from "./file-manifest-summary/file-manifest-summary.reducer";
import * as fileFacetMetadataSummaryReducer from "./file-facet-metadata-summary/file-facet-metadata-summary.reducer";
import * as fileExportReducer from "./file-export/file-export.reducer";
import * as tableReducer from "./table/table.reducer";

export const reducer = {
fileSummary: fileSummaryReducer.reducer,
fileFacetList: fileFacetListReducer.reducer,
fileManifestSummary: fileManifestSummaryReducer.reducer,
fileFacetMetadataSummary: fileFacetMetadataSummaryReducer.reducer,
tableState: tableReducer.reducer,
fileExportState: fileExportReducer.reducer
tableState: tableReducer.reducer
};
3 changes: 0 additions & 3 deletions spa/src/app/files/_ngrx/file.selectors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ import { createFeatureSelector, createSelector } from "@ngrx/store";
import { FileSummaryState } from "./file-summary/file-summary.state";
import { FileFacetListState } from "./file-facet-list/file-facet-list.state";
import { FileFacetMetadataSummaryState } from "./file-facet-metadata-summary/file-facet-metadata-summary.state";
import { FileExportManifestState } from "./file-export/file-export.state";
import { TableState } from "./table/table.state";

export const selectFileFacets = createFeatureSelector<FileFacetListState>("fileFacetList");
Expand Down Expand Up @@ -51,5 +50,3 @@ export const selectTableQueryParams = createSelector(selectSelectedFacetsMap, se
return { selectedFacets, pagination };
});

export const selectExportFileState = createFeatureSelector<FileExportManifestState>("fileExportState");

2 changes: 0 additions & 2 deletions spa/src/app/files/_ngrx/file.state.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,11 @@ import { FileFacetListState } from "./file-facet-list/file-facet-list.state";
import { FileManifestSummaryState } from "./file-manifest-summary/file-manifest-summary.state";
import { FileFacetMetadataSummaryState } from "./file-facet-metadata-summary/file-facet-metadata-summary.state";
import { TableState } from "./table/table.state";
import { FileExportManifestState } from "./file-export/file-export.state";

export interface FileState {
fileSummary: FileSummaryState;
fileFacetList: FileFacetListState;
fileManifestSummary: FileManifestSummaryState;
fileFacetMetadataSummary: FileFacetMetadataSummaryState;
tableState: TableState;
exportFileManifestState: FileExportManifestState;
}
7 changes: 7 additions & 0 deletions spa/src/app/files/file-export/file-export.component.css
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,10 @@
display: flex;
font-size: large;
}
mat-radio-group {
margin-bottom: 10px;
}

mat-radio-button {
margin-right: 10px;
}
23 changes: 16 additions & 7 deletions spa/src/app/files/file-export/file-export.component.html
Original file line number Diff line number Diff line change
@@ -1,24 +1,33 @@
<h1 mat-dialog-title>Export to FireCloud</h1>
<div mat-dialog-content>
<mat-progress-bar mode="indeterminate" *ngIf="exporting"></mat-progress-bar>
<p *ngIf="exported">The selected files were exported to the FireCloud <a [href]="firecloudUrl" target="_blank">{{data.workspace}} workspace.</a></p>
<p *ngIf="exported">The selected files were exported to the FireCloud <a [href]="firecloudUrl" target="_blank">{{data.workspace}} workspace. <mat-icon style="font-size: 16px;">launch</mat-icon></a></p>
<p *ngIf="errorMessage">There was an error exporting to FireCloud: {{errorMessage}}</p>
<div *ngIf="!exported && !errorMessage">
<p>Export the selected facets into a new FireCloud workspace.</p>
<p>Export the selected facets into a FireCloud workspace.</p>
<div class="column-flex">
<mat-form-field>
<input matInput [(ngModel)]="data.workspace" placeholder="Workspace name" required autofocus [disabled]="exporting">
<mat-select placeholder="Billing project" [(value)]="data.namespace" [disabled]="exporting" required (change)="onBillingProjectChanged($event)">
<mat-option *ngFor="let namespace of billingProjects" [value]="namespace">{{namespace}}</mat-option>
</mat-select>
</mat-form-field>
<mat-form-field>
<mat-select placeholder="Billing project" [(value)]="data.namespace" [disabled]="exporting" required>
<mat-option *ngFor="let namespace of data.namespaces" [value]="namespace">{{namespace}}</mat-option>
<mat-radio-group [(ngModel)]="workspaceType" (change)="onWorkspaceTypeChanged($event)">
<mat-radio-button value="new" [disabled]="!canCreateWorkspace">New workspace</mat-radio-button>
<mat-radio-button value="existing">Existing workspace</mat-radio-button>
</mat-radio-group>
<mat-form-field *ngIf="workspaceType === 'new'">
<input matInput [(ngModel)]="data.workspace" placeholder="Workspace" required autofocus [disabled]="exporting">
</mat-form-field>
<mat-form-field *ngIf="workspaceType === 'existing'">
<mat-select placeholder="Workspace" [disabled]="exporting" [(value)]="data.workspace" required>
<mat-option *ngFor="let workspace of filteredWorkspaces" [value]="workspace.workspace.name">{{workspace.workspace.name}}</mat-option>
</mat-select>
</mat-form-field>
</div>
</div>
</div>
<div mat-dialog-actions align="end">
<button mat-button (click)="onClose()" *ngIf="!exported && !errorMessage">Cancel</button>
<button mat-button (click)="onExport()" *ngIf="!exported && !errorMessage" [disabled]="exporting || !data.workspace">Export</button>
<button mat-button (click)="onExport()" *ngIf="!exported && !errorMessage" [disabled]="exporting || !data.workspace">{{exporting ? 'Exporting...' : 'Export'}}</button>
<button mat-button (click)="onClose()" *ngIf="exported || errorMessage">Close</button>
</div>
Loading

0 comments on commit d769483

Please sign in to comment.