From 8a0464f7d9cf1b667bb0dc096c7901f05c259e54 Mon Sep 17 00:00:00 2001 From: Sam Kagan <skagan@nrao.edu> Date: Mon, 21 Oct 2024 17:06:49 -0600 Subject: [PATCH 01/10] Added service to retrieve project and proposal info to UI --- .../app/workspaces/model/archive-models.ts | 30 ++++++++++++++ .../services/archive.service.spec.ts | 35 ++++++++++++++++ .../workspaces/services/archive.service.ts | 40 +++++++++++++++++++ 3 files changed, 105 insertions(+) create mode 100644 apps/web/src/app/workspaces/model/archive-models.ts create mode 100644 apps/web/src/app/workspaces/services/archive.service.spec.ts create mode 100644 apps/web/src/app/workspaces/services/archive.service.ts diff --git a/apps/web/src/app/workspaces/model/archive-models.ts b/apps/web/src/app/workspaces/model/archive-models.ts new file mode 100644 index 000000000..94ec25da8 --- /dev/null +++ b/apps/web/src/app/workspaces/model/archive-models.ts @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2023 Associated Universities, Inc. Washington DC, USA. + * + * This file is part of NRAO Workspaces. + * + * Workspaces is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Workspaces is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Workspaces. If not, see <https://www.gnu.org/licenses/>. + * + */ +export interface ProductMetadata { + telescope: string; + project_code: string; + type: string; + date: Date; + mous_id: string | null; + segment: string | null; + title: string; + abstract: string; + observing_types: Array<string>; +} diff --git a/apps/web/src/app/workspaces/services/archive.service.spec.ts b/apps/web/src/app/workspaces/services/archive.service.spec.ts new file mode 100644 index 000000000..cc1f8282a --- /dev/null +++ b/apps/web/src/app/workspaces/services/archive.service.spec.ts @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2023 Associated Universities, Inc. Washington DC, USA. + * + * This file is part of NRAO Workspaces. + * + * Workspaces is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Workspaces is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Workspaces. If not, see <https://www.gnu.org/licenses/>. + * + */ +import { TestBed } from '@angular/core/testing'; + +import { ArchiveService } from './archive.service'; + +describe('ArchiveService', () => { + let service: ArchiveService; + + beforeEach(() => { + TestBed.configureTestingModule({}); + service = TestBed.inject(ArchiveService); + }); + + it('should be created', () => { + expect(service).toBeTruthy(); + }); +}); diff --git a/apps/web/src/app/workspaces/services/archive.service.ts b/apps/web/src/app/workspaces/services/archive.service.ts new file mode 100644 index 000000000..03ffc572e --- /dev/null +++ b/apps/web/src/app/workspaces/services/archive.service.ts @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2023 Associated Universities, Inc. Washington DC, USA. + * + * This file is part of NRAO Workspaces. + * + * Workspaces is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Workspaces is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Workspaces. If not, see <https://www.gnu.org/licenses/>. + * + */ +import { HttpClient, HttpParams } from '@angular/common/http'; +import { Injectable } from '@angular/core'; +import { Observable } from 'rxjs'; +import { environment } from 'src/environments/environment'; +import { UpdateOutputFileStampsProject } from 'typescript'; +import { ProductMetadata } from '../model/archive-models'; + +@Injectable({ + providedIn: 'root' +}) +export class ArchiveService { + + constructor(private httpClient: HttpClient) { } + + getMetadataForProduct(product_locator: string, include_proposal_info = true): Observable<ProductMetadata> { + let params = new HttpParams(); + params = params.append("locator", product_locator); + params = params.append("include_proposal_info", include_proposal_info) + return this.httpClient.get<ProductMetadata>(`${environment.apiUrl}product/metadata`, {params: params}); + } +} -- GitLab From ae4a6c0fefffc9b98099501a654d47aace16734c Mon Sep 17 00:00:00 2001 From: Sam Kagan <skagan@nrao.edu> Date: Tue, 22 Oct 2024 12:36:12 -0600 Subject: [PATCH 02/10] Hooked up product-metadata endpoint to WS UI --- .../capability-request.component.html | 3 +++ .../capability-request.component.ts | 16 ++++++++++++++++ 2 files changed, 19 insertions(+) diff --git a/apps/web/src/app/workspaces/components/capability-request/capability-request.component.html b/apps/web/src/app/workspaces/components/capability-request/capability-request.component.html index 1781d9a84..0419b44e0 100644 --- a/apps/web/src/app/workspaces/components/capability-request/capability-request.component.html +++ b/apps/web/src/app/workspaces/components/capability-request/capability-request.component.html @@ -45,6 +45,9 @@ (versionEvent)="setVersion($event)" ></app-versions> <br /> + <span>{{productMetadata | json}}</span> + <br /> + <br /> <span id="parameters-label" *ngIf="currentVersion" >Version {{ currentVersion.version_number }} Parameters</span > diff --git a/apps/web/src/app/workspaces/components/capability-request/capability-request.component.ts b/apps/web/src/app/workspaces/components/capability-request/capability-request.component.ts index eb1a1ae39..6dee9e81f 100644 --- a/apps/web/src/app/workspaces/components/capability-request/capability-request.component.ts +++ b/apps/web/src/app/workspaces/components/capability-request/capability-request.component.ts @@ -32,6 +32,8 @@ import { JsonObject } from "@angular/compiler-cli/ngcc/src/packages/entry_point" import { Subject, Observable } from "rxjs"; import { takeUntil, repeatWhen } from "rxjs/operators"; import { Capability } from "../../model/capability"; +import { ProductMetadata } from "../../model/archive-models"; +import { ArchiveService } from "../../services/archive.service"; @Component({ selector: "app-capability-request", @@ -49,6 +51,7 @@ export class CapabilityRequestComponent implements OnInit, OnDestroy { private alertService: AlertService, private titleService: Title, private urlService: UrlService, + private archiveService: ArchiveService, ) { const requestID = parseInt(this.route.snapshot.paramMap.get("id")); this.pollingDataUpdaterService @@ -73,6 +76,7 @@ export class CapabilityRequestComponent implements OnInit, OnDestroy { public currentVersion: CapabilityVersion; public latestVersion: CapabilityVersion; public previousUrlString: string; + public productMetadata: ProductMetadata | null = null; activeRequestsPageDefaultUrl = "/workspaces/active-requests"; previousUrl: Observable<string> = this.urlService.previousUrl$; @@ -140,6 +144,9 @@ export class CapabilityRequestComponent implements OnInit, OnDestroy { this.currentVersion.version_number === this.latestVersion.version_number ) { this.setVersion(this.latestVersion); + // Refresh product metadata whenever select version changes + // since it owns the request's product locator + this.archiveService.getMetadataForProduct(this.latestVersion.parameters["product_locator"] as string).subscribe(this.productMetadataObserver); } } else { console.error("Current version returned undefined."); @@ -148,6 +155,15 @@ export class CapabilityRequestComponent implements OnInit, OnDestroy { error: (error) => console.error("Error when retrieving current version:" + error), }; + private productMetadataObserver = { + next: (productMetadata: ProductMetadata) => { + if (productMetadata) { + this.productMetadata = productMetadata; + } + }, + error: (error) => console.error(`Error when retrieving product metadata for CapabilityRequest ${this.capabilityRequest?.id} Version ${this.currentVersion.version_number} :` + error), + }; + public requestMessageObserver = { next: () => { window.location.reload(); -- GitLab From e587f865f7286c0ce9760c1c1ed800ff7286011c Mon Sep 17 00:00:00 2001 From: Sam Kagan <skagan@nrao.edu> Date: Mon, 28 Oct 2024 12:22:20 -0600 Subject: [PATCH 03/10] Added ngb-modal-draggable library's source code from GH --- .licenserc.yaml | 2 + .../app/shared/ngb-modal-draggable/LICENSE | 21 ++++++ .../app/shared/ngb-modal-draggable/README.md | 50 ++++++++++++++ .../ngb-modal-draggable.directive.ts | 66 +++++++++++++++++++ .../ngb-modal-draggable.module.ts | 13 ++++ 5 files changed, 152 insertions(+) create mode 100644 apps/web/src/app/shared/ngb-modal-draggable/LICENSE create mode 100644 apps/web/src/app/shared/ngb-modal-draggable/README.md create mode 100644 apps/web/src/app/shared/ngb-modal-draggable/ngb-modal-draggable.directive.ts create mode 100644 apps/web/src/app/shared/ngb-modal-draggable/ngb-modal-draggable.module.ts diff --git a/.licenserc.yaml b/.licenserc.yaml index 9f841f4ef..85be0da9c 100644 --- a/.licenserc.yaml +++ b/.licenserc.yaml @@ -28,5 +28,7 @@ header: - '**/node_modules/**' - '**/cli/executables/pexable/pycapo/**' - '**/schema/versions/**' + # This is third-party code provided under the MIT license + - 'apps/web/src/app/shared/ngb-modal-draggable' license-location-threshold: 20 diff --git a/apps/web/src/app/shared/ngb-modal-draggable/LICENSE b/apps/web/src/app/shared/ngb-modal-draggable/LICENSE new file mode 100644 index 000000000..caf0a3f00 --- /dev/null +++ b/apps/web/src/app/shared/ngb-modal-draggable/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2016 Vanden Bosch Cédric + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/apps/web/src/app/shared/ngb-modal-draggable/README.md b/apps/web/src/app/shared/ngb-modal-draggable/README.md new file mode 100644 index 000000000..4f96069a1 --- /dev/null +++ b/apps/web/src/app/shared/ngb-modal-draggable/README.md @@ -0,0 +1,50 @@ +**Copied from [https://github.com/mattxu-zz/ngb-modal-draggable](https://github.com/mattxu-zz/ngb-modal-draggable)** + +### NgbModalDraggable + +Angular Directive used for make ngbModal draggable + +### Install + +``` +npm i ngb-modal-draggable --save +``` + +### How to use + +First import NgbModalDraggableModule in your modules + +``` +import { NgModule } from '@angular/core'; +import { NgbModalDraggableModule } from 'ngb-modal-draggable' + +@NgModule({ + imports: [ + .... + + NgbModalDraggableModule + ], + ... +}) +``` + +Then just use in your modal component html + +``` +<div ngb-modal-draggable [ngbModalDraggableHandle]="draggableHandle"> + <div #draggableHandle class="modal-header"> + <h4 class="modal-title" id="modal-basic-title">Profile update</h4> + <button type="button" class="close" aria-label="Close" (click)="modal.dismiss('Cross click')"> + <span aria-hidden="true">×</span> + </button> + </div> + ... +</div> +``` + +### Properties + +| Input | Type | Default | Description | +| ------ | ------ | ------ | ------ | +| ngbModalDraggableHandle | HTMLElement | null | Use template variable to refer to the handle element. Then only the handle element is draggable | +| ngbModalRootLevel | number | 2 | Set the root level from current element which set the directive. If you set the directive on the root element of the NgbModal component, the true root level would be 2 as the NgbModalService would add two parent element automatically | diff --git a/apps/web/src/app/shared/ngb-modal-draggable/ngb-modal-draggable.directive.ts b/apps/web/src/app/shared/ngb-modal-draggable/ngb-modal-draggable.directive.ts new file mode 100644 index 000000000..3e0c0d2fb --- /dev/null +++ b/apps/web/src/app/shared/ngb-modal-draggable/ngb-modal-draggable.directive.ts @@ -0,0 +1,66 @@ +import { Directive, ElementRef, HostListener, AfterViewInit, Input } from "@angular/core"; +@Directive({ + selector: '[ngb-modal-draggable]' +}) +export class NgbModalDraggable implements AfterViewInit { + private modalElement: HTMLElement; + private topStart: number; + private leftStart: number; + private md: boolean; + private handleElement: HTMLElement; + private rootLevel: number = 2; + + constructor(public element: ElementRef) { + } + + ngAfterViewInit() { + let element = this.element.nativeElement; + for (let level = this.rootLevel; level > 0; level --) { + element = element.parentNode; + } + + this.modalElement = element; + + this.modalElement.style.position = 'relative'; + this.modalElement.className += ' cursor-draggable'; + } + + @HostListener('mousedown', ['$event']) + onMouseDown(event: MouseEvent) { + if (event.button === 2 || (this.handleElement && event.target !== this.handleElement)) + return; // prevents right click drag, remove this if you don't want it + this.md = true; + this.topStart = event.clientY - Number(this.modalElement.style.top.replace('px', '')); + this.leftStart = event.clientX - Number(this.modalElement.style.left.replace('px', '')); + event.preventDefault(); + } + + @HostListener('document:mouseup', ['$event']) + onMouseUp(event: MouseEvent) { + this.md = false; + } + + @HostListener('document:mousemove', ['$event']) + onMouseMove(event: MouseEvent) { + //console.dir(event.target) + if (this.md) { + this.modalElement.style.top = (event.clientY - this.topStart) + 'px'; + this.modalElement.style.left = (event.clientX - this.leftStart) + 'px'; + } + } + + @HostListener('document:mouseleave', ['$event']) + onMouseLeave(event: MouseEvent) { + this.md = false; + } + + @Input() + set ngbModalDraggableHandle(handle: HTMLElement){ + this.handleElement = handle; + } + + @Input() + set ngbModalRootLevel(level: number) { + this.rootLevel = level; + } +} diff --git a/apps/web/src/app/shared/ngb-modal-draggable/ngb-modal-draggable.module.ts b/apps/web/src/app/shared/ngb-modal-draggable/ngb-modal-draggable.module.ts new file mode 100644 index 000000000..c0299f9f8 --- /dev/null +++ b/apps/web/src/app/shared/ngb-modal-draggable/ngb-modal-draggable.module.ts @@ -0,0 +1,13 @@ +import {NgModule} from '@angular/core'; +import {NgbModalDraggable} from './ngb-modal-draggable.directive'; + +@NgModule({ + declarations:[ + NgbModalDraggable + ], + exports:[ + NgbModalDraggable + ] +}) + +export class NgbModalDraggableModule{} -- GitLab From fb69f2ccaa8e54013bdb168c8f6b86e13411933a Mon Sep 17 00:00:00 2001 From: Sam Kagan <skagan@nrao.edu> Date: Tue, 29 Oct 2024 13:05:08 -0600 Subject: [PATCH 04/10] Updated cursor when dragging & fixed bugs in ngb-modal-draggable --- .../ngb-modal-draggable.directive.ts | 31 +++++++++++++++---- 1 file changed, 25 insertions(+), 6 deletions(-) diff --git a/apps/web/src/app/shared/ngb-modal-draggable/ngb-modal-draggable.directive.ts b/apps/web/src/app/shared/ngb-modal-draggable/ngb-modal-draggable.directive.ts index 3e0c0d2fb..847c39035 100644 --- a/apps/web/src/app/shared/ngb-modal-draggable/ngb-modal-draggable.directive.ts +++ b/apps/web/src/app/shared/ngb-modal-draggable/ngb-modal-draggable.directive.ts @@ -23,20 +23,38 @@ export class NgbModalDraggable implements AfterViewInit { this.modalElement.style.position = 'relative'; this.modalElement.className += ' cursor-draggable'; + this.element = new ElementRef(this.modalElement); + + if (!this.handleElement) { + this.handleElement = this.modalElement; + } + + this.handleElement.addEventListener('mouseenter', (event: MouseEvent) => {this.modalElement.style.cursor = "grab";}) + this.handleElement.addEventListener('mouseleave', (event: MouseEvent) => { + if (!this.md) { + this.modalElement.style.cursor = "auto"; + } + }) } @HostListener('mousedown', ['$event']) onMouseDown(event: MouseEvent) { - if (event.button === 2 || (this.handleElement && event.target !== this.handleElement)) - return; // prevents right click drag, remove this if you don't want it - this.md = true; - this.topStart = event.clientY - Number(this.modalElement.style.top.replace('px', '')); - this.leftStart = event.clientX - Number(this.modalElement.style.left.replace('px', '')); - event.preventDefault(); + if (event.button !== 2 && (event.target instanceof Element && this.handleElement.contains(event.target))) { + this.md = true; + this.modalElement.style.cursor = "grabbing"; + this.topStart = event.clientY - Number(this.modalElement.style.top.replace('px', '')); + this.leftStart = event.clientX - Number(this.modalElement.style.left.replace('px', '')); + event.preventDefault(); + } } @HostListener('document:mouseup', ['$event']) onMouseUp(event: MouseEvent) { + if (event.target instanceof Element && this.handleElement.contains(event.target)) { + this.modalElement.style.cursor = "grab"; + } else { + this.modalElement.style.cursor = "auto"; + } this.md = false; } @@ -52,6 +70,7 @@ export class NgbModalDraggable implements AfterViewInit { @HostListener('document:mouseleave', ['$event']) onMouseLeave(event: MouseEvent) { this.md = false; + this.modalElement.style.cursor = "auto"; } @Input() -- GitLab From 03e178e747cb2e6f2a4274435d0460c34e60b8d6 Mon Sep 17 00:00:00 2001 From: Sam Kagan <skagan@nrao.edu> Date: Tue, 29 Oct 2024 11:31:38 -0600 Subject: [PATCH 05/10] Added modal displaying prop-info to request page --- .../capability-request.component.html | 12 +++-- .../proposal-info.component.html | 42 +++++++++++++++ .../proposal-info.component.scss | 0 .../proposal-info.component.spec.ts | 43 +++++++++++++++ .../proposal-info/proposal-info.component.ts | 54 +++++++++++++++++++ .../src/app/workspaces/workspaces.module.ts | 4 ++ 6 files changed, 152 insertions(+), 3 deletions(-) create mode 100644 apps/web/src/app/workspaces/components/capability-request/components/proposal-info/proposal-info.component.html create mode 100644 apps/web/src/app/workspaces/components/capability-request/components/proposal-info/proposal-info.component.scss create mode 100644 apps/web/src/app/workspaces/components/capability-request/components/proposal-info/proposal-info.component.spec.ts create mode 100644 apps/web/src/app/workspaces/components/capability-request/components/proposal-info/proposal-info.component.ts diff --git a/apps/web/src/app/workspaces/components/capability-request/capability-request.component.html b/apps/web/src/app/workspaces/components/capability-request/capability-request.component.html index 0419b44e0..7e4cabf77 100644 --- a/apps/web/src/app/workspaces/components/capability-request/capability-request.component.html +++ b/apps/web/src/app/workspaces/components/capability-request/capability-request.component.html @@ -45,9 +45,6 @@ (versionEvent)="setVersion($event)" ></app-versions> <br /> - <span>{{productMetadata | json}}</span> - <br /> - <br /> <span id="parameters-label" *ngIf="currentVersion" >Version {{ currentVersion.version_number }} Parameters</span > @@ -117,6 +114,15 @@ [currentVersion]="this.currentVersion" ></internal-notes> <br /> + <span id="metadata-label" *ngIf="productMetadata" + >Version {{ currentVersion.version_number }} Proposal Information</span + > + <app-proposal-info + *ngIf="productMetadata" + id="internal-notes-button" + [productMetadata]="productMetadata" + ></app-proposal-info> + <br /> <app-request-operations id="operations" class="pt-2" diff --git a/apps/web/src/app/workspaces/components/capability-request/components/proposal-info/proposal-info.component.html b/apps/web/src/app/workspaces/components/capability-request/components/proposal-info/proposal-info.component.html new file mode 100644 index 000000000..9c70b651a --- /dev/null +++ b/apps/web/src/app/workspaces/components/capability-request/components/proposal-info/proposal-info.component.html @@ -0,0 +1,42 @@ +<div id="proposal-info-container" +class="container-fluid rounded-top rounded-3 p-3 bg-light" +*ngIf="title && abstract && project_code" +> + <div class="row my-2"> + <div class="col"> + <div class="d-flex justify-content-left"> + <button + id="open-editor-modal-button" + type="button" + class="btn btn-primary" + (click)="open(editor)" + > + Version N Proposal Information + </button> + </div> + </div> + </div> +</div> +<ng-template #editor let-modal> + <div ngb-modal-draggable [ngbModalDraggableHandle]="editormodalheader"> + <div #editormodalheader class="modal-header"> + <h5 class="modal-title" id="modal-label">Proposal Information</h5> + <button type="button" class="close" aria-label="Close" (click)="modal.close('exit')"> + <span aria-hidden="true">×</span> + </button> + </div> + <div class="modal-body" style="line-height:1.5"> + <span class="font-weight-bold">Archive Project Code:</span> {{project_code}} + <br/> + <span class="font-weight-bold">Title:</span> {{title}} + <br/> + <span class="font-weight-bold">Observing Types</span> + <ul> + <li *ngFor="let observing_type of observing_types">{{observing_type}}</li> + </ul> + <span class="font-weight-bold">Abstract</span> + <br/> + <span style="line-height:2">{{abstract}}</span> + </div> + </div> +</ng-template> diff --git a/apps/web/src/app/workspaces/components/capability-request/components/proposal-info/proposal-info.component.scss b/apps/web/src/app/workspaces/components/capability-request/components/proposal-info/proposal-info.component.scss new file mode 100644 index 000000000..e69de29bb diff --git a/apps/web/src/app/workspaces/components/capability-request/components/proposal-info/proposal-info.component.spec.ts b/apps/web/src/app/workspaces/components/capability-request/components/proposal-info/proposal-info.component.spec.ts new file mode 100644 index 000000000..c982b27f0 --- /dev/null +++ b/apps/web/src/app/workspaces/components/capability-request/components/proposal-info/proposal-info.component.spec.ts @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2021 Associated Universities, Inc. Washington DC, USA. + * + * This file is part of NRAO Workspaces. + * + * Workspaces is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Workspaces is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Workspaces. If not, see <https://www.gnu.org/licenses/>. + */ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { ProposalInfoComponent } from './proposal-info.component'; + +describe('ProposalInfoComponent', () => { + let component: ProposalInfoComponent; + let fixture: ComponentFixture<ProposalInfoComponent>; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ ProposalInfoComponent ] + }) + .compileComponents(); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(ProposalInfoComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/apps/web/src/app/workspaces/components/capability-request/components/proposal-info/proposal-info.component.ts b/apps/web/src/app/workspaces/components/capability-request/components/proposal-info/proposal-info.component.ts new file mode 100644 index 000000000..64005eb69 --- /dev/null +++ b/apps/web/src/app/workspaces/components/capability-request/components/proposal-info/proposal-info.component.ts @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2021 Associated Universities, Inc. Washington DC, USA. + * + * This file is part of NRAO Workspaces. + * + * Workspaces is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Workspaces is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Workspaces. If not, see <https://www.gnu.org/licenses/>. + */ +import { Component, OnInit, Input, Output, EventEmitter } from '@angular/core'; +import { NgbModal } from "@ng-bootstrap/ng-bootstrap"; +import { ProductMetadata } from 'src/app/workspaces/model/archive-models'; + +@Component({ + selector: 'app-proposal-info', + templateUrl: './proposal-info.component.html', + styleUrls: ['./proposal-info.component.scss'] +}) +export class ProposalInfoComponent implements OnInit { + @Output() editorOpenEvent = new EventEmitter<boolean>(); + @Input() set productMetadata(productMetadata: ProductMetadata) { + if (productMetadata) { + this.title = productMetadata.title; + this.abstract = productMetadata.abstract; + this.observing_types = productMetadata.observing_types; + this.project_code = productMetadata.project_code; + } + } + + public title: string; + public abstract: string; + public observing_types: Array<string>; + public project_code: string; + + constructor( + private modalService: NgbModal, + ) { } + + public open(content) { + this.modalService.open(content, { ariaLabelledBy: "modal-title", centered: true, scrollable: true }); + } + + ngOnInit(): void {} + +} diff --git a/apps/web/src/app/workspaces/workspaces.module.ts b/apps/web/src/app/workspaces/workspaces.module.ts index 701475af5..a0a8a4b80 100644 --- a/apps/web/src/app/workspaces/workspaces.module.ts +++ b/apps/web/src/app/workspaces/workspaces.module.ts @@ -41,6 +41,7 @@ import {QaControlsComponent} from './components/capability-request/components/qa import {SendEmailComponent} from './components/send-email/send-email.component'; import {CapabilityDataAccessComponent} from "./components/capability-request/components/capability-data-access/capability-data-access.component"; import {InternalNotesComponent} from './components/capability-request/components/./internal-notes/internal-notes.component'; +import { ProposalInfoComponent } from './components/capability-request/components/proposal-info/proposal-info.component' import {WsHeaderComponent} from './ws-header/ws-header.component'; import {WsHomeComponent} from './ws-home/ws-home.component'; import {MatAutocompleteModule} from '@angular/material/autocomplete'; @@ -48,11 +49,13 @@ import {MatFormFieldModule} from '@angular/material/form-field'; import {MatSelectModule} from '@angular/material/select'; import {MatInputModule} from '@angular/material/input'; import {MatPaginatorModule} from '@angular/material/paginator'; +import { NgbModalDraggableModule } from "../shared/ngb-modal-draggable/ngb-modal-draggable.module"; @NgModule({ declarations: [ WorkspacesComponent, CapabilityRequestComponent, + ProposalInfoComponent, RequestHeaderComponent, StatusBadgeComponent, ParametersComponent, @@ -77,6 +80,7 @@ import {MatPaginatorModule} from '@angular/material/paginator'; imports: [ CommonModule, NgbModule, + NgbModalDraggableModule, WorkspacesRoutingModule, ReactiveFormsModule, FormsModule, -- GitLab From 7df611fc663965849f0eb131779d29eaffc26181 Mon Sep 17 00:00:00 2001 From: Sam Kagan <skagan@nrao.edu> Date: Tue, 29 Oct 2024 14:53:13 -0600 Subject: [PATCH 06/10] Got prop-info modal to scroll --- .../components/proposal-info/proposal-info.component.html | 2 +- .../components/proposal-info/proposal-info.component.scss | 8 ++++++++ .../components/proposal-info/proposal-info.component.ts | 3 +-- 3 files changed, 10 insertions(+), 3 deletions(-) diff --git a/apps/web/src/app/workspaces/components/capability-request/components/proposal-info/proposal-info.component.html b/apps/web/src/app/workspaces/components/capability-request/components/proposal-info/proposal-info.component.html index 9c70b651a..985642455 100644 --- a/apps/web/src/app/workspaces/components/capability-request/components/proposal-info/proposal-info.component.html +++ b/apps/web/src/app/workspaces/components/capability-request/components/proposal-info/proposal-info.component.html @@ -18,7 +18,7 @@ class="container-fluid rounded-top rounded-3 p-3 bg-light" </div> </div> <ng-template #editor let-modal> - <div ngb-modal-draggable [ngbModalDraggableHandle]="editormodalheader"> + <div ngb-modal-draggable [ngbModalDraggableHandle]="editormodalheader" class="fix-ngb-modal-scrollable"> <div #editormodalheader class="modal-header"> <h5 class="modal-title" id="modal-label">Proposal Information</h5> <button type="button" class="close" aria-label="Close" (click)="modal.close('exit')"> diff --git a/apps/web/src/app/workspaces/components/capability-request/components/proposal-info/proposal-info.component.scss b/apps/web/src/app/workspaces/components/capability-request/components/proposal-info/proposal-info.component.scss index e69de29bb..0bdce7460 100644 --- a/apps/web/src/app/workspaces/components/capability-request/components/proposal-info/proposal-info.component.scss +++ b/apps/web/src/app/workspaces/components/capability-request/components/proposal-info/proposal-info.component.scss @@ -0,0 +1,8 @@ +/** The style below allows the `scrollable` property on the NgbModal to work with the ngb-modal-draggable directive + Source: https://github.com/ng-bootstrap/ng-bootstrap/issues/3281#issuecomment-509701024 +**/ +.fix-ngb-modal-scrollable { + display: flex; + flex-direction: column; + overflow: hidden; +} diff --git a/apps/web/src/app/workspaces/components/capability-request/components/proposal-info/proposal-info.component.ts b/apps/web/src/app/workspaces/components/capability-request/components/proposal-info/proposal-info.component.ts index 64005eb69..20cc95911 100644 --- a/apps/web/src/app/workspaces/components/capability-request/components/proposal-info/proposal-info.component.ts +++ b/apps/web/src/app/workspaces/components/capability-request/components/proposal-info/proposal-info.component.ts @@ -26,7 +26,6 @@ import { ProductMetadata } from 'src/app/workspaces/model/archive-models'; styleUrls: ['./proposal-info.component.scss'] }) export class ProposalInfoComponent implements OnInit { - @Output() editorOpenEvent = new EventEmitter<boolean>(); @Input() set productMetadata(productMetadata: ProductMetadata) { if (productMetadata) { this.title = productMetadata.title; @@ -46,7 +45,7 @@ export class ProposalInfoComponent implements OnInit { ) { } public open(content) { - this.modalService.open(content, { ariaLabelledBy: "modal-title", centered: true, scrollable: true }); + this.modalService.open(content, { ariaLabelledBy: "modal-title", scrollable: true }); } ngOnInit(): void {} -- GitLab From 823c22c60abad424928a1f7a2e99f78153b9b0de Mon Sep 17 00:00:00 2001 From: Sam Kagan <skagan@nrao.edu> Date: Tue, 29 Oct 2024 15:03:59 -0600 Subject: [PATCH 07/10] Got body to scroll in the background while modal is open --- .../components/proposal-info/proposal-info.component.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/apps/web/src/app/workspaces/components/capability-request/components/proposal-info/proposal-info.component.ts b/apps/web/src/app/workspaces/components/capability-request/components/proposal-info/proposal-info.component.ts index 20cc95911..8181d5ec9 100644 --- a/apps/web/src/app/workspaces/components/capability-request/components/proposal-info/proposal-info.component.ts +++ b/apps/web/src/app/workspaces/components/capability-request/components/proposal-info/proposal-info.component.ts @@ -46,6 +46,9 @@ export class ProposalInfoComponent implements OnInit { public open(content) { this.modalService.open(content, { ariaLabelledBy: "modal-title", scrollable: true }); + // NgbModal prohibits the body from scrolling by default. + // Prevent that here so that DAs can see all the other data on the page while the modal is open + document.body.style.overflow = "inherit"; } ngOnInit(): void {} -- GitLab From 464adf98e8f028fd572d0f9e858d8b3884df2bef Mon Sep 17 00:00:00 2001 From: Sam Kagan <skagan@nrao.edu> Date: Tue, 29 Oct 2024 17:40:02 -0600 Subject: [PATCH 08/10] Polished up CSS for prop-info modal --- .../proposal-info.component.html | 31 ++++++++++++------- .../proposal-info.component.scss | 12 +++++++ 2 files changed, 31 insertions(+), 12 deletions(-) diff --git a/apps/web/src/app/workspaces/components/capability-request/components/proposal-info/proposal-info.component.html b/apps/web/src/app/workspaces/components/capability-request/components/proposal-info/proposal-info.component.html index 985642455..426ec8a4a 100644 --- a/apps/web/src/app/workspaces/components/capability-request/components/proposal-info/proposal-info.component.html +++ b/apps/web/src/app/workspaces/components/capability-request/components/proposal-info/proposal-info.component.html @@ -25,18 +25,25 @@ class="container-fluid rounded-top rounded-3 p-3 bg-light" <span aria-hidden="true">×</span> </button> </div> - <div class="modal-body" style="line-height:1.5"> - <span class="font-weight-bold">Archive Project Code:</span> {{project_code}} - <br/> - <span class="font-weight-bold">Title:</span> {{title}} - <br/> - <span class="font-weight-bold">Observing Types</span> - <ul> - <li *ngFor="let observing_type of observing_types">{{observing_type}}</li> - </ul> - <span class="font-weight-bold">Abstract</span> - <br/> - <span style="line-height:2">{{abstract}}</span> + <div class="modal-body"> + <div class="row flex-column"> + <span class="col font-weight-bold">Archive Project Code</span> + <span class="col">{{project_code}}</span> + </div> + <div class="row flex-column"> + <span class="col font-weight-bold">Title</span> + <span class="col">{{title}}</span> + </div> + <div class="row flex-column"> + <span class="col font-weight-bold">Observing Types</span> + <ul class="col"> + <li *ngFor="let observing_type of observing_types">{{observing_type}}</li> + </ul> + </div> + <div class="row flex-column"> + <span class="col font-weight-bold">Abstract</span> + <span class="col" id="proposal-info-abstract">{{abstract}}</span> + </div> </div> </div> </ng-template> diff --git a/apps/web/src/app/workspaces/components/capability-request/components/proposal-info/proposal-info.component.scss b/apps/web/src/app/workspaces/components/capability-request/components/proposal-info/proposal-info.component.scss index 0bdce7460..cba4aebd3 100644 --- a/apps/web/src/app/workspaces/components/capability-request/components/proposal-info/proposal-info.component.scss +++ b/apps/web/src/app/workspaces/components/capability-request/components/proposal-info/proposal-info.component.scss @@ -6,3 +6,15 @@ flex-direction: column; overflow: hidden; } + +.modal-body .row { + margin-bottom: 0.5em; +} + +.modal-body ul { + padding-left: 3em; +} + +#proposal-info-abstract { + line-height: 2; +} -- GitLab From 3a7aeb52455e930efbc7040009a8703cc96cf493 Mon Sep 17 00:00:00 2001 From: Sam Kagan <skagan@nrao.edu> Date: Wed, 30 Oct 2024 09:30:18 -0600 Subject: [PATCH 09/10] Fixed bug when currVer changes, tweaked other stuff --- .../capability-request.component.html | 10 ++++++---- .../capability-request/capability-request.component.ts | 8 ++++---- .../proposal-info/proposal-info.component.html | 2 +- .../proposal-info/proposal-info.component.scss | 2 +- 4 files changed, 12 insertions(+), 10 deletions(-) diff --git a/apps/web/src/app/workspaces/components/capability-request/capability-request.component.html b/apps/web/src/app/workspaces/components/capability-request/capability-request.component.html index 7e4cabf77..5ba199972 100644 --- a/apps/web/src/app/workspaces/components/capability-request/capability-request.component.html +++ b/apps/web/src/app/workspaces/components/capability-request/capability-request.component.html @@ -114,11 +114,13 @@ [currentVersion]="this.currentVersion" ></internal-notes> <br /> - <span id="metadata-label" *ngIf="productMetadata" - >Version {{ currentVersion.version_number }} Proposal Information</span - > + <!--Because productMetadata is populated on setVersion(), currentVersion should always exist if pM exists. + The checks below are just added security. + --> + <span id="metadata-label" *ngIf="productMetadata && currentVersion" + >Version {{ currentVersion.version_number }} Proposal Information</span> <app-proposal-info - *ngIf="productMetadata" + *ngIf="productMetadata && currentVersion" id="internal-notes-button" [productMetadata]="productMetadata" ></app-proposal-info> diff --git a/apps/web/src/app/workspaces/components/capability-request/capability-request.component.ts b/apps/web/src/app/workspaces/components/capability-request/capability-request.component.ts index 6dee9e81f..daa2c6200 100644 --- a/apps/web/src/app/workspaces/components/capability-request/capability-request.component.ts +++ b/apps/web/src/app/workspaces/components/capability-request/capability-request.component.ts @@ -94,7 +94,7 @@ export class CapabilityRequestComponent implements OnInit, OnDestroy { ]; } if (!this.currentVersion) { - this.currentVersion = this.latestVersion; + this.setVersion(this.latestVersion); } if (this.currentVersion.current_execution) { this.capabilityExecution = this.currentVersion.current_execution; @@ -144,9 +144,6 @@ export class CapabilityRequestComponent implements OnInit, OnDestroy { this.currentVersion.version_number === this.latestVersion.version_number ) { this.setVersion(this.latestVersion); - // Refresh product metadata whenever select version changes - // since it owns the request's product locator - this.archiveService.getMetadataForProduct(this.latestVersion.parameters["product_locator"] as string).subscribe(this.productMetadataObserver); } } else { console.error("Current version returned undefined."); @@ -198,6 +195,9 @@ export class CapabilityRequestComponent implements OnInit, OnDestroy { setVersion(version: CapabilityVersion): void { this.currentVersion = version; + // Refresh product metadata whenever select version changes + // since it owns the request's product locator + this.archiveService.getMetadataForProduct(this.currentVersion.parameters["product_locator"] as string).subscribe(this.productMetadataObserver); } catchOpenEditorEventFromChild(isEditorOpen: boolean): void { diff --git a/apps/web/src/app/workspaces/components/capability-request/components/proposal-info/proposal-info.component.html b/apps/web/src/app/workspaces/components/capability-request/components/proposal-info/proposal-info.component.html index 426ec8a4a..d1b5ec86c 100644 --- a/apps/web/src/app/workspaces/components/capability-request/components/proposal-info/proposal-info.component.html +++ b/apps/web/src/app/workspaces/components/capability-request/components/proposal-info/proposal-info.component.html @@ -11,7 +11,7 @@ class="container-fluid rounded-top rounded-3 p-3 bg-light" class="btn btn-primary" (click)="open(editor)" > - Version N Proposal Information + Proposal Information </button> </div> </div> diff --git a/apps/web/src/app/workspaces/components/capability-request/components/proposal-info/proposal-info.component.scss b/apps/web/src/app/workspaces/components/capability-request/components/proposal-info/proposal-info.component.scss index cba4aebd3..d69489202 100644 --- a/apps/web/src/app/workspaces/components/capability-request/components/proposal-info/proposal-info.component.scss +++ b/apps/web/src/app/workspaces/components/capability-request/components/proposal-info/proposal-info.component.scss @@ -4,7 +4,7 @@ .fix-ngb-modal-scrollable { display: flex; flex-direction: column; - overflow: hidden; + overflow-y: hidden; } .modal-body .row { -- GitLab From fb89a2512b1335fa5a251cadbef08becb73012d2 Mon Sep 17 00:00:00 2001 From: Sam Kagan <skagan@nrao.edu> Date: Thu, 31 Oct 2024 13:50:57 -0600 Subject: [PATCH 10/10] Added cleanup for product-metadata Observable, removed unused imports Also fixed a bug where title for Prop Info would show up even when button wouldn't --- .../capability-request.component.html | 4 ++-- .../capability-request.component.ts | 11 ++++++++++- .../proposal-info/proposal-info.component.ts | 2 +- .../src/app/workspaces/services/archive.service.ts | 1 - 4 files changed, 13 insertions(+), 5 deletions(-) diff --git a/apps/web/src/app/workspaces/components/capability-request/capability-request.component.html b/apps/web/src/app/workspaces/components/capability-request/capability-request.component.html index 5ba199972..cd87651b1 100644 --- a/apps/web/src/app/workspaces/components/capability-request/capability-request.component.html +++ b/apps/web/src/app/workspaces/components/capability-request/capability-request.component.html @@ -117,10 +117,10 @@ <!--Because productMetadata is populated on setVersion(), currentVersion should always exist if pM exists. The checks below are just added security. --> - <span id="metadata-label" *ngIf="productMetadata && currentVersion" + <span id="metadata-label" *ngIf="canDisplayProposalInformation()" >Version {{ currentVersion.version_number }} Proposal Information</span> <app-proposal-info - *ngIf="productMetadata && currentVersion" + *ngIf="canDisplayProposalInformation()" id="internal-notes-button" [productMetadata]="productMetadata" ></app-proposal-info> diff --git a/apps/web/src/app/workspaces/components/capability-request/capability-request.component.ts b/apps/web/src/app/workspaces/components/capability-request/capability-request.component.ts index daa2c6200..1dd84ee21 100644 --- a/apps/web/src/app/workspaces/components/capability-request/capability-request.component.ts +++ b/apps/web/src/app/workspaces/components/capability-request/capability-request.component.ts @@ -197,7 +197,12 @@ export class CapabilityRequestComponent implements OnInit, OnDestroy { this.currentVersion = version; // Refresh product metadata whenever select version changes // since it owns the request's product locator - this.archiveService.getMetadataForProduct(this.currentVersion.parameters["product_locator"] as string).subscribe(this.productMetadataObserver); + this.archiveService.getMetadataForProduct(this.currentVersion.parameters["product_locator"] as string) + .pipe( + takeUntil(this.ngUnsubscribe), + repeatWhen(() => this.ngUnsubscribe) + ) + .subscribe(this.productMetadataObserver); } catchOpenEditorEventFromChild(isEditorOpen: boolean): void { @@ -232,6 +237,10 @@ export class CapabilityRequestComponent implements OnInit, OnDestroy { .subscribe(this.requestMessageObserver); } + public canDisplayProposalInformation() { + return this.productMetadata && this.productMetadata.title && this.productMetadata.abstract && this.productMetadata.project_code && this.currentVersion; + } + ngOnInit(): void { this.urlService.previousUrl$.subscribe((previousUrl: string) => { this.previousUrlString = previousUrl; diff --git a/apps/web/src/app/workspaces/components/capability-request/components/proposal-info/proposal-info.component.ts b/apps/web/src/app/workspaces/components/capability-request/components/proposal-info/proposal-info.component.ts index 8181d5ec9..313bcdbbe 100644 --- a/apps/web/src/app/workspaces/components/capability-request/components/proposal-info/proposal-info.component.ts +++ b/apps/web/src/app/workspaces/components/capability-request/components/proposal-info/proposal-info.component.ts @@ -16,7 +16,7 @@ * You should have received a copy of the GNU General Public License * along with Workspaces. If not, see <https://www.gnu.org/licenses/>. */ -import { Component, OnInit, Input, Output, EventEmitter } from '@angular/core'; +import { Component, OnInit, Input } from '@angular/core'; import { NgbModal } from "@ng-bootstrap/ng-bootstrap"; import { ProductMetadata } from 'src/app/workspaces/model/archive-models'; diff --git a/apps/web/src/app/workspaces/services/archive.service.ts b/apps/web/src/app/workspaces/services/archive.service.ts index 03ffc572e..f98c0de09 100644 --- a/apps/web/src/app/workspaces/services/archive.service.ts +++ b/apps/web/src/app/workspaces/services/archive.service.ts @@ -21,7 +21,6 @@ import { HttpClient, HttpParams } from '@angular/common/http'; import { Injectable } from '@angular/core'; import { Observable } from 'rxjs'; import { environment } from 'src/environments/environment'; -import { UpdateOutputFileStampsProject } from 'typescript'; import { ProductMetadata } from '../model/archive-models'; @Injectable({ -- GitLab