Skip to content

Commit e455e31

Browse files
authored
[ENG-11260] My Projects Page Improvements (#1033)
* [ENG-11380] Project Overview Page UI Improvements (#1014) * [ENG-11451] My Projects Page: Privacy Filter (#1015) * [ENG-11452] My Projects Page: Download Button (#1018)
1 parent f45357b commit e455e31

48 files changed

Lines changed: 1874 additions & 225 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

src/app/features/files/components/file-metadata/file-metadata.component.spec.ts

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -133,13 +133,11 @@ describe('FileMetadataComponent', () => {
133133

134134
it('should open metadata url when file guid exists', () => {
135135
setup({ routeParams: { fileGuid: 'guid-123' } });
136-
const focus = vi.fn();
137-
const openSpy = vi.spyOn(window, 'open').mockReturnValue({ focus } as unknown as Window);
136+
const openSpy = vi.spyOn(window, 'open').mockReturnValue(null);
138137

139138
component.downloadFileMetadata();
140139

141-
expect(openSpy).toHaveBeenCalledWith(expect.stringMatching(/\/metadata\/guid-123$/));
142-
expect(focus).toHaveBeenCalled();
140+
expect(openSpy).toHaveBeenCalledWith(expect.stringMatching(/\/metadata\/guid-123$/), '_blank');
143141
});
144142

145143
it('should not open metadata url when file guid is missing', () => {

src/app/features/files/components/file-metadata/file-metadata.component.ts

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,9 @@ import { ChangeDetectionStrategy, Component, DestroyRef, inject } from '@angular
1111
import { takeUntilDestroyed, toSignal } from '@angular/core/rxjs-interop';
1212
import { ActivatedRoute, Router } from '@angular/router';
1313

14-
import { ENVIRONMENT } from '@core/provider/environment.provider';
1514
import { LanguageLabelPipe } from '@osf/shared/pipes/language-label.pipe';
1615
import { CustomDialogService } from '@osf/shared/services/custom-dialog.service';
16+
import { MetadataRecordsService } from '@osf/shared/services/metadata-records.service';
1717
import { ViewOnlyLinkHelperService } from '@osf/shared/services/view-only-link-helper.service';
1818

1919
import { FileMetadataFields } from '../../constants';
@@ -33,7 +33,7 @@ export class FileMetadataComponent {
3333
private readonly router = inject(Router);
3434
private readonly destroyRef = inject(DestroyRef);
3535
private readonly customDialogService = inject(CustomDialogService);
36-
private readonly environment = inject(ENVIRONMENT);
36+
private readonly metadataRecordsService = inject(MetadataRecordsService);
3737
private readonly viewOnlyService = inject(ViewOnlyLinkHelperService);
3838

3939
private readonly actions = createDispatchMap({ setFileMetadata: SetFileMetadata });
@@ -49,8 +49,10 @@ export class FileMetadataComponent {
4949
readonly metadataFields = FileMetadataFields;
5050

5151
downloadFileMetadata(): void {
52-
if (this.fileGuid()) {
53-
window.open(`${this.environment.webUrl}/metadata/${this.fileGuid()}`)?.focus();
52+
const fileGuid = this.fileGuid();
53+
54+
if (fileGuid) {
55+
this.metadataRecordsService.downloadMetadata(fileGuid);
5456
}
5557
}
5658

src/app/features/files/components/files-tree-explorer/files-tree-explorer.component.spec.ts

Lines changed: 16 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -13,15 +13,13 @@ import { FileMenuType } from '@osf/shared/enums/file-menu-type.enum';
1313
import { CurrentResourceType } from '@osf/shared/enums/resource-type.enum';
1414
import { FileFolderModel } from '@osf/shared/models/files/file-folder.model';
1515
import { FileLabelModel } from '@osf/shared/models/files/file-label.model';
16-
import { DataciteService } from '@osf/shared/services/datacite/datacite.service';
17-
import { FilesService } from '@osf/shared/services/files.service';
16+
import { FileDownloadService } from '@osf/shared/services/file-download.service';
1817
import { FilesShareEmbedService } from '@osf/shared/services/files-share-embed.service';
1918
import { ViewOnlyLinkHelperService } from '@osf/shared/services/view-only-link-helper.service';
2019

2120
import { FileModelMock } from '@testing/mocks/file.model.mock';
2221
import { provideOSFCore } from '@testing/osf.testing.provider';
23-
import { DataciteServiceMock, DataciteServiceMockType } from '@testing/providers/datacite.service.mock';
24-
import { FilesServiceMock, FilesServiceMockType } from '@testing/providers/files-service.mock';
22+
import { FileDownloadServiceMock, FileDownloadServiceMockType } from '@testing/providers/file-download-service.mock';
2523
import {
2624
FilesShareEmbedServiceMock,
2725
FilesShareEmbedServiceMockType,
@@ -37,8 +35,7 @@ describe('FilesTreeExplorerComponent', () => {
3735
let component: FilesTreeExplorerComponent;
3836
let fixture: ComponentFixture<FilesTreeExplorerComponent>;
3937
let routerMock: RouterMockType;
40-
let filesService: FilesServiceMockType;
41-
let dataciteService: DataciteServiceMockType;
38+
let fileDownloadService: FileDownloadServiceMockType;
4239
let filesShareEmbedService: FilesShareEmbedServiceMockType;
4340
let viewOnlyHelper: ViewOnlyLinkHelperMockType;
4441

@@ -62,8 +59,7 @@ describe('FilesTreeExplorerComponent', () => {
6259

6360
function setup() {
6461
routerMock = RouterMockBuilder.create().withUrl('/node-1/files').build();
65-
filesService = FilesServiceMock.simple();
66-
dataciteService = DataciteServiceMock.simple();
62+
fileDownloadService = FileDownloadServiceMock.simple();
6763
viewOnlyHelper = ViewOnlyLinkHelperMock.simple(false);
6864
filesShareEmbedService = FilesShareEmbedServiceMock.simple();
6965

@@ -75,8 +71,7 @@ describe('FilesTreeExplorerComponent', () => {
7571
providers: [
7672
provideOSFCore(),
7773
MockProvider(Router, routerMock),
78-
MockProvider(FilesService, filesService),
79-
MockProvider(DataciteService, dataciteService),
74+
MockProvider(FileDownloadService, fileDownloadService),
8075
MockProvider(FilesShareEmbedService, filesShareEmbedService),
8176
MockProvider(ViewOnlyLinkHelperService, viewOnlyHelper),
8277
],
@@ -244,14 +239,20 @@ describe('FilesTreeExplorerComponent', () => {
244239
expect(emitSpy).toHaveBeenNthCalledWith(2, { file, action: MoveCopyAction.Copy });
245240
});
246241

247-
it('should download folder with resolved zip link', () => {
242+
it('should download folder from file menu action', () => {
248243
setup();
249-
const openSpy = vi.spyOn(window, 'open').mockReturnValue({ focus: vi.fn() } as unknown as Window);
244+
const file = FileModelMock.simple({
245+
kind: FileKind.Folder,
246+
links: { ...FileModelMock.simple().links, upload: '/upload-folder' },
247+
});
250248

251-
component.downloadFolder('/upload-folder');
249+
component.onFileMenuAction({ value: FileMenuType.Download }, file);
252250

253-
expect(filesService.getFolderDownloadLink).toHaveBeenCalledWith('/upload-folder');
254-
expect(openSpy).toHaveBeenCalledWith('/upload-folder?zip=', '_blank');
251+
expect(fileDownloadService.downloadFileOrFolder).toHaveBeenCalledWith({
252+
resourceId: 'node-1',
253+
resourceType: CurrentResourceType.Projects,
254+
file,
255+
});
255256
});
256257

257258
it('should open share link in new tab for non self target', () => {

src/app/features/files/components/files-tree-explorer/files-tree-explorer.component.ts

Lines changed: 7 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@ import {
88
ChangeDetectionStrategy,
99
Component,
1010
computed,
11-
DestroyRef,
1211
effect,
1312
inject,
1413
input,
@@ -17,7 +16,6 @@ import {
1716
PLATFORM_ID,
1817
signal,
1918
} from '@angular/core';
20-
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
2119
import { Router } from '@angular/router';
2220

2321
import { FileMenuComponent } from '@osf/shared/components/file-menu/file-menu.component';
@@ -34,8 +32,7 @@ import { FileFolderModel } from '@osf/shared/models/files/file-folder.model';
3432
import { FileLabelModel } from '@osf/shared/models/files/file-label.model';
3533
import { FileMenuAction, FileMenuFlags } from '@osf/shared/models/files/file-menu-action.model';
3634
import { FilePageLinkModel } from '@osf/shared/models/files/file-page-link.model';
37-
import { DataciteService } from '@osf/shared/services/datacite/datacite.service';
38-
import { FilesService } from '@osf/shared/services/files.service';
35+
import { FileDownloadService } from '@osf/shared/services/file-download.service';
3936
import { FilesShareEmbedService } from '@osf/shared/services/files-share-embed.service';
4037
import { ViewOnlyLinkHelperService } from '@osf/shared/services/view-only-link-helper.service';
4138

@@ -60,10 +57,8 @@ import { MenuMoveCopyPayload } from '../../models/menu-move-copy.model';
6057
changeDetection: ChangeDetectionStrategy.OnPush,
6158
})
6259
export class FilesTreeExplorerComponent {
63-
private readonly destroyRef = inject(DestroyRef);
6460
private readonly router = inject(Router);
65-
private readonly filesService = inject(FilesService);
66-
private readonly dataciteService = inject(DataciteService);
61+
private readonly fileDownloadService = inject(FileDownloadService);
6762
private readonly filesShareEmbedService = inject(FilesShareEmbedService);
6863
private readonly viewOnlyService = inject(ViewOnlyLinkHelperService);
6964
private readonly isBrowser = isPlatformBrowser(inject(PLATFORM_ID));
@@ -189,29 +184,11 @@ export class FilesTreeExplorerComponent {
189184
}
190185

191186
downloadFileOrFolder(file: FileModel) {
192-
this.dataciteService
193-
.logFileDownload(this.resourceId(), this.resourceType())
194-
.pipe(takeUntilDestroyed(this.destroyRef))
195-
.subscribe();
196-
197-
if (file.kind === FileKind.File) {
198-
this.downloadFile(file.links.download);
199-
} else {
200-
this.downloadFolder(file.links.upload);
201-
}
202-
}
203-
204-
downloadFile(link: string): void {
205-
if (this.isBrowser) {
206-
window.open(link)?.focus();
207-
}
208-
}
209-
210-
downloadFolder(downloadLink: string): void {
211-
if (downloadLink) {
212-
const link = this.filesService.getFolderDownloadLink(downloadLink);
213-
window.open(link, '_blank')?.focus();
214-
}
187+
this.fileDownloadService.downloadFileOrFolder({
188+
resourceId: this.resourceId(),
189+
resourceType: this.resourceType(),
190+
file,
191+
});
215192
}
216193

217194
onLazyLoad(event: TreeLazyLoadEvent) {

src/app/features/files/pages/files/files.component.spec.ts

Lines changed: 16 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -23,19 +23,19 @@ import { CurrentResource } from '@osf/shared/models/current-resource.model';
2323
import { FileFolderModel } from '@osf/shared/models/files/file-folder.model';
2424
import { FileLabelModel } from '@osf/shared/models/files/file-label.model';
2525
import { CustomDialogService } from '@osf/shared/services/custom-dialog.service';
26+
import { FileDownloadService } from '@osf/shared/services/file-download.service';
2627
import { FilesService } from '@osf/shared/services/files.service';
2728
import { FilesTreeActionsService } from '@osf/shared/services/files-tree-actions.service';
2829
import { ToastService } from '@osf/shared/services/toast.service';
2930
import { ViewOnlyLinkHelperService } from '@osf/shared/services/view-only-link-helper.service';
3031
import { CurrentResourceSelectors, GetResourceDetails } from '@osf/shared/stores/current-resource';
31-
import { DataciteService } from '@shared/services/datacite/datacite.service';
3232

3333
import { MOCK_CONFIGURED_ADDON } from '@testing/mocks/configured-addon.mock';
3434
import { FileModelMock } from '@testing/mocks/file.model.mock';
3535
import { OSF_FILE_MOCK } from '@testing/mocks/osf-file.mock';
3636
import { provideOSFCore } from '@testing/osf.testing.provider';
3737
import { CustomDialogServiceMock, CustomDialogServiceMockType } from '@testing/providers/custom-dialog-provider.mock';
38-
import { DataciteServiceMock, DataciteServiceMockType } from '@testing/providers/datacite.service.mock';
38+
import { FileDownloadServiceMock, FileDownloadServiceMockType } from '@testing/providers/file-download-service.mock';
3939
import { FilesServiceMock, FilesServiceMockType } from '@testing/providers/files-service.mock';
4040
import { ActivatedRouteMockBuilder } from '@testing/providers/route-provider.mock';
4141
import { RouterMockBuilder, RouterMockType } from '@testing/providers/router-provider.mock';
@@ -97,7 +97,7 @@ describe('FilesComponent', () => {
9797
uploadFiles: Mock;
9898
};
9999
let customDialogService: CustomDialogServiceMockType;
100-
let dataciteService: DataciteServiceMockType;
100+
let fileDownloadService: FileDownloadServiceMockType;
101101

102102
const currentFolder: FileFolderModel = {
103103
...OSF_FILE_MOCK,
@@ -121,7 +121,7 @@ describe('FilesComponent', () => {
121121
toastService = ToastServiceMock.simple();
122122
viewOnlyHelper = ViewOnlyLinkHelperMock.simple(false);
123123
customDialogService = CustomDialogServiceMock.simple();
124-
dataciteService = DataciteServiceMock.simple();
124+
fileDownloadService = FileDownloadServiceMock.simple();
125125

126126
filesActionsService = {
127127
deleteSelected: vi.fn(),
@@ -195,7 +195,7 @@ describe('FilesComponent', () => {
195195
MockProvider(ToastService, toastService),
196196
MockProvider(ViewOnlyLinkHelperService, viewOnlyHelper),
197197
MockProvider(CustomDialogService, customDialogService),
198-
MockProvider(DataciteService, dataciteService),
198+
MockProvider(FileDownloadService, fileDownloadService),
199199
MockProvider(FilesActionsService, filesActionsService),
200200
MockProvider(FilesTreeActionsService, filesTreeActionsService),
201201
MockProvider(FilesUploadService, filesUploadService),
@@ -468,28 +468,28 @@ describe('FilesComponent', () => {
468468
expect(store.dispatch).toHaveBeenCalledWith(new GetFiles('/files-link', 1));
469469
});
470470

471-
it('should log download and open folder zip link', () => {
471+
it('should download current folder as zip', () => {
472472
setup();
473-
(store.dispatch as Mock).mockClear();
474-
const openSpy = vi.spyOn(window, 'open').mockReturnValue({ focus: vi.fn() } as unknown as Window);
475473

476474
component.downloadFolder();
477475

478-
expect(dataciteService.logFileDownload).toHaveBeenCalledWith('node-1', 'nodes');
479-
expect(filesService.getFolderDownloadLink).toHaveBeenCalledWith('/v2/files/file-123/download/');
480-
expect(openSpy).toHaveBeenCalledWith('/v2/files/file-123/download/?zip=', '_blank');
481-
openSpy.mockRestore();
476+
expect(fileDownloadService.downloadFolderAsZip).toHaveBeenCalledWith({
477+
resourceId: 'node-1',
478+
resourceType: 'nodes',
479+
downloadLink: '/v2/files/file-123/download/',
480+
});
482481
});
483482

484483
it('should skip download when resource id is missing', () => {
485484
setup({ resourceId: '' });
486-
const openSpy = vi.spyOn(window, 'open');
487485

488486
component.downloadFolder();
489487

490-
expect(dataciteService.logFileDownload).not.toHaveBeenCalled();
491-
expect(openSpy).not.toHaveBeenCalled();
492-
openSpy.mockRestore();
488+
expect(fileDownloadService.downloadFolderAsZip).toHaveBeenCalledWith({
489+
resourceId: '',
490+
resourceType: 'nodes',
491+
downloadLink: '/v2/files/file-123/download/',
492+
});
493493
});
494494

495495
it('should open files browser info dialog', () => {

src/app/features/files/pages/files/files.component.ts

Lines changed: 7 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ import { CurrentResourceType, ResourceType } from '@osf/shared/enums/resource-ty
3939
import { mapRootFoldersToStorageLabels } from '@osf/shared/helpers/storage-addon-options.helper';
4040
import { FilePageLinkModel } from '@osf/shared/models/files/file-page-link.model';
4141
import { CustomDialogService } from '@osf/shared/services/custom-dialog.service';
42+
import { FileDownloadService } from '@osf/shared/services/file-download.service';
4243
import { FilesService } from '@osf/shared/services/files.service';
4344
import { FilesTreeActionsService } from '@osf/shared/services/files-tree-actions.service';
4445
import { ToastService } from '@osf/shared/services/toast.service';
@@ -48,7 +49,6 @@ import { StorageItem } from '@shared/models/addons/storage-item.model';
4849
import { FileModel } from '@shared/models/files/file.model';
4950
import { FileFolderModel } from '@shared/models/files/file-folder.model';
5051
import { FileLabelModel } from '@shared/models/files/file-label.model';
51-
import { DataciteService } from '@shared/services/datacite/datacite.service';
5252

5353
import { FileBrowserInfoComponent } from '../../components/file-browser-info/file-browser-info.component';
5454
import { FilesSelectionActionsComponent } from '../../components/files-selection-actions/files-selection-actions.component';
@@ -107,7 +107,7 @@ export class FilesComponent {
107107
private readonly customDialogService = inject(CustomDialogService);
108108
private readonly translateService = inject(TranslateService);
109109
private readonly router = inject(Router);
110-
private readonly dataciteService = inject(DataciteService);
110+
private readonly fileDownloadService = inject(FileDownloadService);
111111
private readonly filesActionsService = inject(FilesActionsService);
112112
private readonly filesTreeActionsService = inject(FilesTreeActionsService);
113113
private readonly filesUploadService = inject(FilesUploadService);
@@ -506,17 +506,11 @@ export class FilesComponent {
506506
}
507507

508508
downloadFolder(): void {
509-
const resourceId = this.resourceId();
510-
const resourcePath = this.resourceMetadata()?.type ?? 'nodes';
511-
const downloadLink = this.currentFolder()?.links.download ?? '';
512-
if (resourceId && downloadLink) {
513-
this.dataciteService
514-
.logFileDownload(resourceId, resourcePath)
515-
.pipe(takeUntilDestroyed(this.destroyRef))
516-
.subscribe();
517-
const link = this.filesService.getFolderDownloadLink(downloadLink);
518-
window.open(link, '_blank')?.focus();
519-
}
509+
this.fileDownloadService.downloadFolderAsZip({
510+
resourceId: this.resourceId() ?? '',
511+
resourceType: this.resourceMetadata()?.type ?? 'nodes',
512+
downloadLink: this.currentFolder()?.links.download ?? '',
513+
});
520514
}
521515

522516
showInfoDialog() {

src/app/features/home/pages/dashboard/dashboard.component.spec.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import { ComponentFixture, TestBed } from '@angular/core/testing';
1111
import { ActivatedRoute, Router } from '@angular/router';
1212

1313
import { ScheduledBannerComponent } from '@core/components/osf-banners/scheduled-banner/scheduled-banner.component';
14-
import { CreateProjectDialogComponent } from '@osf/features/my-projects/components';
14+
import { CreateProjectDialogComponent } from '@osf/features/my-projects/components/create-project-dialog/create-project-dialog.component';
1515
import { IconComponent } from '@osf/shared/components/icon/icon.component';
1616
import { LoadingSpinnerComponent } from '@osf/shared/components/loading-spinner/loading-spinner.component';
1717
import { MyProjectsTableComponent } from '@osf/shared/components/my-projects-table/my-projects-table.component';

src/app/features/home/pages/dashboard/dashboard.component.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ import { FormControl } from '@angular/forms';
1515
import { ActivatedRoute, Router, RouterLink } from '@angular/router';
1616

1717
import { ScheduledBannerComponent } from '@core/components/osf-banners/scheduled-banner/scheduled-banner.component';
18-
import { CreateProjectDialogComponent } from '@osf/features/my-projects/components';
18+
import { CreateProjectDialogComponent } from '@osf/features/my-projects/components/create-project-dialog/create-project-dialog.component';
1919
import { IconComponent } from '@osf/shared/components/icon/icon.component';
2020
import { LoadingSpinnerComponent } from '@osf/shared/components/loading-spinner/loading-spinner.component';
2121
import { MyProjectsTableComponent } from '@osf/shared/components/my-projects-table/my-projects-table.component';

src/app/features/metadata/components/cedar-template-form/cedar-template-form.component.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import { toSignal } from '@angular/core/rxjs-interop';
2323
import { ActivatedRoute } from '@angular/router';
2424

2525
import { ENVIRONMENT } from '@core/provider/environment.provider';
26+
import { MetadataRecordsService } from '@osf/shared/services/metadata-records.service';
2627
import { SocialShareService } from '@osf/shared/services/social-share.service';
2728

2829
import { CEDAR_CONFIG, CEDAR_VIEWER_CONFIG } from '../../constants';
@@ -63,6 +64,7 @@ export class CedarTemplateFormComponent {
6364

6465
private route = inject(ActivatedRoute);
6566
readonly environment = inject(ENVIRONMENT);
67+
private readonly metadataRecordsService = inject(MetadataRecordsService);
6668
private readonly socialShareService = inject(SocialShareService);
6769

6870
readonly recordId = signal<string>('');
@@ -136,8 +138,10 @@ export class CedarTemplateFormComponent {
136138
}
137139

138140
downloadMetadadaRecord() {
139-
if (this.fileGuid()) {
140-
window.open(`${this.environment.webUrl}/metadata/${this.fileGuid()}`)?.focus();
141+
const fileGuid = this.fileGuid();
142+
143+
if (fileGuid) {
144+
this.metadataRecordsService.downloadMetadata(fileGuid);
141145
} else {
142146
window.open(this.downloadUrl(), '_blank');
143147
}

src/app/features/my-projects/components/index.ts

Lines changed: 0 additions & 1 deletion
This file was deleted.

0 commit comments

Comments
 (0)