[PM-9926] Linked Fields order (#10174)

* refactor linkedFieldOption to take in an attributes object

* add sort positions for existing linked fields

* sort linked fields on new custom fields
This commit is contained in:
Nick Krantz 2024-07-23 12:26:02 -05:00 committed by GitHub
parent 88f585e09d
commit decc7a3031
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 58 additions and 40 deletions

View File

@ -1,11 +1,30 @@
import { LinkedIdType } from "./enums";
import { ItemView } from "./models/view/item.view";
type LinkedMetadataAttributes = {
/**
* The i18n key used to describe the decorated class property in the UI.
* If it is null, then the name of the class property will be used as the i18n key.
*/
i18nKey?: string;
/**
* The position of the individual field to be applied when sorted.
*/
sortPosition: number;
};
export class LinkedMetadata {
private readonly _i18nKey: string;
readonly sortPosition: number;
constructor(
readonly propertyKey: string,
private readonly _i18nKey?: string,
) {}
attributes: LinkedMetadataAttributes,
) {
this._i18nKey = attributes?.i18nKey;
this.sortPosition = attributes.sortPosition;
}
get i18nKey() {
return this._i18nKey ?? this.propertyKey;
@ -16,15 +35,14 @@ export class LinkedMetadata {
* A decorator used to set metadata used by Linked custom fields. Apply it to a class property or getter to make it
* available as a Linked custom field option.
* @param id - A unique value that is saved in the Field model. It is used to look up the decorated class property.
* @param i18nKey - The i18n key used to describe the decorated class property in the UI. If it is null, then the name
* of the class property will be used as the i18n key.
* @param options - {@link LinkedMetadataAttributes}
*/
export function linkedFieldOption(id: LinkedIdType, i18nKey?: string) {
export function linkedFieldOption(id: LinkedIdType, attributes: LinkedMetadataAttributes) {
return (prototype: ItemView, propertyKey: string) => {
if (prototype.linkedFieldOptions == null) {
prototype.linkedFieldOptions = new Map<LinkedIdType, LinkedMetadata>();
}
prototype.linkedFieldOptions.set(id, new LinkedMetadata(propertyKey, i18nKey));
prototype.linkedFieldOptions.set(id, new LinkedMetadata(propertyKey, attributes));
};
}

View File

@ -6,13 +6,13 @@ import { linkedFieldOption } from "../../linked-field-option.decorator";
import { ItemView } from "./item.view";
export class CardView extends ItemView {
@linkedFieldOption(LinkedId.CardholderName)
@linkedFieldOption(LinkedId.CardholderName, { sortPosition: 0 })
cardholderName: string = null;
@linkedFieldOption(LinkedId.ExpMonth, "expirationMonth")
@linkedFieldOption(LinkedId.ExpMonth, { sortPosition: 3, i18nKey: "expirationMonth" })
expMonth: string = null;
@linkedFieldOption(LinkedId.ExpYear, "expirationYear")
@linkedFieldOption(LinkedId.ExpYear, { sortPosition: 4, i18nKey: "expirationYear" })
expYear: string = null;
@linkedFieldOption(LinkedId.Code, "securityCode")
@linkedFieldOption(LinkedId.Code, { sortPosition: 5, i18nKey: "securityCode" })
code: string = null;
private _brand: string = null;
@ -27,7 +27,7 @@ export class CardView extends ItemView {
return this.number != null ? "•".repeat(this.number.length) : null;
}
@linkedFieldOption(LinkedId.Brand)
@linkedFieldOption(LinkedId.Brand, { sortPosition: 2 })
get brand(): string {
return this._brand;
}
@ -36,7 +36,7 @@ export class CardView extends ItemView {
this._subTitle = null;
}
@linkedFieldOption(LinkedId.Number)
@linkedFieldOption(LinkedId.Number, { sortPosition: 1 })
get number(): string {
return this._number;
}

View File

@ -7,37 +7,37 @@ import { linkedFieldOption } from "../../linked-field-option.decorator";
import { ItemView } from "./item.view";
export class IdentityView extends ItemView {
@linkedFieldOption(LinkedId.Title)
@linkedFieldOption(LinkedId.Title, { sortPosition: 0 })
title: string = null;
@linkedFieldOption(LinkedId.MiddleName)
@linkedFieldOption(LinkedId.MiddleName, { sortPosition: 2 })
middleName: string = null;
@linkedFieldOption(LinkedId.Address1)
@linkedFieldOption(LinkedId.Address1, { sortPosition: 12 })
address1: string = null;
@linkedFieldOption(LinkedId.Address2)
@linkedFieldOption(LinkedId.Address2, { sortPosition: 13 })
address2: string = null;
@linkedFieldOption(LinkedId.Address3)
@linkedFieldOption(LinkedId.Address3, { sortPosition: 14 })
address3: string = null;
@linkedFieldOption(LinkedId.City, "cityTown")
@linkedFieldOption(LinkedId.City, { sortPosition: 15, i18nKey: "cityTown" })
city: string = null;
@linkedFieldOption(LinkedId.State, "stateProvince")
@linkedFieldOption(LinkedId.State, { sortPosition: 16, i18nKey: "stateProvince" })
state: string = null;
@linkedFieldOption(LinkedId.PostalCode, "zipPostalCode")
@linkedFieldOption(LinkedId.PostalCode, { sortPosition: 17, i18nKey: "zipPostalCode" })
postalCode: string = null;
@linkedFieldOption(LinkedId.Country)
@linkedFieldOption(LinkedId.Country, { sortPosition: 18 })
country: string = null;
@linkedFieldOption(LinkedId.Company)
@linkedFieldOption(LinkedId.Company, { sortPosition: 6 })
company: string = null;
@linkedFieldOption(LinkedId.Email)
@linkedFieldOption(LinkedId.Email, { sortPosition: 10 })
email: string = null;
@linkedFieldOption(LinkedId.Phone)
@linkedFieldOption(LinkedId.Phone, { sortPosition: 11 })
phone: string = null;
@linkedFieldOption(LinkedId.Ssn)
@linkedFieldOption(LinkedId.Ssn, { sortPosition: 7 })
ssn: string = null;
@linkedFieldOption(LinkedId.Username)
@linkedFieldOption(LinkedId.Username, { sortPosition: 5 })
username: string = null;
@linkedFieldOption(LinkedId.PassportNumber)
@linkedFieldOption(LinkedId.PassportNumber, { sortPosition: 8 })
passportNumber: string = null;
@linkedFieldOption(LinkedId.LicenseNumber)
@linkedFieldOption(LinkedId.LicenseNumber, { sortPosition: 9 })
licenseNumber: string = null;
private _firstName: string = null;
@ -48,7 +48,7 @@ export class IdentityView extends ItemView {
super();
}
@linkedFieldOption(LinkedId.FirstName)
@linkedFieldOption(LinkedId.FirstName, { sortPosition: 1 })
get firstName(): string {
return this._firstName;
}
@ -57,7 +57,7 @@ export class IdentityView extends ItemView {
this._subTitle = null;
}
@linkedFieldOption(LinkedId.LastName)
@linkedFieldOption(LinkedId.LastName, { sortPosition: 4 })
get lastName(): string {
return this._lastName;
}
@ -83,7 +83,7 @@ export class IdentityView extends ItemView {
return this._subTitle;
}
@linkedFieldOption(LinkedId.FullName)
@linkedFieldOption(LinkedId.FullName, { sortPosition: 3 })
get fullName(): string {
if (
this.title != null ||

View File

@ -10,9 +10,9 @@ import { ItemView } from "./item.view";
import { LoginUriView } from "./login-uri.view";
export class LoginView extends ItemView {
@linkedFieldOption(LinkedId.Username)
@linkedFieldOption(LinkedId.Username, { sortPosition: 0 })
username: string = null;
@linkedFieldOption(LinkedId.Password)
@linkedFieldOption(LinkedId.Password, { sortPosition: 1 })
password: string = null;
passwordRevisionDate?: Date = null;

View File

@ -20,7 +20,6 @@ import { Subject, zip } from "rxjs";
import { JslibModule } from "@bitwarden/angular/jslib.module";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { Utils } from "@bitwarden/common/platform/misc/utils";
import { CipherType, FieldType, LinkedIdType } from "@bitwarden/common/vault/enums";
import { CardView } from "@bitwarden/common/vault/models/view/card.view";
import { FieldView } from "@bitwarden/common/vault/models/view/field.view";
@ -135,13 +134,14 @@ export class CustomFieldsComponent implements OnInit, AfterViewInit {
ngOnInit() {
const linkedFieldsOptionsForCipher = this.getLinkedFieldsOptionsForCipher();
const optionsArray = Array.from(linkedFieldsOptionsForCipher?.entries() ?? []);
optionsArray.sort((a, b) => a[1].sortPosition - b[1].sortPosition);
// Populate options for linked custom fields
this.linkedFieldOptions = Array.from(linkedFieldsOptionsForCipher?.entries() ?? [])
.map(([id, linkedFieldOption]) => ({
name: this.i18nService.t(linkedFieldOption.i18nKey),
value: id,
}))
.sort(Utils.getSortFunction(this.i18nService, "name"));
this.linkedFieldOptions = optionsArray.map(([id, linkedFieldOption]) => ({
name: this.i18nService.t(linkedFieldOption.i18nKey),
value: id,
}));
// Populate the form with the existing fields
this.cipherFormContainer.originalCipherView?.fields?.forEach((field) => {