mirror of
https://github.com/datahub-project/datahub.git
synced 2025-08-22 16:18:10 +00:00
Merge pull request #704 from theseyi/ownership
ts all the things. refactor getuserentities: cache and allow only one…
This commit is contained in:
commit
e20a5c84cb
@ -174,6 +174,20 @@ export default Component.extend({
|
||||
updatedOwner,
|
||||
...updatingOwners.slice(ownerPosition + 1)
|
||||
];
|
||||
// The list of ldap userNames currently in the list
|
||||
const userNames = updatedOwners.mapBy('userName');
|
||||
// Checks that the userNames are not already in the list of current owners
|
||||
const hasDuplicates = new Set(userNames).size !== userNames.length;
|
||||
|
||||
if (hasDuplicates) {
|
||||
set(
|
||||
this,
|
||||
'errorMessage',
|
||||
'Uh oh! Looks like there are duplicates in the list of owners, please remove them first.'
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Full reset of the `owners` list with the new list
|
||||
sourceOwners.setObjects(updatedOwners);
|
||||
@ -213,6 +227,7 @@ export default Component.extend({
|
||||
* @return {Promise.<void>}
|
||||
*/
|
||||
async editUserName(currentOwner, userName) {
|
||||
set(this, 'errorMessage', '');
|
||||
if (userName) {
|
||||
// getUser returns a promise, treat as such
|
||||
const getUser = get(this, 'ldapUsers.getPartyEntityWithUserName');
|
||||
|
@ -3,9 +3,8 @@ import { makeUrnBreadcrumbs } from 'wherehows-web/utils/entities';
|
||||
import { datasetComplianceFor, datasetComplianceSuggestionsFor } from 'wherehows-web/utils/api/datasets/compliance';
|
||||
import {
|
||||
getDatasetOwners,
|
||||
getPartyEntities,
|
||||
isRequiredMinOwnersNotConfirmed,
|
||||
getPartyEntitiesMap
|
||||
getUserEntities,
|
||||
isRequiredMinOwnersNotConfirmed
|
||||
} from 'wherehows-web/utils/api/datasets/owners';
|
||||
|
||||
const { Route, get, set, setProperties, isPresent, inject: { service }, $: { getJSON } } = Ember;
|
||||
@ -395,15 +394,15 @@ export default Route.extend({
|
||||
|
||||
// Retrieve the current owners of the dataset and store on the controller
|
||||
(async id => {
|
||||
const [owners, userEntities] = await Promise.all([getDatasetOwners(id), getPartyEntities()]);
|
||||
const [owners, { userEntitiesSource, userEntitiesMaps }] = await Promise.all([
|
||||
getDatasetOwners(id),
|
||||
getUserEntities()
|
||||
]);
|
||||
setProperties(controller, {
|
||||
requiredMinNotConfirmed: isRequiredMinOwnersNotConfirmed(owners),
|
||||
owners,
|
||||
userEntitiesMaps: getPartyEntitiesMap(userEntities),
|
||||
get userEntitiesSource() {
|
||||
// TODO: memoize
|
||||
return Object.keys(this.userEntitiesMaps);
|
||||
}
|
||||
userEntitiesMaps,
|
||||
userEntitiesSource
|
||||
});
|
||||
})(id);
|
||||
},
|
||||
|
@ -1,74 +0,0 @@
|
||||
import Ember from 'ember';
|
||||
|
||||
const { isEmpty, Service, $: { getJSON } } = Ember;
|
||||
const partyEntitiesUrl = '/api/v1/party/entities';
|
||||
|
||||
const cache = {
|
||||
// Cache containing results from the last request to partyEntities api
|
||||
partyEntities: null
|
||||
};
|
||||
|
||||
/**
|
||||
* Async request for partyEntities. Caches the value from the initial request
|
||||
* @return {Promise.<Object|void>}
|
||||
*/
|
||||
const getLDAPUsers = () =>
|
||||
new Promise(resolve => {
|
||||
const cachedResults = cache.partyEntities;
|
||||
|
||||
// Resolve with cachedResults if this has been previously requested
|
||||
if (!isEmpty(cachedResults)) {
|
||||
return resolve(cachedResults);
|
||||
}
|
||||
|
||||
// Cast $.getJSON to native Promise
|
||||
Promise.resolve(getJSON(partyEntitiesUrl))
|
||||
.then(({ status, userEntities = [] }) => {
|
||||
if (status === 'ok' && userEntities.length) {
|
||||
/**
|
||||
* @type {Object} userEntitiesMaps hash of userEntities: label -> displayName
|
||||
*/
|
||||
const userEntitiesMaps = userEntities.reduce(
|
||||
(map, { label, displayName }) => ((map[label] = displayName), map),
|
||||
{}
|
||||
);
|
||||
|
||||
return {
|
||||
userEntities,
|
||||
userEntitiesMaps,
|
||||
userEntitiesSource: Object.keys(userEntitiesMaps)
|
||||
};
|
||||
}
|
||||
})
|
||||
.then(results => (cache.partyEntities = results))
|
||||
.then(resolve);
|
||||
});
|
||||
|
||||
/**
|
||||
* Takes a userNameQuery query and find userNames that match by starting with
|
||||
* the pattern
|
||||
* @param {String} userNameQuery pattern to search for
|
||||
* @param {Function} syncResults callback
|
||||
* @param {Function} asyncResults callback
|
||||
*/
|
||||
const ldapResolver = (userNameQuery, syncResults, asyncResults) => {
|
||||
const regex = new RegExp(`^${userNameQuery}.*`, 'i');
|
||||
|
||||
getLDAPUsers()
|
||||
.then(({ userEntitiesSource = {} }) => userEntitiesSource.filter(entity => regex.test(entity)))
|
||||
.then(asyncResults);
|
||||
};
|
||||
|
||||
/**
|
||||
* For a given userName, find the userEntity object that contains the userName
|
||||
* @param {String} userName the unique userName
|
||||
* @return {Promise.<TResult|null>} resolves with the userEntity or null otherwise
|
||||
*/
|
||||
const getPartyEntityWithUserName = userName =>
|
||||
getLDAPUsers().then(({ userEntities }) => userEntities.find(({ label }) => label === userName) || null);
|
||||
|
||||
export default Service.extend({
|
||||
getPartyEntityWithUserName,
|
||||
userNamesResolver: ldapResolver,
|
||||
fetchUserNames: getLDAPUsers
|
||||
});
|
35
wherehows-web/app/services/user-lookup.ts
Normal file
35
wherehows-web/app/services/user-lookup.ts
Normal file
@ -0,0 +1,35 @@
|
||||
import Ember from 'ember';
|
||||
import { getUserEntities } from 'wherehows-web/utils/api/datasets/owners';
|
||||
import { IPartyEntity, IPartyProps } from 'wherehows-web/typings/api/datasets/party-entities';
|
||||
|
||||
const { Service } = Ember;
|
||||
|
||||
/**
|
||||
* Takes a userNameQuery query and find userNames that match by starting with
|
||||
* the pattern
|
||||
* @param {string} userNameQuery pattern to search for
|
||||
* @param {Function} _syncResults callback
|
||||
* @param {Function} asyncResults callback
|
||||
* @return {Promise<void>}
|
||||
*/
|
||||
const ldapResolver = async (userNameQuery: string, _syncResults: Function, asyncResults: Function): Promise<void> => {
|
||||
const ldapRegex = new RegExp(`^${userNameQuery}.*`, 'i');
|
||||
const { userEntitiesSource = [] }: IPartyProps = await getUserEntities();
|
||||
asyncResults(userEntitiesSource.filter((entity: string) => ldapRegex.test(entity)));
|
||||
};
|
||||
|
||||
/**
|
||||
* For a given userName, find the userEntity object that contains the userName
|
||||
* @param {string} userName the unique userName
|
||||
* @return {Promise<IPartyEntity>} resolves with the userEntity or null otherwise
|
||||
*/
|
||||
const getPartyEntityWithUserName = (userName: string): Promise<IPartyEntity | null> =>
|
||||
getUserEntities().then(
|
||||
({ userEntities }: IPartyProps) => userEntities.find(({ label }: { label: string }) => label === userName) || null
|
||||
);
|
||||
|
||||
export default Service.extend({
|
||||
getPartyEntityWithUserName,
|
||||
userNamesResolver: ldapResolver,
|
||||
fetchUserNames: getUserEntities
|
||||
});
|
@ -16,3 +16,19 @@ export interface IPartyEntityResponse {
|
||||
status: ApiStatus;
|
||||
userEntities?: Array<IPartyEntity>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Describes a userEntityMap interface
|
||||
*/
|
||||
export interface userEntityMap {
|
||||
[label: string]: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Describes the props resolved by the getUserEntities function
|
||||
*/
|
||||
export interface IPartyProps {
|
||||
userEntities: Array<IPartyEntity>;
|
||||
userEntitiesMaps: userEntityMap;
|
||||
userEntitiesSource: Array<keyof userEntityMap>;
|
||||
}
|
||||
|
@ -1,7 +1,12 @@
|
||||
import Ember from 'ember';
|
||||
import { ApiRoot, ApiStatus } from 'wherehows-web/utils/api/shared';
|
||||
import { datasetUrlById } from 'wherehows-web/utils/api/datasets/shared';
|
||||
import { IPartyEntity, IPartyEntityResponse } from 'wherehows-web/typings/api/datasets/party-entities';
|
||||
import {
|
||||
IPartyEntity,
|
||||
IPartyEntityResponse,
|
||||
IPartyProps,
|
||||
userEntityMap
|
||||
} from 'wherehows-web/typings/api/datasets/party-entities';
|
||||
import { IOwner, IOwnerResponse } from 'wherehows-web/typings/api/datasets/owners';
|
||||
|
||||
/**
|
||||
@ -48,12 +53,64 @@ export const getPartyEntities = async (): Promise<Array<IPartyEntity>> => {
|
||||
return status === ApiStatus.OK ? userEntities : Promise.reject(status);
|
||||
};
|
||||
|
||||
/**
|
||||
* 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>}
|
||||
*/
|
||||
export const getUserEntities: () => Promise<IPartyProps> = (() => {
|
||||
/**
|
||||
* Memoized reference to the resolved value of a previous invocation to curried function in getUserEntities
|
||||
* @type {{result: IPartyProps | null}}
|
||||
*/
|
||||
const cache: { result: IPartyProps | null; userEntitiesSource: Array<keyof userEntityMap> } = {
|
||||
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) {
|
||||
inflightRequest = getPartyEntities();
|
||||
}
|
||||
|
||||
const userEntities: Array<IPartyEntity> = await inflightRequest;
|
||||
|
||||
return (cache.result = {
|
||||
userEntities,
|
||||
userEntitiesMaps: getPartyEntitiesMap(userEntities),
|
||||
// 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));
|
||||
}
|
||||
});
|
||||
};
|
||||
})();
|
||||
|
||||
/**
|
||||
* Transforms a list of party entities into a map of entity label to displayName value
|
||||
* @param {Array<IPartyEntity>} partyEntities
|
||||
* @return {Object<string>}
|
||||
*/
|
||||
export const getPartyEntitiesMap = (partyEntities: Array<IPartyEntity>): { [label: string]: string } =>
|
||||
export const getPartyEntitiesMap = (partyEntities: Array<IPartyEntity>): userEntityMap =>
|
||||
partyEntities.reduce(
|
||||
(map: { [label: string]: string }, { label, displayName }: IPartyEntity) => ((map[label] = displayName), map),
|
||||
{}
|
||||
|
Loading…
x
Reference in New Issue
Block a user