From 24a3087f52ef50f699c4394a2f5156be71260813 Mon Sep 17 00:00:00 2001 From: Adam Coster Date: Fri, 20 Aug 2021 19:15:37 -0500 Subject: [PATCH] feat: Add method to Card instances for fetching associated Custom Field definitions. --- src/lib/clientLib/BravoResponse.ts | 29 +++++++++++ src/lib/entities/BravoCard.ts | 84 ++++++++++++++++++++++++++---- 2 files changed, 102 insertions(+), 11 deletions(-) diff --git a/src/lib/clientLib/BravoResponse.ts b/src/lib/clientLib/BravoResponse.ts index f5fdd3f..4695efc 100644 --- a/src/lib/clientLib/BravoResponse.ts +++ b/src/lib/clientLib/BravoResponse.ts @@ -32,6 +32,13 @@ export class BravoResponseEntities< private _entitiesCache: Entity[] = []; private _hydratedLatestPage = false; private _latestPage?: FavroResponse; + /** + * Entities to cache in a *map* while looping over entries, + * but with multiple caches (one for each identifier field). + */ + private _entitiesCachedById: { + [identifierField: string]: { [id: string]: Entity }; + } = {}; constructor( private _client: BravoClient, @@ -87,6 +94,28 @@ export class BravoResponseEntities< } } + /** + * Find an entity by an identifier field. + */ + async findById(identifierName: string, identifierValue: string) { + // Ensure we have the per-identifier name cache + this._entitiesCachedById[identifierName] ||= {}; + const cache = this._entitiesCachedById[identifierName]; + const entity = cache[identifierValue]; + if (entity) { + return entity; + } + + // Need to iterate over the list! But we can cache as we go. + for await (const entity of this) { + cache[identifierValue] ||= entity; + // @ts-expect-error + if (entity[identifierName] === identifierValue) { + return entity; + } + } + } + /** * Exhaustively fetch all entities (including those on * subsequent pages, requiring additional API calls), diff --git a/src/lib/entities/BravoCard.ts b/src/lib/entities/BravoCard.ts index 072bf7d..74d8f02 100644 --- a/src/lib/entities/BravoCard.ts +++ b/src/lib/entities/BravoCard.ts @@ -15,6 +15,7 @@ import { wrapIfNotArray, } from '../utility.js'; import type { BravoColumn } from './BravoColumn.js'; +import { BravoCustomFieldDefinition } from './BravoCustomField.js'; /** * A Card update can be pretty complex, and to save API @@ -237,17 +238,6 @@ export class BravoCardInstance extends BravoEntity { return this._data.cardCommonId; } - /** - * The column this card appears in (i.e. the status it has) - * on the current widget. */ - get columnId() { - return this._data.columnId; - } - - get parentCardId() { - return this._data.parentCardId; - } - /** * The widget that the current instance of this * card appears in. (A single card can live in, @@ -257,6 +247,9 @@ export class BravoCardInstance extends BravoEntity { return this._data.widgetCommonId; } + /** + * The list of tag IDs associated with this card. + */ get tags() { return [...this._data.tags]; } @@ -265,6 +258,30 @@ export class BravoCardInstance extends BravoEntity { return this._data.detailedDescription; } + /** + * The column this card appears in (i.e. the status it has) + * on the current widget. */ + get columnId() { + return this._data.columnId; + } + + /** + * Get the raw Custom Field IDs and values associated + * with this card. (These are global, not per-Widget.) + * + * These are typically a collection of IDs and values. + * For more human-friendly versions, with hydrated classes + * populating all of the info, + * see {@link BravoCardInstance.getCustomFields}. + */ + get customFieldsValuesRaw() { + return [...this._data.customFields]; + } + + get parentCardId() { + return this._data.parentCardId; + } + get attachments() { return this._data.attachments; } @@ -297,6 +314,51 @@ export class BravoCardInstance extends BravoEntity { return this._updateBuilder; } + /** + * Get the custom field definitions associated with + * the custom field values that are set on this card. + * + * Note that definitions are only discoverable for *set* + * custom field values on the card, even though in the Favro UI you + * can see others that are not set. + */ + async getCustomFieldDefinitions() { + const cardFieldValueMap: { + [customFieldId: string]: BravoCustomFieldDefinition; + } = {}; + const definitions = await this._client.listCustomFieldDefinitions(); + for (const value of this.customFieldsValuesRaw) { + const definition = await definitions.findById( + 'customFieldId', + value.customFieldId, + ); + assertBravoClaim( + definition, + `Could not find Custom Field with ID ${value.customFieldId}`, + ); + cardFieldValueMap[value.customFieldId] = definition; + } + return cardFieldValueMap; + } + + // /** + // * Get full information about the populated custom fields + // * on this Card. + // */ + // async getCustomFieldsValues() { + // const definitions = await this.getCustomFieldDefinitions(); + // const values: BravoCustomFieldValue[] = []; + // for (const rawValue of this.customFieldsValuesRaw) { + // values.push( + // new BravoCustomFieldValue( + // rawValue, + // definitions[rawValue.customFieldId], + // ), + // ); + // } + // return values; + // } + /** * Get the list of Columns (statuses) that this Card Instance * could be assigned to on its current Widget.