Skip to content

Commit

Permalink
feat: Add caching to Widget Columns.
Browse files Browse the repository at this point in the history
  • Loading branch information
adam-coster committed Jul 2, 2021
1 parent 22c268f commit 956dcd7
Show file tree
Hide file tree
Showing 2 changed files with 97 additions and 13 deletions.
54 changes: 42 additions & 12 deletions src/lib/BravoClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,31 @@ import { BravoColumn } from './entities/BravoColumn.js';
import { DataFavroColumn } from '$/types/FavroColumnTypes.js';
import { ArrayMatchFunction } from '$/types/Utility.js';

/**
* The `BravoClient` class should be singly-instanced for a given
* set of credentials and a target organization. Once the organizationId
* is set (either out of the gate via env var or construct args, or
* by using `client.setOrganizationIdByName` when the org name is known
* but the id is not).
*
* All Favro API fetching and caching is centralized and managed in
* this class. "Entities" returned from Favro API endpoints are wrapped
* in per-type class instances, providing shortcuts to many of the methods
* here.
*
* Entities store their raw data, as originally fetched from Favro,
* available via the `.toJSON()` method (this method is automatically
* used by JSON.stringify(), allowing you to use that general function
* to get the raw data back). Note that the raw data
* **does not necessarily get updated** by Bravo when things are mutated.
*
* @example
* const client = new BravoClient();
* await client.setOrganizationIdByName('my-org');
* const newCollection = await client.createCollection('My New Collection');
* await newCollection.delete();
* // ^^ shortcut for `await client.deleteCollection(newCollection.collectionId)`
*/
export class BravoClient extends FavroClient {
//#region Organizations
private cache = new BravoClientCache();
Expand Down Expand Up @@ -289,7 +314,7 @@ export class BravoClient extends FavroClient {
{ method: 'get', query: { collectionId } },
BravoWidget,
)) as BravoResponseEntities<DataFavroWidget, BravoWidget>;
this.cache.addWidgets(res, collectionId);
this.cache.setWidgets(res, collectionId);
}
return this.cache.getWidgets(collectionId)!;
}
Expand All @@ -311,7 +336,7 @@ export class BravoClient extends FavroClient {
body: {
collectionId,
name,
type: options?.type || 'backlog',
type: options?.type || 'board',
color: options?.color || 'cyan',
},
},
Expand Down Expand Up @@ -413,18 +438,21 @@ export class BravoClient extends FavroClient {
);
const column = (await res.getFirstEntity()) as BravoColumn;
assertBravoClaim(column, `Failed to create widget`);
// TODO: UPDATE CACHE
this.cache.addColumn(widgetCommonId, column);
return column;
}

async listColumns(widgetCommonId: string) {
const res = (await this.requestWithReturnedEntities(
`columns`,
{ method: 'get', query: { widgetCommonId } },
BravoColumn,
)) as BravoResponseEntities<DataFavroColumn, BravoColumn>;
// TODO: UPDATE CACHE
return await res.getAllEntities();
if (!this.cache.getColumns(widgetCommonId)) {
const res = (await this.requestWithReturnedEntities(
`columns`,
{ method: 'get', query: { widgetCommonId } },
BravoColumn,
)) as BravoResponseEntities<DataFavroColumn, BravoColumn>;
const columns = await res.getAllEntities();
this.cache.setColumns(widgetCommonId, columns);
}
return this.cache.getColumns(widgetCommonId)!;
}

/**
Expand All @@ -437,9 +465,11 @@ export class BravoClient extends FavroClient {
return await find(await this.listColumns(widgetCommonId), matchFunction);
}

async deleteColumnById(columnId: string) {
// TODO: UPDATE CACHE
async deleteColumn(widgetCommonId: string, columnId: string) {
// Note: technically we don't NEED the widgetId to delete a column,
// but coupling these together is useful and allows for cache management.
await this.deleteEntity(`columns/${columnId}`);
this.cache.removeColumn(widgetCommonId, columnId);
}

//#endregion
Expand Down
56 changes: 55 additions & 1 deletion src/lib/clientLib/BravoClientCache.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { BravoUser } from '$entities/users';
import { BravoCollection } from '$entities/BravoCollection.js';
import type { BravoResponseWidgets } from './BravoResponse.js';
import { assertBravoClaim } from '$lib/errors.js';
import { BravoColumn } from '../entities/BravoColumn.js';

export class BravoClientCache {
protected _organizations?: BravoOrganization[];
Expand All @@ -13,6 +14,13 @@ export class BravoClientCache {
* used to key the paging result from not using a collectionId (global).
*/
protected _widgets: Map<string, BravoResponseWidgets> = new Map();
/**
* Widget columns are fetched separately via the API. They can
* be fetched directly, but more likely will all be fetched at once
* for a given Widget. Caching on a per-widget basis probably
* makes the most sense.
*/
protected _columns: Map<string, BravoColumn[]> = new Map();

get collections() {
// @ts-expect-error
Expand Down Expand Up @@ -53,11 +61,56 @@ export class BravoClientCache {
* a given collection. If the collectionId is unset, or `''`, it's
* assumed the widget pager is from a
*/
addWidgets(widgetPager: BravoResponseWidgets, collectionId = '') {
setWidgets(widgetPager: BravoResponseWidgets, collectionId = '') {
assertBravoClaim(widgetPager, 'Must provide a widget pager!');
this._widgets.set(collectionId, widgetPager);
}

getColumns(collectionId: string) {
const columns = this._columns.get(collectionId);
return columns && [...columns];
}

/** Set the cache for the columns of a widget */
setColumns(widgetCommonId: string, columns: BravoColumn[]) {
assertBravoClaim(widgetCommonId, 'Must provide a widget id!');
this._columns.set(widgetCommonId, [...columns]);
}

/**
* Replace/add a cached column for a widget.
* Useful for updating the cache after adding or
* updating a column.
* Does nothing if there isn't already a cached
* list of columns for this widget.
*
* **Use with caution:** you can create bad caches with this!
*/
addColumn(widgetCommonId: string, column: BravoColumn) {
this.removeColumn(widgetCommonId, column.columnId);
// Only add if the cache already exists
this._columns.get(widgetCommonId)?.push(column);
}

/**
* Remove a cached column for a widget.
* Useful for updating the cache after deleting a column.
* Does nothing if there isn't already a cached
* list of columns for this widget.
*
* **Use with caution:** you can create bad caches with this!
*/
removeColumn(widgetCommonId: string, columnId: string) {
const columns = this._columns.get(widgetCommonId);
if (!columns) {
return;
}
const idx = columns.findIndex((col) => col.columnId == columnId);
if (idx > -1) {
columns.splice(idx, 1);
}
}

/**
* Add a collection to the cache *if the cache already exists*,
* e.g. for updating it after creating a new collection. Ensures
Expand Down Expand Up @@ -103,5 +156,6 @@ export class BravoClientCache {
this._organizations = undefined;
this._collections = undefined;
this._widgets.clear();
this._columns.clear();
}
}

0 comments on commit 956dcd7

Please sign in to comment.