Skip to content

Commit

Permalink
feat: schedule export, show schedules into menu, caching automations
Browse files Browse the repository at this point in the history
 - Caching backend for automations + tests

risk: medium
JIRA: F1-437
  • Loading branch information
hackerstanislav committed Jul 17, 2024
1 parent a992847 commit 53d83a4
Show file tree
Hide file tree
Showing 29 changed files with 925 additions and 78 deletions.
6 changes: 6 additions & 0 deletions libs/sdk-backend-base/api/sdk-backend-base.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,7 @@ import { IWidgetAlertCount } from '@gooddata/sdk-backend-spi';
import { IWidgetAlertDefinition } from '@gooddata/sdk-model';
import { IWidgetReferences } from '@gooddata/sdk-backend-spi';
import { IWorkspaceAttributesService } from '@gooddata/sdk-backend-spi';
import { IWorkspaceAutomationService } from '@gooddata/sdk-backend-spi';
import { IWorkspaceCatalog } from '@gooddata/sdk-backend-spi';
import { IWorkspaceCatalogAvailableItemsFactory } from '@gooddata/sdk-backend-spi';
import { IWorkspaceCatalogFactory } from '@gooddata/sdk-backend-spi';
Expand Down Expand Up @@ -209,6 +210,9 @@ export class AuthProviderCallGuard implements IAuthProviderCallGuard {
reset: () => void;
}

// @alpha (undocumented)
export type AutomationsDecoratorFactory = (automations: IWorkspaceAutomationService, workspace: string) => IWorkspaceAutomationService;

