1
0
mirror of https://github.com/bitwarden/browser synced 2025-12-12 06:13:38 +00:00

[PM-11926] - send created redirect (#11140)

* send created redirect

* fix test

* fix test

* fix send form save

* return SendData from saveSend

* When saving a Send, bubble up a SendView which can be passed to the SendCreated component

* Use events to initiate navigation and move actual navigation into client-specific component

---------

Co-authored-by: Daniel James Smith <djsmith85@users.noreply.github.com>
This commit is contained in:
Jordan Aasen
2024-10-01 12:58:00 -07:00
committed by GitHub
parent ab5a02f483
commit dab60dbaea
9 changed files with 62 additions and 28 deletions

View File

@@ -4,7 +4,8 @@
<tools-send-form <tools-send-form
formId="sendForm" formId="sendForm"
[config]="config" [config]="config"
(sendSaved)="onSendSaved()" (onSendCreated)="onSendCreated($event)"
(onSendUpdated)="onSendUpdated($event)"
[submitBtn]="submitBtn" [submitBtn]="submitBtn"
> >
</tools-send-form> </tools-send-form>

View File

@@ -2,12 +2,13 @@ import { CommonModule, Location } from "@angular/common";
import { Component } from "@angular/core"; import { Component } from "@angular/core";
import { takeUntilDestroyed } from "@angular/core/rxjs-interop"; import { takeUntilDestroyed } from "@angular/core/rxjs-interop";
import { FormsModule } from "@angular/forms"; import { FormsModule } from "@angular/forms";
import { ActivatedRoute, Params } from "@angular/router"; import { ActivatedRoute, Params, Router } from "@angular/router";
import { map, switchMap } from "rxjs"; import { map, switchMap } from "rxjs";
import { JslibModule } from "@bitwarden/angular/jslib.module"; import { JslibModule } from "@bitwarden/angular/jslib.module";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { SendType } from "@bitwarden/common/tools/send/enums/send-type"; import { SendType } from "@bitwarden/common/tools/send/enums/send-type";
import { SendView } from "@bitwarden/common/tools/send/models/view/send.view";
import { SendApiService } from "@bitwarden/common/tools/send/services/send-api.service.abstraction"; import { SendApiService } from "@bitwarden/common/tools/send/services/send-api.service.abstraction";
import { SendId } from "@bitwarden/common/types/guid"; import { SendId } from "@bitwarden/common/types/guid";
import { import {
@@ -95,14 +96,25 @@ export class SendAddEditComponent {
private sendApiService: SendApiService, private sendApiService: SendApiService,
private toastService: ToastService, private toastService: ToastService,
private dialogService: DialogService, private dialogService: DialogService,
private router: Router,
) { ) {
this.subscribeToParams(); this.subscribeToParams();
} }
/** /**
* Handles the event when the send is saved. * Handles the event when the send is created.
*/ */
onSendSaved() { async onSendCreated(send: SendView) {
await this.router.navigate(["/send-created"], {
queryParams: { sendId: send.id },
});
return;
}
/**
* Handles the event when the send is updated.
*/
onSendUpdated(send: SendView) {
this.location.back(); this.location.back();
} }

View File

@@ -1,6 +1,11 @@
<main class="tw-top-0"> <main class="tw-top-0">
<popup-page> <popup-page>
<popup-header slot="header" [pageTitle]="'createdSend' | i18n" showBackButton> <popup-header
slot="header"
[pageTitle]="'createdSend' | i18n"
showBackButton
[backAction]="close.bind(this)"
>
<ng-container slot="end"> <ng-container slot="end">
<app-pop-out></app-pop-out> <app-pop-out></app-pop-out>
</ng-container> </ng-container>

View File

@@ -1,6 +1,6 @@
import { CommonModule, Location } from "@angular/common"; import { CommonModule, Location } from "@angular/common";
import { ComponentFixture, TestBed } from "@angular/core/testing"; import { ComponentFixture, TestBed } from "@angular/core/testing";
import { ActivatedRoute, RouterLink } from "@angular/router"; import { ActivatedRoute, Router, RouterLink } from "@angular/router";
import { RouterTestingModule } from "@angular/router/testing"; import { RouterTestingModule } from "@angular/router/testing";
import { MockProxy, mock } from "jest-mock-extended"; import { MockProxy, mock } from "jest-mock-extended";
import { of } from "rxjs"; import { of } from "rxjs";
@@ -33,6 +33,7 @@ describe("SendCreatedComponent", () => {
let location: MockProxy<Location>; let location: MockProxy<Location>;
let activatedRoute: MockProxy<ActivatedRoute>; let activatedRoute: MockProxy<ActivatedRoute>;
let environmentService: MockProxy<EnvironmentService>; let environmentService: MockProxy<EnvironmentService>;
let router: MockProxy<Router>;
const sendId = "test-send-id"; const sendId = "test-send-id";
const deletionDate = new Date(); const deletionDate = new Date();
@@ -52,6 +53,7 @@ describe("SendCreatedComponent", () => {
location = mock<Location>(); location = mock<Location>();
activatedRoute = mock<ActivatedRoute>(); activatedRoute = mock<ActivatedRoute>();
environmentService = mock<EnvironmentService>(); environmentService = mock<EnvironmentService>();
router = mock<Router>();
Object.defineProperty(environmentService, "environment$", { Object.defineProperty(environmentService, "environment$", {
configurable: true, configurable: true,
get: () => of(new SelfHostedEnvironment({ webVault: "https://example.com" })), get: () => of(new SelfHostedEnvironment({ webVault: "https://example.com" })),
@@ -89,6 +91,7 @@ describe("SendCreatedComponent", () => {
{ provide: ConfigService, useValue: mock<ConfigService>() }, { provide: ConfigService, useValue: mock<ConfigService>() },
{ provide: EnvironmentService, useValue: environmentService }, { provide: EnvironmentService, useValue: environmentService },
{ provide: PopupRouterCacheService, useValue: mock<PopupRouterCacheService>() }, { provide: PopupRouterCacheService, useValue: mock<PopupRouterCacheService>() },
{ provide: Router, useValue: router },
], ],
}).compileComponents(); }).compileComponents();
}); });
@@ -109,10 +112,10 @@ describe("SendCreatedComponent", () => {
expect(component["daysAvailable"]).toBe(7); expect(component["daysAvailable"]).toBe(7);
}); });
it("should navigate back on close", () => { it("should navigate back to send list on close", async () => {
fixture.detectChanges(); fixture.detectChanges();
component.close(); await component.close();
expect(location.back).toHaveBeenCalled(); expect(router.navigate).toHaveBeenCalledWith(["/tabs/send"]);
}); });
describe("getDaysAvailable", () => { describe("getDaysAvailable", () => {

View File

@@ -1,7 +1,7 @@
import { CommonModule, Location } from "@angular/common"; import { CommonModule } from "@angular/common";
import { Component } from "@angular/core"; import { Component } from "@angular/core";
import { takeUntilDestroyed } from "@angular/core/rxjs-interop"; import { takeUntilDestroyed } from "@angular/core/rxjs-interop";
import { ActivatedRoute, RouterLink } from "@angular/router"; import { ActivatedRoute, Router, RouterLink, RouterModule } from "@angular/router";
import { firstValueFrom } from "rxjs"; import { firstValueFrom } from "rxjs";
import { JslibModule } from "@bitwarden/angular/jslib.module"; import { JslibModule } from "@bitwarden/angular/jslib.module";
@@ -30,6 +30,7 @@ import { PopupPageComponent } from "../../../../platform/popup/layout/popup-page
PopupHeaderComponent, PopupHeaderComponent,
PopupPageComponent, PopupPageComponent,
RouterLink, RouterLink,
RouterModule,
PopupFooterComponent, PopupFooterComponent,
IconModule, IconModule,
], ],
@@ -45,10 +46,11 @@ export class SendCreatedComponent {
private sendService: SendService, private sendService: SendService,
private route: ActivatedRoute, private route: ActivatedRoute,
private toastService: ToastService, private toastService: ToastService,
private location: Location, private router: Router,
private environmentService: EnvironmentService, private environmentService: EnvironmentService,
) { ) {
const sendId = this.route.snapshot.queryParamMap.get("sendId"); const sendId = this.route.snapshot.queryParamMap.get("sendId");
this.sendService.sendViews$.pipe(takeUntilDestroyed()).subscribe((sendViews) => { this.sendService.sendViews$.pipe(takeUntilDestroyed()).subscribe((sendViews) => {
this.send = sendViews.find((s) => s.id === sendId); this.send = sendViews.find((s) => s.id === sendId);
if (this.send) { if (this.send) {
@@ -62,8 +64,8 @@ export class SendCreatedComponent {
return Math.max(0, Math.ceil((send.deletionDate.getTime() - now) / (1000 * 60 * 60 * 24))); return Math.max(0, Math.ceil((send.deletionDate.getTime() - now) / (1000 * 60 * 60 * 24)));
} }
close() { async close() {
this.location.back(); await this.router.navigate(["/tabs/send"]);
} }
async copyLink() { async copyLink() {

View File

@@ -36,5 +36,5 @@ export abstract class SendApiService {
renewSendFileUploadUrl: (sendId: string, fileId: string) => Promise<SendFileUploadDataResponse>; renewSendFileUploadUrl: (sendId: string, fileId: string) => Promise<SendFileUploadDataResponse>;
removePassword: (id: string) => Promise<any>; removePassword: (id: string) => Promise<any>;
delete: (id: string) => Promise<any>; delete: (id: string) => Promise<any>;
save: (sendData: [Send, EncArrayBuffer]) => Promise<any>; save: (sendData: [Send, EncArrayBuffer]) => Promise<Send>;
} }

View File

@@ -135,11 +135,12 @@ export class SendApiService implements SendApiServiceAbstraction {
return this.apiService.send("DELETE", "/sends/" + id, null, true, false); return this.apiService.send("DELETE", "/sends/" + id, null, true, false);
} }
async save(sendData: [Send, EncArrayBuffer]): Promise<any> { async save(sendData: [Send, EncArrayBuffer]): Promise<Send> {
const response = await this.upload(sendData); const response = await this.upload(sendData);
const data = new SendData(response); const data = new SendData(response);
await this.sendService.upsert(data); await this.sendService.upsert(data);
return new Send(data);
} }
async delete(id: string): Promise<any> { async delete(id: string): Promise<any> {

View File

@@ -85,9 +85,14 @@ export class SendFormComponent implements AfterViewInit, OnInit, OnChanges, Send
submitBtn?: ButtonComponent; submitBtn?: ButtonComponent;
/** /**
* Event emitted when the send is saved successfully. * Event emitted when the send is created successfully.
*/ */
@Output() sendSaved = new EventEmitter<SendView>(); @Output() onSendCreated = new EventEmitter<SendView>();
/**
* Event emitted when the send is updated successfully.
*/
@Output() onSendUpdated = new EventEmitter<SendView>();
/** /**
* The original send being edited or cloned. Null for add mode. * The original send being edited or cloned. Null for add mode.
@@ -200,22 +205,26 @@ export class SendFormComponent implements AfterViewInit, OnInit, OnChanges, Send
return; return;
} }
const sendView = await this.addEditFormService.saveSend(
this.updatedSendView,
this.file,
this.config,
);
if (this.config.mode === "add") {
this.onSendCreated.emit(sendView);
return;
}
if (Utils.isNullOrWhitespace(this.updatedSendView.password)) { if (Utils.isNullOrWhitespace(this.updatedSendView.password)) {
this.updatedSendView.password = null; this.updatedSendView.password = null;
} }
await this.addEditFormService.saveSend(this.updatedSendView, this.file, this.config);
this.toastService.showToast({ this.toastService.showToast({
variant: "success", variant: "success",
title: null, title: null,
message: this.i18nService.t( message: this.i18nService.t("editedItem"),
this.config.mode === "edit" || this.config.mode === "partial-edit"
? "editedItem"
: "addedItem",
),
}); });
this.onSendUpdated.emit(this.updatedSendView);
this.sendSaved.emit(this.updatedSendView);
}; };
} }

View File

@@ -19,6 +19,7 @@ export class DefaultSendFormService implements SendFormService {
async saveSend(send: SendView, file: File | ArrayBuffer, config: SendFormConfig) { async saveSend(send: SendView, file: File | ArrayBuffer, config: SendFormConfig) {
const sendData = await this.sendService.encrypt(send, file, send.password, null); const sendData = await this.sendService.encrypt(send, file, send.password, null);
return await this.sendApiService.save(sendData); const newSend = await this.sendApiService.save(sendData);
return await this.decryptSend(newSend);
} }
} }