Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feature/deseng751: Added ability to use an external link as if it is an internal file. #126

Merged
merged 3 commits into from
Jan 15, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,10 @@
### 1.12.0 Jan 9, 2025
* Added the ability to add an external link as if it is an internal file. [DESENG-751](https://apps.itsm.gov.bc.ca/jira/browse/DESENG-751)
* Functionality works in all file views, including file list, file details, file add/edit, comment period view, comment period edit.
* All file list functionality should work, including open/download, publish, unpublish, edit, and delete.
* All upper-right context menu items are working from file list, file details, etc.
* No multi-edit for external link files yet.

### 1.11.0 Nov 26, 2024
* Made agreements optional when creating or editing projects, adjusted project overview display. [DESENG-742](https://apps.itsm.gov.bc.ca/jira/browse/DESENG-742)
* Fixed broken repository link for XLSX (Git has been disabled due to download traffic).
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "landuseplanning-admin",
"version": "1.11.0",
"version": "1.12.0",
"license": "Apache-2.0",
"scripts": {
"ng": "ng",
Expand Down
8 changes: 4 additions & 4 deletions src/app/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,11 +66,9 @@ import { AddEditContactComponent } from './contacts/add-edit-contact/add-edit-co
import { OrganizationsComponent } from './organizations/organizations.component';
import { OrganizationsTableRowsComponent } from './organizations/organizations-table-rows/organizations-table-rows.component';
import { AddEditOrganizationComponent } from './organizations/add-edit-organization/add-edit-organization.component';
import { LinkOrganizationComponent } from './shared/components/link-organization/link-organization.component';
import { LinkOrganizationTableRowsComponent } from './shared/components/link-organization/link-organization-table-rows/link-organization-table-rows.component';
import { ContactSelectComponent } from './shared/components/contact-select/contact-select.component';
import { ContactSelectTableRowsComponent } from './shared/components/contact-select/contact-select-table-rows/contact-select-table-rows.component';
import { FileUploadModalComponent } from './file-upload-modal/file-upload-modal.component';
import { LinkService } from './services/link.service';
import { ExternalLinkComponent } from './project/project-documents/external-link/external-link.component';

export function kcFactory(keycloakService: KeycloakService) {
return () => keycloakService.init();
Expand Down Expand Up @@ -106,6 +104,7 @@ export function kcFactory(keycloakService: KeycloakService) {
UserTableRowsComponent,
EnvBannerComponent,
FileUploadModalComponent,
ExternalLinkComponent,
],
imports: [
BrowserAnimationsModule,
Expand Down Expand Up @@ -157,6 +156,7 @@ export function kcFactory(keycloakService: KeycloakService) {
SurveyResponseService,
SurveyBuilderService,
UserService,
LinkService,
],
bootstrap: [AppComponent]
})
Expand Down
25 changes: 25 additions & 0 deletions src/app/models/externalLink.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
export class ExternalLink {
_id: string;
project: string;
displayName: string;
externalLink: string;
section: string;
dateAdded: Date;
dateUpdated: Date;
description: string;
projectPhase: string;
checkbox: boolean;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just curious what this property is


constructor(obj?: any) {
this._id = obj && obj._id || null;
this.project = obj && obj.project || null;
this.displayName = obj && obj.displayName || null;
this.externalLink = obj && obj.externalLink || null;
this.section = obj && obj.section || null;
this.dateAdded = obj && obj.dateAdded || null;
this.dateUpdated = obj && obj.dateUpdated || null;
this.description = obj && obj.description || null;
this.projectPhase = obj && obj.projectPhase || null;
this.checkbox = false || null;
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Component, Input, OnInit, OnChanges, OnDestroy } from '@angular/core';
import { Subject } from 'rxjs';
import { forkJoin, Subject } from 'rxjs';
import { MatSnackBar } from '@angular/material/snack-bar';
import { Router, ActivatedRoute } from '@angular/router';
import { NgxSmartModalService } from 'ngx-smart-modal';
Expand All @@ -13,6 +13,7 @@ import { ApiService } from 'app/services/api';
import { CommentPeriodService } from 'app/services/commentperiod.service';
import { StorageService } from 'app/services/storage.service';
import { DocumentService } from 'app/services/document.service';
import { LinkService } from 'app/services/link.service';

@Component({
selector: 'app-comment-period-details-tab',
Expand Down Expand Up @@ -41,6 +42,7 @@ export class CommentPeriodDetailsTabComponent implements OnInit, OnChanges, OnDe
constructor(
private api: ApiService,
private surveyService: SurveyService,
private externalLinkService: LinkService,
private commentPeriodService: CommentPeriodService,
private documentService: DocumentService,
private route: ActivatedRoute,
Expand All @@ -63,11 +65,11 @@ export class CommentPeriodDetailsTabComponent implements OnInit, OnChanges, OnDe
this.projectId = this.storageService.state.currentProject.data._id;

if (this.commentPeriod.relatedDocuments.length > 0) {
this.documentService.getByMultiId(this.commentPeriod.relatedDocuments)
forkJoin([this.documentService.getByMultiId(this.commentPeriod.relatedDocuments), this.externalLinkService.getByMultiId(this.commentPeriod.relatedDocuments)])
.takeUntil(this.ngUnsubscribe)
.subscribe(
data => {
this.commentPeriodDocs = data;
this.commentPeriodDocs = [...data[0] || [], ...data[1] || []];
}
);
}
Expand Down Expand Up @@ -272,7 +274,7 @@ export class CommentPeriodDetailsTabComponent implements OnInit, OnChanges, OnDe
* @returns {Promise<void>}
*/
public downloadDocument(document) {
return this.api.downloadDocument(document);
return document.externalLink ? window.open(document.externalLink) : this.api.downloadDocument(document);
}

/**
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { Injectable } from '@angular/core';
import { Resolve, ActivatedRouteSnapshot } from '@angular/router';
import { Observable } from 'rxjs';
import { forkJoin, Observable } from 'rxjs';

import { SearchService } from 'app/services/search.service';
import { StorageService } from 'app/services/storage.service';
Expand All @@ -12,6 +12,26 @@ export class AddDocumentsResolver implements Resolve<Observable<object>> {
private storageService: StorageService
) { }

/**
* Retrieves documents or external links
*
* @param {ActivatedRouteSnapshot} route The route to get params from.
* @param {string} schema The schema type to use, either 'Document' or 'ExternalLink'
* @param {string} projectId The project ID of the documents you wish to retrieve
* @returns {Observable<Object>}
*/
getFiles = (route: ActivatedRouteSnapshot, schema: string, projectId: string): Observable<Object> => {
const keys = route.params.keywords || '';
const dataset = schema;
const fields = [{ 'name': 'project', 'value': projectId }];
const pageNum = 1;
const pageSize = 1000;
const sortBy = route.queryParams['sortBy'] || null;
const queryModifier = {};
const populate = true;
return this.searchService.getSearchResults(keys, dataset, fields, pageNum, pageSize, sortBy, queryModifier, populate);
}

/**
* Get route params and make a request to the API to get a set of
* documents(files) that match the request params.
Expand All @@ -20,24 +40,13 @@ export class AddDocumentsResolver implements Resolve<Observable<object>> {
* @returns {Observable<Object>}
*/
resolve(route: ActivatedRouteSnapshot): Observable<object> {
let projectId;
if (this.storageService.state.currentProject) {
projectId = this.storageService.state.currentProject.data._id;
} else {
projectId = route.parent.parent.params.projId;
}

const pageNum = Number(route.queryParams['pageNum'] ? route.queryParams['pageNum'] : 1);
const pageSize = Number(route.queryParams['pageSize'] ? route.queryParams['pageSize'] : 10);
const sortBy = route.queryParams['sortBy'] ? route.queryParams['sortBy'] : null;
const keywords = route.params.keywords;
return this.searchService.getSearchResults(keywords,
'Document',
[{ 'name': 'project', 'value': projectId }],
pageNum,
pageSize,
sortBy,
{ documentSource: 'PROJECT' },
);
const projectId = this.storageService.state.currentProject?.data?._id || route.parent.parent.params.projId;
const documents = this.getFiles(route, 'Document', projectId);
const externalLinks = this.getFiles(route, 'ExternalLink', projectId);
return forkJoin({
documents: documents,
externalLinks: externalLinks,
});
}
}

Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@
<i class="material-icons">insert_drive_file</i>
</span>
<span class="cell name" [title]="doc.displayName || ''">
<span class="cell__txt-content">{{doc.documentFileName}}</span>
<span class="cell__txt-content">{{doc.documentFileName || doc.externalLink}}</span>
</span>
<span class="cell actions">
<button class="btn btn-icon" type="button" title="Delete this document" (click)="removeSelectedDoc(doc)">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,10 @@ import { Component, OnInit, ChangeDetectorRef, OnDestroy, ChangeDetectionStrateg
import { PlatformLocation } from '@angular/common';
import { Router, ActivatedRoute } from '@angular/router';
import { Subject } from 'rxjs';

import { Document } from 'app/models/document';
import { SearchTerms } from 'app/models/search';

import { ApiService } from 'app/services/api';
import { SearchService } from 'app/services/search.service';
import { StorageService } from 'app/services/storage.service';

import { AddDocumentTableRowsComponent } from './add-document-table-rows/add-document-table-rows.component';
import { TableObject } from 'app/shared/components/table-template/table-object';
import { TableParamsObject } from 'app/shared/components/table-template/table-params-object';
Expand All @@ -25,7 +21,8 @@ import { encode } from 'punycode';
export class AddDocumentComponent implements OnInit, OnDestroy {
public terms = new SearchTerms();
private ngUnsubscribe: Subject<boolean> = new Subject<boolean>();
public documents: Document[] = null;
public documents = null;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just curious why you removed the type for this one. Maybe I can help restore it?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As discussed, this is to prevent errors that arise from combining types in an array and then referencing properties. It could be handled more strictly with more verbosity, but that would not be in line with the degree of typing that is present in the existing codebase.

public documentVault = null;
public loading = true;

public isEditing = false;
Expand Down Expand Up @@ -115,19 +112,18 @@ export class AddDocumentComponent implements OnInit, OnDestroy {
this.tableParams.keywords = params.keywords;
});

this.originalSelectedDocs = Object.assign([], this.storageService.state.selectedDocumentsForCP.data);
this.originalSelectedDocs = Object.assign([], this.storageService.state.selectedDocumentsForCP?.data);

this.route.data
.takeUntil(this.ngUnsubscribe)
.subscribe((res: any) => {
if (res) {
if (res.documents[0].data.meta && res.documents[0].data.meta.length > 0) {
this.tableParams.totalListItems = res.documents[0].data.meta[0].searchResultsTotal;
this.documents = res.documents[0].data.searchResults;
} else {
this.tableParams.totalListItems = 0;
this.documents = [];
}
const documents = res.documents?.documents?.at(0) || [];
const links = res.documents?.externalLinks?.at(0) || [];
const combinedResults = [...documents?.data?.searchResults, ...links?.data?.searchResults];
this.tableParams.totalListItems = combinedResults?.length || 0;
const sortedResults = this.sortDocuments(combinedResults);
this.documents = this.documentVault = sortedResults;
this.setDocumentRowData();
this.loading = false;
this._changeDetectionRef.detectChanges();
Expand All @@ -140,6 +136,59 @@ export class AddDocumentComponent implements OnInit, OnDestroy {
}
}

/**
* Sorts documents based on the current sort selection.
*
* @param {any} documents The combined documents, including actual documents and external links.
* @return {any}
*
*/
public sortDocuments = (documents: any[]) => {
const sortData = this.tableParams.sortBy || '-datePosted';
const sortDir = '-' === this.tableParams.sortBy.charAt(0) ? -1 : 1;
const sortBy = sortData.substring(1);
const mappedResults = documents.map(doc => this.mapRowData(doc));
if ('displayName' === sortBy || 'internalExt' === sortBy) {
// If sorting strings then convert to lower case.
mappedResults.sort((a, b) => {
if (a[sortBy].toLowerCase() < b[sortBy].toLowerCase()) return -1 * sortDir;
if (a[sortBy].toLowerCase() > b[sortBy].toLowerCase()) return 1 * sortDir;
return 0;
});
} else {
mappedResults.sort((a, b) => {
if (a[sortBy] < b[sortBy]) return -1 * sortDir;
if (a[sortBy] > b[sortBy]) return 1 * sortDir;
return 0;
});
}
return mappedResults || [];
}

/**
* Maps row data to a format that is familiar for the table.
*
* @param {any} file The file to be mapped. "Any" because it could be a Document or ExternalLink.
* @returns {object}
*
*/
mapRowData(file) {
return {
displayName: file.displayName,
documentFileName: file.documentFileName || file.externalLink || '',
internalSize: file.internalSize || null,
internalExt: file.internalExt || 'external',
datePosted: file.datePosted || file.dateAdded,
status: file.read.includes('public') ? 'Published' : 'Not Published',
_id: file._id,
project: file.project,
read: file.read,
projectPhase: file.projectPhase,
description: file.description,
section: file.section,
}
}

/**
* Handle various actions for modifying/working with documents(files).
* This includes, copying their links, selecting, downloading.
Expand Down Expand Up @@ -246,7 +295,7 @@ export class AddDocumentComponent implements OnInit, OnDestroy {
*/
setDocumentRowData() {
let documentList = [];
if (this.documents && this.documents.length > 0) {
if (this.documents?.length > 0) {
this.documents.forEach(document => {
documentList.push(
{
Expand All @@ -267,19 +316,23 @@ export class AddDocumentComponent implements OnInit, OnDestroy {
}

/**
* When the user sorts the table by column, update the table params
* with the sort type and direction(+,-), then get a list of documents(files)
* sorted accordingly.
* Sort existing results by column (name, date, size, type, etc.)
*
* @param {string} column What value to sort by.
* @return {void}
* @param {string} column The column to sort by.
*/
setColumnSort(column) {
setColumnSort(column: string): void {
if (this.tableParams.sortBy.charAt(0) === '+') {
this.tableParams.sortBy = '-' + column;
} else {
this.tableParams.sortBy = '+' + column;
}
window.scrollTo(0, 0);
this.loading = true;
this.documentVault = this.sortDocuments(this.documentVault);
this.tableTemplateUtils.updateUrl(this.tableParams.sortBy, this.tableParams.currentPage, this.tableParams.pageSize, this.tableParams.filter, this.tableParams.keywords || '');
this.setDocumentRowData();
this.loading = false;
this._changeDetectionRef.detectChanges();
this.getPaginatedDocs(this.tableParams.currentPage);
}

Expand Down Expand Up @@ -316,30 +369,19 @@ export class AddDocumentComponent implements OnInit, OnDestroy {
* @param {number} pageNumber The page number of documents to get.
* @return {void}
*/
getPaginatedDocs(pageNumber) {
// Go to top of page after clicking to a different page.
public getPaginatedDocs(pageNumber: number): void {
window.scrollTo(0, 0);
this.loading = true;

this.tableParams = this.tableTemplateUtils.updateTableParams(this.tableParams, pageNumber, this.tableParams.sortBy);

this.searchService.getSearchResults(
this.tableParams.keywords || '',
'Document',
[{ 'name': 'project', 'value': this.currentProject._id }],
pageNumber,
this.tableParams.pageSize,
this.tableParams.sortBy,
{ documentSource: 'PROJECT' })
.takeUntil(this.ngUnsubscribe)
.subscribe((res: any) => {
this.tableParams.totalListItems = res[0].data.meta[0].searchResultsTotal;
this.documents = res[0].data.searchResults;
this.tableTemplateUtils.updateUrl(this.tableParams.sortBy, this.tableParams.currentPage, this.tableParams.pageSize, null, this.tableParams.keywords || '');
this.setDocumentRowData();
this.loading = false;
this._changeDetectionRef.detectChanges();
});
const startIndex = (pageNumber - 1) * this.tableParams.pageSize;
const endIndex = startIndex + this.tableParams.pageSize;
if (endIndex && 0 < this.documentVault.length) {
this.documents = this.documentVault.slice(startIndex, endIndex);
this.tableTemplateUtils.updateUrl(this.tableParams.sortBy, this.tableParams.currentPage, this.tableParams.pageSize, this.tableParams.filter, this.tableParams.keywords || '');
this.setDocumentRowData();
this.loading = false;
this._changeDetectionRef.detectChanges();
}
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -156,7 +156,7 @@ <h2 class="h2">Related Documents</h2>
<i class="material-icons">insert_drive_file</i>
</span>
<span class="cell name" [title]="doc.displayName || ''">
<span class="cell__txt-content">{{doc.documentFileName}}</span>
<span class="cell__txt-content">{{doc.documentFileName || doc.externalLink}}</span>
</span>
<span class="cell actions">
<button class="btn btn-icon" type="button" title="Delete this document"
Expand Down
Loading
Loading