// @beta
export class Builder<T> implements IBuilder<T> {
constructor(item: Partial<T>, validator?: ((item: Partial<T>) => void) | undefined);
Expand Down Expand Up @@ -254,6 +258,7 @@ export type CachingConfiguration = {
maxSecuritySettingsOrgUrls?: number;
maxSecuritySettingsOrgUrlsAge?: number;
maxAttributeWorkspaces?: number;
maxAutomationsWorkspaces?: number;
maxAttributeDisplayFormsPerWorkspace?: number;
maxAttributesPerWorkspace?: number;
maxAttributeElementResultsPerWorkspace?: number;
Expand Down Expand Up @@ -598,6 +603,7 @@ export type DecoratorFactories = {
securitySettings?: SecuritySettingsDecoratorFactory;
workspaceSettings?: WorkspaceSettingsDecoratorFactory;
attributes?: AttributesDecoratorFactory;
automations?: AutomationsDecoratorFactory;
dashboards?: DashboardsDecoratorFactory;
};

Expand Down
203 changes: 203 additions & 0 deletions libs/sdk-backend-base/src/cachingBackend/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,13 +22,20 @@ import {
IWorkspaceSettings,
IWorkspaceSettingsService,
ValidationContext,
IWorkspaceAutomationService,
IGetAutomationsOptions,
IGetAutomationOptions,
IAutomationsQuery,
IAutomationsQueryResult,
AutomationType,
} from "@gooddata/sdk-backend-spi";
import {
AttributesDecoratorFactory,
CatalogDecoratorFactory,
ExecutionDecoratorFactory,
SecuritySettingsDecoratorFactory,
WorkspaceSettingsDecoratorFactory,
AutomationsDecoratorFactory,
} from "../decoratedBackend/types.js";
import { decoratedBackend } from "../decoratedBackend/index.js";
import { LRUCache } from "lru-cache";
Expand Down Expand Up @@ -67,9 +74,15 @@ import {
IMeasureDefinitionType,
IRelativeDateFilter,
IAbsoluteDateFilter,
IAutomationMetadataObject,
IAutomationMetadataObjectDefinition,
} from "@gooddata/sdk-model";
import { DecoratedWorkspaceAttributesService } from "../decoratedBackend/attributes.js";
import { DecoratedWorkspaceSettingsService } from "../decoratedBackend/workspaceSettings.js";
import {
DecoratedWorkspaceAutomationsService,
DecoratedAutomationsQuery,
} from "../decoratedBackend/automations.js";

//
// Supporting types
Expand All @@ -93,6 +106,11 @@ type AttributeCacheEntry = {
attributeElementResults?: LRUCache<string, Promise<IElementsQueryResult>>;
};

type AutomationCacheEntry = {
automations: LRUCache<string, Promise<IAutomationMetadataObject[]>>;
queries: LRUCache<string, Promise<IAutomationsQueryResult>>;
};

type WorkspaceSettingsCacheEntry = {
userWorkspaceSettings: LRUCache<string, Promise<IUserWorkspaceSettings>>;
workspaceSettings: LRUCache<string, Promise<IWorkspaceSettings>>;
Expand All @@ -104,6 +122,7 @@ type CachingContext = {
workspaceCatalogs?: LRUCache<string, CatalogCacheEntry>;
securitySettings?: LRUCache<string, SecuritySettingsCacheEntry>;
workspaceAttributes?: LRUCache<string, AttributeCacheEntry>;
workspaceAutomations?: LRUCache<string, AutomationCacheEntry>;
workspaceSettings?: LRUCache<string, WorkspaceSettingsCacheEntry>;
};
config: CachingConfiguration;
Expand Down Expand Up @@ -737,6 +756,165 @@ class WithAttributesCaching extends DecoratedWorkspaceAttributesService {
}
}

//AUTOMATIONS CACHING

function getOrCreateAutomationsCache(ctx: CachingContext, workspace: string): AutomationCacheEntry {
const cache = ctx.caches.workspaceAutomations!;
let cacheEntry = cache.get(workspace);

if (!cacheEntry) {
cacheEntry = {
automations: new LRUCache<string, Promise<IAutomationMetadataObject[]>>({
max: ctx.config.maxAutomationsWorkspaces!,
}),
queries: new LRUCache<string, Promise<IAutomationsQueryResult>>({
max: ctx.config.maxAutomationsWorkspaces!,
}),
};
cache.set(workspace, cacheEntry);
}

return cacheEntry;
}

class CachedAutomationsQueryFactory extends DecoratedAutomationsQuery {
private settings: {
size: number;
page: number;
filter: { title?: string };
sort: NonNullable<unknown>;
type: AutomationType | undefined;
totalCount: number | undefined;
} = {
size: 50,
page: 0,
filter: {},
sort: {},
type: undefined,
totalCount: undefined,
};

constructor(
decorated: IAutomationsQuery,
private readonly ctx: CachingContext,
private readonly workspace: string,
) {
super(decorated);
}

withSize(size: number): IAutomationsQuery {
this.settings.size = size;
super.withSize(size);
return this;
}

withPage(page: number): IAutomationsQuery {
this.settings.page = page;
super.withPage(page);
return this;
}

withFilter(filter: { title?: string }): IAutomationsQuery {
this.settings.filter = { ...filter };
this.settings.totalCount = undefined;
super.withFilter(filter);
return this;
}

withSorting(sort: string[]): IAutomationsQuery {
this.settings.sort = { sort };
super.withSorting(sort);
return this;
}

withType(type: AutomationType): IAutomationsQuery {
this.settings.type = type;
super.withType(type);
return this;
}

public query(): Promise<IAutomationsQueryResult> {
const cache = getOrCreateAutomationsCache(this.ctx, this.workspace);
const key = stringify(this.settings);

const result = cache.queries.get(key);

if (!result) {
const promise = super.query().catch((e) => {
cache.queries.delete(key);
throw e;
});

cache.queries.set(key, promise);
return promise;
}

return result;
}
}

class WithAutomationsCaching extends DecoratedWorkspaceAutomationsService {
constructor(
decorated: IWorkspaceAutomationService,
private readonly ctx: CachingContext,
private readonly workspace: string,
) {
super(decorated);
}

public getAutomations(options?: IGetAutomationsOptions): Promise<IAutomationMetadataObject[]> {
const cache = getOrCreateAutomationsCache(this.ctx, this.workspace).automations;
const key = stringify(options);

const result = cache.get(key);

if (!result) {
const promise = super.getAutomations(options).catch((e) => {
cache.delete(key);
throw e;
});

cache.set(key, promise);
return promise;
}

return result;
}

public async createAutomation(
automation: IAutomationMetadataObjectDefinition,
options?: IGetAutomationOptions,
): Promise<IAutomationMetadataObject> {
const cache = getOrCreateAutomationsCache(this.ctx, this.workspace);
const result = await super.createAutomation(automation, options);
cache.automations.clear();
cache.queries.clear();
return result;
}

public async updateAutomation(
automation: IAutomationMetadataObject,
options?: IGetAutomationOptions,
): Promise<IAutomationMetadataObject> {
const cache = getOrCreateAutomationsCache(this.ctx, this.workspace);
const result = await super.updateAutomation(automation, options);
cache.automations.clear();
cache.queries.clear();
return result;
}

public async deleteAutomation(id: string): Promise<void> {
const cache = getOrCreateAutomationsCache(this.ctx, this.workspace);
await super.deleteAutomation(id);
cache.automations.clear();
cache.queries.clear();
}

getAutomationsQuery(): IAutomationsQuery {
return new CachedAutomationsQueryFactory(super.getAutomationsQuery(), this.ctx, this.workspace);
}
}

//
//
//
Expand All @@ -762,6 +940,10 @@ function cachedAttributes(ctx: CachingContext): AttributesDecoratorFactory {
return (original, workspace) => new WithAttributesCaching(original, ctx, workspace);
}

function cachedAutomations(ctx: CachingContext): AutomationsDecoratorFactory {
return (original, workspace) => new WithAutomationsCaching(original, ctx, workspace);
}

function cachingEnabled(desiredSize: number | undefined): boolean {
return desiredSize !== undefined && desiredSize > 0;
}
Expand Down Expand Up @@ -974,6 +1156,20 @@ export type CachingConfiguration = {
*/
maxAttributeWorkspaces?: number;

/**
* Maximum number of workspaces for which to cache selected {@link @gooddata/sdk-backend-spi#IWorkspaceAutomationsService} calls.
* The workspace identifier is used as cache key.
*
* When limit is reached, cache entries will be evicted using LRU policy.
*
* When no maximum number is specified, the cache will be unbounded and no evictions will happen. Unbounded
* cache may be OK in applications where number of workspaces is small - the cache will be limited
* naturally and will not grow uncontrollably.
*
* When non-positive number is specified, then no caching will be done.
*/
maxAutomationsWorkspaces?: number;

/**
* Maximum number of attribute display forms to cache per workspace.
*
Expand Down Expand Up @@ -1060,6 +1256,7 @@ export const RecommendedCachingConfiguration: CachingConfiguration = {
maxAttributesPerWorkspace: 100,
maxAttributeElementResultsPerWorkspace: 100,
maxWorkspaceSettings: 1,
maxAutomationsWorkspaces: 1,
};

/**
Expand All @@ -1084,6 +1281,7 @@ export function withCaching(
const securitySettingsCaching = cachingEnabled(config.maxSecuritySettingsOrgs);
const attributeCaching = cachingEnabled(config.maxAttributeWorkspaces);
const workspaceSettingsCaching = cachingEnabled(config.maxWorkspaceSettings);
const automationsCaching = cachingEnabled(config.maxAutomationsWorkspaces);

const ctx: CachingContext = {
caches: {
Expand All @@ -1098,6 +1296,9 @@ export function withCaching(
workspaceSettings: workspaceSettingsCaching
? new LRUCache({ max: config.maxWorkspaceSettings! })
: undefined,
workspaceAutomations: automationsCaching
? new LRUCache({ max: config.maxAutomationsWorkspaces! })
: undefined,
},
config,
capabilities: realBackend.capabilities,
Expand All @@ -1107,6 +1308,7 @@ export function withCaching(
const catalog = catalogCaching ? cachedCatalog(ctx) : identity;
const securitySettings = securitySettingsCaching ? cachedSecuritySettings(ctx) : identity;
const attributes = attributeCaching ? cachedAttributes(ctx) : identity;
const automations = automationsCaching ? cachedAutomations(ctx) : identity;
const workspaceSettings = workspaceSettingsCaching ? cachedWorkspaceSettings(ctx) : identity;

if (config.onCacheReady) {
Expand All @@ -1119,6 +1321,7 @@ export function withCaching(
securitySettings,
attributes,
workspaceSettings,
automations,
});
}

Expand Down
Loading

0 comments on commit 53d83a4

Please sign in to comment.