[Soft Delete] - cipher search rem deleted flag, filter array conditional

This commit is contained in:
Chad Scharf 2020-04-08 16:44:13 -04:00
parent 549fcc18ff
commit 3a10c1ff30
5 changed files with 63 additions and 20 deletions

View File

@ -4,7 +4,8 @@ export abstract class SearchService {
clearIndex: () => void; clearIndex: () => void;
isSearchable: (query: string) => boolean; isSearchable: (query: string) => boolean;
indexCiphers: () => Promise<void>; indexCiphers: () => Promise<void>;
searchCiphers: (query: string, filter?: (cipher: CipherView) => boolean, searchCiphers: (query: string,
ciphers?: CipherView[], deleted?: boolean) => Promise<CipherView[]>; filter?: ((cipher: CipherView) => boolean) | (Array<(cipher: CipherView) => boolean>),
ciphers?: CipherView[]) => Promise<CipherView[]>;
searchCiphersBasic: (ciphers: CipherView[], query: string, deleted?: boolean) => CipherView[]; searchCiphersBasic: (ciphers: CipherView[], query: string, deleted?: boolean) => CipherView[];
} }

View File

@ -50,6 +50,7 @@ export class AddEditComponent implements OnInit {
@Input() organizationId: string = null; @Input() organizationId: string = null;
@Output() onSavedCipher = new EventEmitter<CipherView>(); @Output() onSavedCipher = new EventEmitter<CipherView>();
@Output() onDeletedCipher = new EventEmitter<CipherView>(); @Output() onDeletedCipher = new EventEmitter<CipherView>();
@Output() onRestoredCipher = new EventEmitter<CipherView>();
@Output() onCancelled = new EventEmitter<CipherView>(); @Output() onCancelled = new EventEmitter<CipherView>();
@Output() onEditAttachments = new EventEmitter<CipherView>(); @Output() onEditAttachments = new EventEmitter<CipherView>();
@Output() onShareCipher = new EventEmitter<CipherView>(); @Output() onShareCipher = new EventEmitter<CipherView>();
@ -63,6 +64,7 @@ export class AddEditComponent implements OnInit {
title: string; title: string;
formPromise: Promise<any>; formPromise: Promise<any>;
deletePromise: Promise<any>; deletePromise: Promise<any>;
restorePromise: Promise<any>;
checkPasswordPromise: Promise<number>; checkPasswordPromise: Promise<number>;
showPassword: boolean = false; showPassword: boolean = false;
showCardCode: boolean = false; showCardCode: boolean = false;
@ -221,6 +223,10 @@ export class AddEditComponent implements OnInit {
} }
async submit(): Promise<boolean> { async submit(): Promise<boolean> {
if (this.cipher.isDeleted) {
return this.restore();
}
if (this.cipher.name == null || this.cipher.name === '') { if (this.cipher.name == null || this.cipher.name === '') {
this.platformUtilsService.showToast('error', this.i18nService.t('errorOccurred'), this.platformUtilsService.showToast('error', this.i18nService.t('errorOccurred'),
this.i18nService.t('nameRequired')); this.i18nService.t('nameRequired'));
@ -331,10 +337,35 @@ export class AddEditComponent implements OnInit {
try { try {
this.deletePromise = this.deleteCipher(); this.deletePromise = this.deleteCipher();
await this.deletePromise; await this.deletePromise;
this.platformUtilsService.eventTrack('Deleted Cipher'); this.platformUtilsService.eventTrack((this.cipher.isDeleted ? 'Permanently ' : '') + 'Deleted Cipher');
this.platformUtilsService.showToast('success', null, this.i18nService.t('deletedItem')); this.platformUtilsService.showToast('success', null,
this.i18nService.t(this.cipher.isDeleted ? 'permanentlyDeletedItem' : 'deletedItem'));
this.onDeletedCipher.emit(this.cipher); this.onDeletedCipher.emit(this.cipher);
this.messagingService.send('deletedCipher'); this.messagingService.send(this.cipher.isDeleted ? 'permanentlyDeletedCipher' : 'deletedCipher');
} catch { }
return true;
}
async restore(): Promise<boolean> {
if (!this.cipher.isDeleted) {
return false;
}
const confirmed = await this.platformUtilsService.showDialog(
this.i18nService.t('restoreItemConfirmation'), this.i18nService.t('restoreItem'),
this.i18nService.t('yes'), this.i18nService.t('no'), 'warning');
if (!confirmed) {
return false;
}
try {
this.restorePromise = this.restoreCipher();
await this.restorePromise;
this.platformUtilsService.eventTrack('Restored Cipher');
this.platformUtilsService.showToast('success', null, this.i18nService.t('restoredItem'));
this.onRestoredCipher.emit(this.cipher);
this.messagingService.send('restoredCipher');
} catch { } } catch { }
return true; return true;
@ -449,6 +480,11 @@ export class AddEditComponent implements OnInit {
} }
protected deleteCipher() { protected deleteCipher() {
return this.cipherService.deleteWithServer(this.cipher.id); return this.cipher.isDeleted ? this.cipherService.deleteWithServer(this.cipher.id)
: this.cipherService.softDeleteWithServer(this.cipher.id);
}
protected restoreCipher() {
return this.cipherService.restoreWithServer(this.cipher.id);
} }
} }

View File

@ -64,7 +64,7 @@ export class CiphersComponent {
async refresh() { async refresh() {
try { try {
this.refreshing = true; this.refreshing = true;
await this.reload(this.filter); await this.reload(this.filter, this.deleted);
} finally { } finally {
this.refreshing = false; this.refreshing = false;
} }
@ -80,14 +80,15 @@ export class CiphersComponent {
if (this.searchTimeout != null) { if (this.searchTimeout != null) {
clearTimeout(this.searchTimeout); clearTimeout(this.searchTimeout);
} }
const deletedFilter: (cipher: CipherView) => boolean = (c) => c.isDeleted === this.deleted;
if (timeout == null) { if (timeout == null) {
this.ciphers = await this.searchService.searchCiphers(this.searchText, this.filter, null, this.deleted); this.ciphers = await this.searchService.searchCiphers(this.searchText, [this.filter, deletedFilter], null);
await this.resetPaging(); await this.resetPaging();
return; return;
} }
this.searchPending = true; this.searchPending = true;
this.searchTimeout = setTimeout(async () => { this.searchTimeout = setTimeout(async () => {
this.ciphers = await this.searchService.searchCiphers(this.searchText, this.filter, null, this.deleted); this.ciphers = await this.searchService.searchCiphers(this.searchText, [this.filter, deletedFilter], null);
await this.resetPaging(); await this.resetPaging();
this.searchPending = false; this.searchPending = false;
}, timeout); }, timeout);

View File

@ -34,6 +34,7 @@ export class ViewComponent implements OnDestroy, OnInit {
@Input() cipherId: string; @Input() cipherId: string;
@Output() onEditCipher = new EventEmitter<CipherView>(); @Output() onEditCipher = new EventEmitter<CipherView>();
@Output() onCloneCipher = new EventEmitter<CipherView>(); @Output() onCloneCipher = new EventEmitter<CipherView>();
@Output() onRestoreCipher = new EventEmitter<CipherView>();
cipher: CipherView; cipher: CipherView;
showPassword: boolean; showPassword: boolean;
@ -110,6 +111,13 @@ export class ViewComponent implements OnDestroy, OnInit {
this.onCloneCipher.emit(this.cipher); this.onCloneCipher.emit(this.cipher);
} }
restore() {
if (!this.cipher.isDeleted) {
return;
}
this.onRestoreCipher.emit(this.cipher);
}
togglePassword() { togglePassword() {
this.platformUtilsService.eventTrack('Toggled Password'); this.platformUtilsService.eventTrack('Toggled Password');
this.showPassword = !this.showPassword; this.showPassword = !this.showPassword;

View File

@ -71,8 +71,9 @@ export class SearchService implements SearchServiceAbstraction {
console.timeEnd('search indexing'); console.timeEnd('search indexing');
} }
async searchCiphers(query: string, filter: (cipher: CipherView) => boolean = null, ciphers: CipherView[] = null, async searchCiphers(query: string,
deleted: boolean = false): filter: (((cipher: CipherView) => boolean) | (Array<(cipher: CipherView) => boolean>)) = null,
ciphers: CipherView[] = null):
Promise<CipherView[]> { Promise<CipherView[]> {
const results: CipherView[] = []; const results: CipherView[] = [];
if (query != null) { if (query != null) {
@ -86,15 +87,11 @@ export class SearchService implements SearchServiceAbstraction {
ciphers = await this.cipherService.getAllDecrypted(); ciphers = await this.cipherService.getAllDecrypted();
} }
ciphers = ciphers.filter((c) => { if (filter != null && Array.isArray(filter) && filter.length > 0) {
if (deleted !== c.isDeleted) { ciphers = ciphers.filter((c) => filter.every((f) => f == null || f(c)));
return false; } else if (filter != null) {
ciphers = ciphers.filter(filter as (cipher: CipherView) => boolean);
} }
if (filter != null) {
return filter(c);
}
return true;
});
if (!this.isSearchable(query)) { if (!this.isSearchable(query)) {
return ciphers; return ciphers;