2018-03-12 11:37:04 -07:00
|
|
|
import { IOwner, IOwnerResponse, IOwnerTypeResponse } from 'wherehows-web/typings/api/datasets/owners';
|
2017-08-24 11:31:54 -07:00
|
|
|
import {
|
|
|
|
IPartyEntity,
|
|
|
|
IPartyEntityResponse,
|
|
|
|
IPartyProps,
|
2017-11-10 10:28:50 -08:00
|
|
|
IUserEntityMap
|
2017-08-24 11:31:54 -07:00
|
|
|
} from 'wherehows-web/typings/api/datasets/party-entities';
|
2018-07-19 16:18:06 -07:00
|
|
|
import { isNotFoundApiError } from 'wherehows-web/utils/api';
|
2018-03-12 11:37:04 -07:00
|
|
|
import { datasetUrlByUrn } from 'wherehows-web/utils/api/datasets/shared';
|
2017-11-13 11:07:38 -08:00
|
|
|
import { getJSON, postJSON } from 'wherehows-web/utils/api/fetcher';
|
2018-01-24 15:41:46 -08:00
|
|
|
import { getApiRoot, ApiStatus } from 'wherehows-web/utils/api/shared';
|
2018-02-21 09:46:04 -08:00
|
|
|
import { arrayFilter, arrayMap } from 'wherehows-web/utils/array';
|
2018-07-31 13:31:57 -07:00
|
|
|
import { omit } from 'wherehows-web/utils/object';
|
2017-08-24 00:23:48 -07:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Defines a string enum for valid owner types
|
|
|
|
*/
|
2017-11-13 17:54:43 -08:00
|
|
|
enum OwnerIdType {
|
2017-08-24 00:23:48 -07:00
|
|
|
User = 'USER',
|
|
|
|
Group = 'GROUP'
|
|
|
|
}
|
|
|
|
|
2017-11-10 00:08:50 -08:00
|
|
|
/**
|
|
|
|
* Defines the string enum for the OwnerType attribute
|
|
|
|
* @type {string}
|
|
|
|
*/
|
2017-11-13 17:54:43 -08:00
|
|
|
enum OwnerType {
|
2018-04-10 10:30:04 -07:00
|
|
|
Owner = 'DataOwner',
|
2017-11-10 00:08:50 -08:00
|
|
|
Consumer = 'Consumer',
|
|
|
|
Delegate = 'Delegate',
|
|
|
|
Producer = 'Producer',
|
|
|
|
Stakeholder = 'Stakeholder'
|
|
|
|
}
|
|
|
|
|
2017-11-09 09:47:57 -08:00
|
|
|
/**
|
|
|
|
* Accepted string values for the namespace of a user
|
|
|
|
*/
|
2017-11-13 17:54:43 -08:00
|
|
|
enum OwnerUrnNamespace {
|
2017-11-09 09:47:57 -08:00
|
|
|
corpUser = 'urn:li:corpuser',
|
2018-02-28 10:20:26 -08:00
|
|
|
groupUser = 'urn:li:corpGroup',
|
|
|
|
multiProduct = 'urn:li:multiProduct'
|
2017-11-09 09:47:57 -08:00
|
|
|
}
|
|
|
|
|
2017-11-13 17:54:43 -08:00
|
|
|
enum OwnerSource {
|
2017-11-09 09:47:57 -08:00
|
|
|
Scm = 'SCM',
|
|
|
|
Nuage = 'NUAGE',
|
|
|
|
Sos = 'SOS',
|
|
|
|
Db = 'DB',
|
|
|
|
Audit = 'AUDIT',
|
|
|
|
Jira = 'JIRA',
|
|
|
|
RB = 'RB',
|
|
|
|
Ui = 'UI',
|
|
|
|
Fs = 'FS',
|
|
|
|
Other = 'OTHER'
|
|
|
|
}
|
|
|
|
|
2018-02-21 09:46:04 -08:00
|
|
|
/**
|
|
|
|
* Returns the dataset owners url by urn
|
|
|
|
* @param {string} urn
|
|
|
|
* @return {string}
|
|
|
|
*/
|
|
|
|
const datasetOwnersUrlByUrn = (urn: string): string => `${datasetUrlByUrn(urn)}/owners`;
|
|
|
|
|
2018-05-29 13:17:15 -07:00
|
|
|
/**
|
|
|
|
* Returns the url to fetch the suggested dataset owners by urn
|
|
|
|
* @param urn
|
|
|
|
*/
|
|
|
|
const datasetSuggestedOwnersUrlByUrn = (urn: string): string => `${datasetUrlByUrn(urn)}/owners/suggestion`;
|
|
|
|
|
2018-02-21 09:46:04 -08:00
|
|
|
/**
|
|
|
|
* Returns the party entities url
|
|
|
|
* @type {string}
|
|
|
|
*/
|
2018-01-24 15:41:46 -08:00
|
|
|
const partyEntitiesUrl = `${getApiRoot()}/party/entities`;
|
2017-08-24 00:23:48 -07:00
|
|
|
|
2018-02-21 09:46:04 -08:00
|
|
|
/**
|
|
|
|
* Returns the owner types url
|
|
|
|
* @return {string}
|
|
|
|
*/
|
|
|
|
const datasetOwnerTypesUrl = () => `${getApiRoot()}/owner/types`;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Modifies an owner object by applying the modified date property as a Date object
|
|
|
|
* @param {IOwner} owner
|
|
|
|
* @return {IOwner}
|
|
|
|
*/
|
|
|
|
const ownerWithModifiedTimeAsDate = (owner: IOwner): IOwner => ({
|
|
|
|
...owner,
|
|
|
|
modifiedTime: new Date(<number>owner.modifiedTime)
|
|
|
|
}); // Api response is always in number format
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Modifies a list of owners with a modified date property as a Date object
|
|
|
|
* @type {(array: Array<IOwner>) => Array<IOwner>}
|
|
|
|
*/
|
|
|
|
const ownersWithModifiedTimeAsDate = arrayMap(ownerWithModifiedTimeAsDate);
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Reads the owners for dataset by urn
|
|
|
|
* @param {string} urn
|
|
|
|
* @return {Promise<Array<IOwner>>}
|
|
|
|
*/
|
2018-03-12 17:35:01 -07:00
|
|
|
const readDatasetOwnersByUrn = async (urn: string): Promise<IOwnerResponse> => {
|
|
|
|
let owners: Array<IOwner> = [],
|
|
|
|
fromUpstream = false,
|
|
|
|
datasetUrn = '';
|
2018-02-27 17:21:44 -08:00
|
|
|
|
|
|
|
try {
|
2018-03-12 17:35:01 -07:00
|
|
|
({ owners = [], fromUpstream, datasetUrn } = await getJSON<IOwnerResponse>({ url: datasetOwnersUrlByUrn(urn) }));
|
|
|
|
return { owners: ownersWithModifiedTimeAsDate(owners), fromUpstream, datasetUrn };
|
2018-02-27 17:21:44 -08:00
|
|
|
} catch (e) {
|
2018-07-19 16:18:06 -07:00
|
|
|
if (isNotFoundApiError(e)) {
|
2018-03-12 17:35:01 -07:00
|
|
|
return { owners, fromUpstream, datasetUrn };
|
2018-02-27 17:21:44 -08:00
|
|
|
} else {
|
|
|
|
throw e;
|
|
|
|
}
|
|
|
|
}
|
2018-02-21 09:46:04 -08:00
|
|
|
};
|
|
|
|
|
2018-05-29 13:17:15 -07:00
|
|
|
/**
|
|
|
|
* For the specific dataset, fetches the system suggested owners for that dataset
|
|
|
|
* @param urn - unique identifier for the dataset
|
|
|
|
* @return {Promise<Array<IOwner>>}
|
|
|
|
*/
|
|
|
|
const readDatasetSuggestedOwnersByUrn = async (urn: string): Promise<IOwnerResponse> => {
|
|
|
|
let owners: Array<IOwner> = [],
|
|
|
|
fromUpstream = false,
|
|
|
|
datasetUrn = '';
|
|
|
|
|
|
|
|
try {
|
|
|
|
({ owners = [], fromUpstream, datasetUrn } = await getJSON<IOwnerResponse>({
|
|
|
|
url: datasetSuggestedOwnersUrlByUrn(urn)
|
|
|
|
}));
|
|
|
|
return { owners: ownersWithModifiedTimeAsDate(owners), fromUpstream, datasetUrn };
|
|
|
|
} catch (e) {
|
2018-07-19 16:18:06 -07:00
|
|
|
if (isNotFoundApiError(e)) {
|
2018-05-29 13:17:15 -07:00
|
|
|
return { owners, fromUpstream, datasetUrn };
|
|
|
|
} else {
|
|
|
|
throw e;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2018-02-21 09:46:04 -08:00
|
|
|
/**
|
|
|
|
* Updates the owners on a dataset by urn
|
|
|
|
* @param {string} urn
|
|
|
|
* @param {string} csrfToken
|
|
|
|
* @param {Array<IOwner>} updatedOwners
|
|
|
|
* @return {Promise<void>}
|
|
|
|
*/
|
2018-02-27 00:03:06 -08:00
|
|
|
const updateDatasetOwnersByUrn = (urn: string, csrfToken: string = '', updatedOwners: Array<IOwner>): Promise<{}> => {
|
2018-07-31 13:31:57 -07:00
|
|
|
const ownersWithoutModifiedTime = arrayMap((owner: IOwner) => omit(owner, ['modifiedTime']));
|
2018-02-21 15:31:25 -08:00
|
|
|
|
2018-02-27 00:03:06 -08:00
|
|
|
return postJSON<{}>({
|
2018-02-21 09:46:04 -08:00
|
|
|
url: datasetOwnersUrlByUrn(urn),
|
|
|
|
headers: { 'csrf-token': csrfToken },
|
|
|
|
data: {
|
|
|
|
csrfToken,
|
2018-02-21 15:31:25 -08:00
|
|
|
owners: ownersWithoutModifiedTime(updatedOwners) // strips the modified time from each owner, remote is source of truth
|
2018-02-21 09:46:04 -08:00
|
|
|
}
|
|
|
|
});
|
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Reads the owner types list on a dataset
|
|
|
|
* @return {Promise<Array<OwnerType>>}
|
|
|
|
*/
|
|
|
|
const readDatasetOwnerTypes = async (): Promise<Array<OwnerType>> => {
|
|
|
|
const url = datasetOwnerTypesUrl();
|
|
|
|
const { ownerTypes = [] } = await getJSON<IOwnerTypeResponse>({ url });
|
|
|
|
return ownerTypes.sort((a: string, b: string) => a.localeCompare(b));
|
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Determines if an owner type supplied is not of type consumer
|
|
|
|
* @param {OwnerType} ownerType
|
|
|
|
* @return {boolean}
|
|
|
|
*/
|
|
|
|
const isNotAConsumer = (ownerType: OwnerType): boolean => ownerType !== OwnerType.Consumer;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Reads the dataset owner types and filters out the OwnerType.Consumer type from the list
|
|
|
|
* @return {Promise<Array<OwnerType>>}
|
|
|
|
*/
|
|
|
|
const readDatasetOwnerTypesWithoutConsumer = async () => arrayFilter(isNotAConsumer)(await readDatasetOwnerTypes());
|
|
|
|
|
2017-08-24 00:23:48 -07:00
|
|
|
/**
|
|
|
|
* Requests party entities and if the response status is OK, resolves with an array of entities
|
|
|
|
* @return {Promise<Array<IPartyEntity>>}
|
|
|
|
*/
|
2017-11-13 17:54:43 -08:00
|
|
|
const readPartyEntities = async (): Promise<Array<IPartyEntity>> => {
|
2017-11-13 11:07:38 -08:00
|
|
|
const { status, userEntities = [], msg } = await getJSON<IPartyEntityResponse>({ url: partyEntitiesUrl });
|
|
|
|
return status === ApiStatus.OK ? userEntities : Promise.reject(msg);
|
2017-08-24 00:23:48 -07:00
|
|
|
};
|
|
|
|
|
2017-08-24 11:31:54 -07:00
|
|
|
/**
|
|
|
|
* IIFE prepares the environment scope and returns a closure function that ensures that
|
|
|
|
* there is ever only one inflight request for userEntities.
|
|
|
|
* Resolves all subsequent calls with the result for the initial invocation.
|
|
|
|
* userEntitiesSource property is also lazy evaluated and cached for app lifetime.
|
|
|
|
* @type {() => Promise<IPartyProps>}
|
|
|
|
*/
|
2017-11-13 17:54:43 -08:00
|
|
|
const getUserEntities: () => Promise<IPartyProps> = (() => {
|
2017-08-24 11:31:54 -07:00
|
|
|
/**
|
|
|
|
* Memoized reference to the resolved value of a previous invocation to curried function in getUserEntities
|
|
|
|
* @type {{result: IPartyProps | null}}
|
|
|
|
*/
|
2018-08-01 16:35:10 -07:00
|
|
|
const cache: { result: IPartyProps | null; userEntitiesSource: Array<Extract<keyof IUserEntityMap, string>> } = {
|
2017-08-24 11:31:54 -07:00
|
|
|
result: null,
|
|
|
|
userEntitiesSource: []
|
|
|
|
};
|
|
|
|
let inflightRequest: Promise<Array<IPartyEntity>>;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Invokes the requestor for party entities, and adds perf optimizations listed above
|
|
|
|
* @return {Promise<IPartyProps>}
|
|
|
|
*/
|
|
|
|
return async (): Promise<IPartyProps> => {
|
|
|
|
// If a previous request has already resolved, return the cached value
|
|
|
|
if (cache.result) {
|
|
|
|
return cache.result;
|
|
|
|
}
|
|
|
|
// If we don't already have a previous api request for party entities,
|
|
|
|
// assign a new one to free variable
|
|
|
|
if (!inflightRequest) {
|
2017-11-13 11:07:38 -08:00
|
|
|
inflightRequest = readPartyEntities();
|
2017-08-24 11:31:54 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
const userEntities: Array<IPartyEntity> = await inflightRequest;
|
|
|
|
|
|
|
|
return (cache.result = {
|
|
|
|
userEntities,
|
2017-11-13 11:07:38 -08:00
|
|
|
userEntitiesMaps: readPartyEntitiesMap(userEntities),
|
2017-08-24 11:31:54 -07:00
|
|
|
// userEntitiesSource is not usually needed immediately
|
|
|
|
// hence using a getter for lazy evaluation
|
|
|
|
get userEntitiesSource() {
|
|
|
|
const userEntitiesSource = cache.userEntitiesSource;
|
|
|
|
if (userEntitiesSource.length) {
|
|
|
|
return userEntitiesSource;
|
|
|
|
}
|
|
|
|
|
|
|
|
return (cache.userEntitiesSource = Object.keys(this.userEntitiesMaps));
|
|
|
|
}
|
|
|
|
});
|
|
|
|
};
|
|
|
|
})();
|
|
|
|
|
2017-08-24 00:23:48 -07:00
|
|
|
/**
|
|
|
|
* Transforms a list of party entities into a map of entity label to displayName value
|
|
|
|
* @param {Array<IPartyEntity>} partyEntities
|
2017-11-10 10:28:50 -08:00
|
|
|
* @return {IUserEntityMap}
|
2017-08-24 00:23:48 -07:00
|
|
|
*/
|
2017-11-13 17:54:43 -08:00
|
|
|
const readPartyEntitiesMap = (partyEntities: Array<IPartyEntity>): IUserEntityMap =>
|
2017-08-24 00:23:48 -07:00
|
|
|
partyEntities.reduce(
|
|
|
|
(map: { [label: string]: string }, { label, displayName }: IPartyEntity) => ((map[label] = displayName), map),
|
|
|
|
{}
|
|
|
|
);
|
|
|
|
|
2017-11-13 17:54:43 -08:00
|
|
|
export {
|
2018-02-21 09:46:04 -08:00
|
|
|
readDatasetOwnersByUrn,
|
2018-05-29 13:17:15 -07:00
|
|
|
readDatasetSuggestedOwnersByUrn,
|
2018-02-21 09:46:04 -08:00
|
|
|
updateDatasetOwnersByUrn,
|
|
|
|
readDatasetOwnerTypesWithoutConsumer,
|
2017-11-13 17:54:43 -08:00
|
|
|
readPartyEntities,
|
|
|
|
readPartyEntitiesMap,
|
|
|
|
getUserEntities,
|
|
|
|
OwnerIdType,
|
|
|
|
OwnerType,
|
|
|
|
OwnerUrnNamespace,
|
|
|
|
OwnerSource
|
|
|
|
};
|