// (C) 2007-2020 GoodData Corporation
import { getIn, handlePolling, getAllPagesByOffsetLimit } from "./util";
import { ITimezone, IColor, IColorPalette, IFeatureFlags } from "./interfaces";
import { IStyleSettingsResponse, IFeatureFlagsResponse } from "./apiResponsesInterfaces";
import { XhrModule, ApiResponse } from "./xhr";
const DEFAULT_PALETTE = [
{ r: 0x2b, g: 0x6b, b: 0xae },
{ r: 0x69, g: 0xaa, b: 0x51 },
{ r: 0xee, g: 0xb1, b: 0x4c },
{ r: 0xd5, g: 0x3c, b: 0x38 },
{ r: 0x89, g: 0x4d, b: 0x94 },
{ r: 0x73, g: 0x73, b: 0x73 },
{ r: 0x44, g: 0xa9, b: 0xbe },
{ r: 0x96, g: 0xbd, b: 0x5f },
{ r: 0xfd, g: 0x93, b: 0x69 },
{ r: 0xe1, g: 0x5d, b: 0x86 },
{ r: 0x7c, g: 0x6f, b: 0xad },
{ r: 0xa5, g: 0xa5, b: 0xa5 },
{ r: 0x7a, g: 0xa6, b: 0xd5 },
{ r: 0x82, g: 0xd0, b: 0x8d },
{ r: 0xff, g: 0xd2, b: 0x89 },
{ r: 0xf1, g: 0x84, b: 0x80 },
{ r: 0xbf, g: 0x90, b: 0xc6 },
{ r: 0xbf, g: 0xbf, b: 0xbf },
];
const isProjectCreated = (project: any) => {
// TODO
const projectState = project.content.state;
return projectState === "ENABLED" || projectState === "DELETED";
};
export interface IProjectConfigSettingItem {
settingItem: {
key: string;
links: {
self: string;
};
source: string;
value: string;
};
}
export interface IProjectConfigResponse {
settings: {
items: IProjectConfigSettingItem[];
};
}
// Parses string values to boolean, number and string
export const parseSettingItemValue = (value: string): boolean | number | string => {
if (value === "true") {
return true;
}
if (value === "false") {
return false;
}
const nr = Number(value);
if (nr.toString() === value) {
return nr;
}
return value;
};
/**
* Functions for working with projects
*
* @class project
* @module project
*/
export class ProjectModule {
constructor(private xhr: XhrModule) {}
/**
* Get current project id
*
* @method getCurrentProjectId
* @return {String} current project identifier
*/
public getCurrentProjectId() {
return this.xhr
.get("/gdc/app/account/bootstrap")
.then(r => r.getData())
.then(this.getCurrentProjectIdInBootstrap);
}
/**
* Return current project id in bootstrap
* @method getCurrentProjectIdInBootstrap
* @param bootstrapData - data was got from bootstrap resource
*/
public getCurrentProjectIdInBootstrap(bootstrapData: any): string | null {
const currentProject = bootstrapData.bootstrapResource.current.project;
// handle situation in which current project is missing (e.g. new user)
if (!currentProject) {
return null;
}
return bootstrapData.bootstrapResource.current.project.links.self.split("/").pop();
}
/**
* Fetches projects available for the user represented by the given profileId
*
* @method getProjects
* @param {String} profileId - User profile identifier
* @return {Array} An Array of projects
*/
public getProjects(profileId: string) {
return getAllPagesByOffsetLimit(
this.xhr,
`/gdc/account/profile/${profileId}/projects`,
"projects",
).then((result: any) => result.map((p: any) => p.project));
}
/**
* Fetches all datasets for the given project
*
* @method getDatasets
* @param {String} projectId - GD project identifier
* @return {Array} An array of objects containing datasets metadata
*/
public getDatasets(projectId: string) {
return this.xhr
.get(`/gdc/md/${projectId}/query/datasets`)
.then(r => r.getData())
.then(getIn("query.entries"));
}
/**
* Fetches a chart color palette for a project represented by the given
* projectId parameter.
*
* @method getColorPalette
* @param {String} projectId - A project identifier
* @return {Array} An array of objects with r, g, b fields representing a project's
* color palette
*/
public getColorPalette(projectId: string) {
return this.xhr
.get(`/gdc/projects/${projectId}/styleSettings`)
.then(r => r.getData())
.then(
(result: any) => {
return result.styleSettings.chartPalette.map((c: any) => {
return {
r: c.fill.r,
g: c.fill.g,
b: c.fill.b,
};
});
},
err => {
if (err.status === 200) {
return DEFAULT_PALETTE;
}
throw new Error(err.statusText);
},
);
}
/**
* Fetches a chart color palette for a project represented by the given
* projectId parameter.
*
* @method getColorPaletteWithGuids
* @param {String} projectId - A project identifier
* @return {Array} An array of objects representing a project's
* color palette with color guid or undefined
*/
public getColorPaletteWithGuids(projectId: string): Promise<IColorPalette | undefined> {
return this.xhr
.get(`/gdc/projects/${projectId}/styleSettings`)
.then((apiResponse: ApiResponse) => {
return apiResponse.getData();
})
.then((result: IStyleSettingsResponse) => {
if (result && result.styleSettings) {
return result.styleSettings.chartPalette;
} else {
return undefined;
}
});
}
/**
* Sets given colors as a color palette for a given project.
*
* @method setColorPalette
* @param {String} projectId - GD project identifier
* @param {Array} colors - An array of colors that we want to use within the project.
* Each color should be an object with r, g, b fields. // TODO really object?
*/
public setColorPalette(projectId: string, colors: IColor[]): Promise<ApiResponse> {
return this.xhr.put(`/gdc/projects/${projectId}/styleSettings`, {
body: {
styleSettings: {
chartPalette: colors.map((fill, idx: number) => {
return { fill, guid: `guid${idx}` };
}),
},
},
});
}
/**
* Gets current timezone and its offset. Example output:
*
* {
* id: 'Europe/Prague',
* displayName: 'Central European Time',
* currentOffsetMs: 3600000
* }
*
* @method getTimezone
* @param {String} projectId - GD project identifier
*/
public getTimezone(projectId: string): Promise<ITimezone> {
const bootstrapUrl = `/gdc/app/account/bootstrap?projectId=${projectId}`;
return this.xhr
.get(bootstrapUrl)
.then(r => r.getData())
.then((result: any) => {
return result.bootstrapResource.current.timezone;
});
}
public setTimezone(projectId: string, timezone: ITimezone) {
const timezoneServiceUrl = `/gdc/md/${projectId}/service/timezone`;
const data = {
service: { timezone },
};
return this.xhr
.post(timezoneServiceUrl, {
body: data,
})
.then(r => r.getData());
}
/**
* Create project
* Note: returns a promise which is resolved when the project creation is finished
*
* @experimental
* @method createProject
* @param {String} title
* @param {String} authorizationToken
* @param {Object} options for project creation (summary, projectTemplate, ...)
* @return {Object} created project object
*/
public createProject(title: string, authorizationToken: string, options: any = {}) {
const {
summary,
projectTemplate,
driver = "Pg",
environment = "TESTING",
guidedNavigation = 1,
} = options;
return this.xhr
.post("/gdc/projects", {
body: JSON.stringify({
project: {
content: {
guidedNavigation,
driver,
authorizationToken,
environment,
},
meta: {
title,
summary,
projectTemplate,
},
},
}),
})
.then(r => r.getData())
.then((project: any) =>
handlePolling(
this.xhr.get.bind(this.xhr),
project.uri,
(response: any) => {
// TODO project response
return isProjectCreated(response.project);
},
options,
),
);
}
/**
* Delete project
*
* @method deleteProject
* @param {String} projectId
*/
public deleteProject(projectId: string) {
return this.xhr.del(`/gdc/projects/${projectId}`);
}
/**
* Gets aggregated feature flags for given project and current user
*
* @method getFeatureFlags
* @param {String} projectId - A project identifier
* @return {IFeatureFlags} Hash table of feature flags and theirs values where feature flag is as key
*/
public getFeatureFlags(projectId: string): Promise<IFeatureFlags> {
return this.xhr
.get(`/gdc/app/projects/${projectId}/featureFlags`)
.then((apiResponse: ApiResponse) => {
return apiResponse.getData();
})
.then((result: IFeatureFlagsResponse) => {
if (result && result.featureFlags) {
return result.featureFlags;
}
return {};
});
}
/**
* Gets project config including project specific feature flags
*
* @param {String} projectId - A project identifier
* @return {IProjectConfigSettingItem[]} An array of project config setting items
*/
public getConfig(projectId: string): Promise<IProjectConfigSettingItem[]> {
return this.xhr
.get(`/gdc/app/projects/${projectId}/config`)
.then((apiResponse: ApiResponse) => {
const projectConfig = apiResponse.getData();
return projectConfig;
})
.then((result: IProjectConfigResponse) => {
if (result && result.settings && result.settings.items) {
return result.settings.items;
}
return [];
});
}
/**
* Gets project specific feature flags
*
* @param {String} projectId - A project identifier
* @param {String} source - optional filter settingItems with specific source
* @return {IFeatureFlags} Hash table of feature flags and theirs values where feature flag is as key
*/
public getProjectFeatureFlags(projectId: string, source?: string): Promise<IFeatureFlags> {
return this.getConfig(projectId).then((settingItems: IProjectConfigSettingItem[]) => {
const filteredSettingItems = source
? settingItems.filter(settingItem => settingItem.settingItem.source === source)
: settingItems;
const featureFlags: IFeatureFlags = {};
filteredSettingItems.forEach(settingItem => {
featureFlags[settingItem.settingItem.key] = parseSettingItemValue(
settingItem.settingItem.value,
);
});
return featureFlags;
});
}
}