diff --git a/wherehows-web/app/components/dataset-authors.ts b/wherehows-web/app/components/dataset-authors.ts
index db9549f76e..dd3997a4b0 100644
--- a/wherehows-web/app/components/dataset-authors.ts
+++ b/wherehows-web/app/components/dataset-authors.ts
@@ -7,8 +7,18 @@ import { assert } from '@ember/debug';
import UserLookup from 'wherehows-web/services/user-lookup';
import CurrentUser from 'wherehows-web/services/current-user';
import { IOwner } from 'wherehows-web/typings/api/datasets/owners';
-import { ownerAlreadyExists, confirmOwner, updateOwner } from 'wherehows-web/constants/datasets/owner';
-import { isRequiredMinOwnersNotConfirmed, OwnerSource, OwnerType } from 'wherehows-web/utils/api/datasets/owners';
+import {
+ ownerAlreadyExists,
+ confirmOwner,
+ updateOwner,
+ minRequiredConfirmedOwners
+} from 'wherehows-web/constants/datasets/owner';
+import {
+ isRequiredMinOwnersNotConfirmed,
+ OwnerSource,
+ OwnerType,
+ validConfirmedOwners
+} from 'wherehows-web/utils/api/datasets/owners';
import Notifications, { NotificationEvent } from 'wherehows-web/services/notifications';
/**
@@ -79,6 +89,15 @@ export default class DatasetAuthors extends Component {
return isRequiredMinOwnersNotConfirmed(get(this, 'confirmedOwners'));
});
+ /**
+ * Counts the number of valid confirmed owners needed to make changes to the dataset
+ * @type {ComputedProperty}
+ * @memberof DatasetAuthors
+ */
+ ownersRequiredCount: ComputedProperty = computed('confirmedOwners.[]', function(this: DatasetAuthors) {
+ return minRequiredConfirmedOwners - validConfirmedOwners(get(this, 'confirmedOwners')).length;
+ });
+
/**
* Lists the owners that have be confirmed view the client ui
* @type {ComputedProperty>}
diff --git a/wherehows-web/app/constants/datasets/owner.ts b/wherehows-web/app/constants/datasets/owner.ts
index a97b746af1..114e8bf2fd 100644
--- a/wherehows-web/app/constants/datasets/owner.ts
+++ b/wherehows-web/app/constants/datasets/owner.ts
@@ -86,9 +86,9 @@ function updateOwner(
* Sets the `confirmedBy` attribute to the currently logged in user
* @param {IOwner} owner the owner to be updated
* @param {string} confirmedBy the userName of the confirming user
- * @returns {(Array | void)}
+ * @returns {IOwner.confirmedBy}
*/
-const confirmOwner = (owner: IOwner, confirmedBy: string): null | string =>
+const confirmOwner = (owner: IOwner, confirmedBy: string): IOwner['confirmedBy'] =>
set(owner, 'confirmedBy', confirmedBy || null);
/**
* Defines the default properties for a newly created IOwner instance
diff --git a/wherehows-web/app/styles/components/dataset-author/_dataset-author.scss b/wherehows-web/app/styles/components/dataset-author/_dataset-author.scss
index 7e4ecabb19..23aa697ac2 100644
--- a/wherehows-web/app/styles/components/dataset-author/_dataset-author.scss
+++ b/wherehows-web/app/styles/components/dataset-author/_dataset-author.scss
@@ -8,6 +8,10 @@ $owner-list-issue-color: set-color(red, maroonflush);
font-size: 20px;
font-weight: fw(normal, 4);
}
+
+ &__required-count {
+ height: item-spacing(4);
+ }
}
.dataset-author-record {
diff --git a/wherehows-web/app/templates/components/dataset-authors.hbs b/wherehows-web/app/templates/components/dataset-authors.hbs
index 71a9030041..8cc725ad5c 100644
--- a/wherehows-web/app/templates/components/dataset-authors.hbs
+++ b/wherehows-web/app/templates/components/dataset-authors.hbs
@@ -10,14 +10,14 @@
-{{#if requiredMinNotConfirmed}}
+
+ {{#if requiredMinNotConfirmed}}
-
- Add at least {{sub 2 confirmedOwners.length}} confirmed owner(s) with ID Type - USER
+ Add at least {{ownersRequiredCount}} owner(s) with ID Type - USER
and Owner Type - Owner
-
-{{/if}}
+ {{/if}}
+
@@ -92,7 +92,7 @@
- These are dataset ownership records, suggested based information derived from the source metadata.
+ These are dataset ownership records, suggested based on information derived from the source metadata.
diff --git a/wherehows-web/app/typings/ember-concurrency.d.ts b/wherehows-web/app/typings/ember-concurrency.d.ts
new file mode 100644
index 0000000000..de0d70dfd5
--- /dev/null
+++ b/wherehows-web/app/typings/ember-concurrency.d.ts
@@ -0,0 +1,91 @@
+declare module 'ember-concurrency' {
+ export function timeout(delay: number): Promise;
+ import ComputedProperty from '@ember/object/computed';
+ import RSVP from 'rsvp';
+
+ export enum TaskInstanceState {
+ Dropped = 'dropped',
+ Canceled = 'canceled',
+ Finished = 'finished',
+ Running = 'running',
+ Waiting = 'waiting'
+ }
+
+ export interface TaskProperty extends ComputedProperty {
+ cancelOn(eventNames: string[]): this;
+ debug(): this;
+ drop(): this;
+ enqueue(): this;
+ group(groupPath: string): this;
+ keepLatest(): this;
+ maxConcurrency(n: number): this;
+ on(eventNames: string[]): this;
+ restartable(): this;
+ }
+
+ export interface TaskInstance extends PromiseLike {
+ readonly error?: any;
+ readonly hasStarted: ComputedProperty;
+ readonly isCanceled: ComputedProperty;
+ readonly isDropped: ComputedProperty;
+ readonly isError: ComputedProperty;
+ readonly isFinished: ComputedProperty;
+ readonly isRunning: ComputedProperty;
+ readonly isSuccessful: ComputedProperty;
+ readonly state: ComputedProperty;
+ readonly value?: T;
+ cancel(): void;
+ catch: () => RSVP.Promise;
+ finally: () => RSVP.Promise;
+ then(
+ onfulfilled?: ((value: T) => TResult1 | RSVP.Promise) | undefined | null,
+ onrejected?: ((reason: any) => TResult2 | PromiseLike) | undefined | null
+ ): RSVP.Promise;
+ }
+
+ export enum TaskState {
+ Running = 'running',
+ Queued = 'queued',
+ Idle = 'idle'
+ }
+
+ type Task = TaskProperty &
+ ComputedProperty<{ perform: P }> & {
+ readonly isIdle: boolean;
+ readonly isQueued: boolean;
+ readonly isRunning: boolean;
+ readonly last?: TaskInstance;
+ readonly lastCanceled?: TaskInstance;
+ readonly lastComplete?: TaskInstance;
+ readonly lastErrored?: TaskInstance;
+ readonly lastIncomplete?: TaskInstance;
+ readonly lastPerformed?: TaskInstance;
+ readonly lastRunning?: TaskInstance;
+ readonly lastSuccessful?: TaskInstance;
+ readonly performCount: number;
+ readonly state: TaskState;
+ cancelAll(): void;
+ };
+
+ export function task(generatorFn: (a: A) => Iterator): Task TaskInstance>;
+
+ export function task(
+ generatorFn: (a1: A1, a2: A2) => Iterator
+ ): Task TaskInstance>;
+
+ export function task(
+ generatorFn: (a1: A1, a2: A2, a3: A3) => Iterator
+ ): Task TaskInstance>;
+
+ export function task(
+ generatorFn: (a1: A1, a2: A2, a3: A3, a4: A4) => Iterator
+ ): Task TaskInstance>;
+
+ export function task(
+ generatorFn: (a1: A1, a2: A2, a3: A3, a4: A4, a5: A5) => Iterator
+ ): Task TaskInstance>;
+
+ export function task(
+ generatorFn: (a1: A1, a2: A2, a3: A3, a4: A4, a6: A6) => Iterator
+ ): Task TaskInstance>;
+}
diff --git a/wherehows-web/app/typings/untyped-js-module.d.ts b/wherehows-web/app/typings/untyped-js-module.d.ts
index d7b9ef5257..8ef543215b 100644
--- a/wherehows-web/app/typings/untyped-js-module.d.ts
+++ b/wherehows-web/app/typings/untyped-js-module.d.ts
@@ -23,24 +23,6 @@ declare module 'wherehows-web/utils/datasets/compliance-policy';
declare module 'ember-cli-mirage';
-declare module 'ember-concurrency' {
- class TaskInstance {}
- class TaskProperty {
- perform(...args: Array): TaskInstance;
- on(): this;
- cancelOn(eventNames: string): this;
- debug(): this;
- drop(): this;
- restartable(): this;
- enqueue(): this;
- keepLatest(): this;
- performs(): this;
- maxConcurrency(n: number): this;
- }
- export function task(...args: Array): TaskProperty;
- export function timeout(delay: number): Promise;
-}
-
// https://github.com/ember-cli/ember-fetch/issues/72
// TS assumes the mapping btw ES modules and CJS modules is 1:1
// However, `ember-fetch` is the module name, but it's imported with `fetch`
diff --git a/wherehows-web/app/utils/api/datasets/owners.ts b/wherehows-web/app/utils/api/datasets/owners.ts
index 9f43f4a7ec..1a053086c9 100644
--- a/wherehows-web/app/utils/api/datasets/owners.ts
+++ b/wherehows-web/app/utils/api/datasets/owners.ts
@@ -12,7 +12,7 @@ import { getJSON, postJSON } from 'wherehows-web/utils/api/fetcher';
/**
* Defines a string enum for valid owner types
*/
-export enum OwnerIdType {
+enum OwnerIdType {
User = 'USER',
Group = 'GROUP'
}
@@ -21,7 +21,7 @@ export enum OwnerIdType {
* Defines the string enum for the OwnerType attribute
* @type {string}
*/
-export enum OwnerType {
+enum OwnerType {
Owner = 'Owner',
Consumer = 'Consumer',
Delegate = 'Delegate',
@@ -32,12 +32,12 @@ export enum OwnerType {
/**
* Accepted string values for the namespace of a user
*/
-export enum OwnerUrnNamespace {
+enum OwnerUrnNamespace {
corpUser = 'urn:li:corpuser',
groupUser = 'urn:li:corpGroup'
}
-export enum OwnerSource {
+enum OwnerSource {
Scm = 'SCM',
Nuage = 'NUAGE',
Sos = 'SOS',
@@ -71,7 +71,7 @@ const partyEntitiesUrl = `${ApiRoot}/party/entities`;
* @param {number} id the dataset Id
* @return {Promise>} the current list of dataset owners
*/
-export const readDatasetOwners = async (id: number): Promise> => {
+const readDatasetOwners = async (id: number): Promise> => {
const { owners = [], status, msg } = await getJSON({ url: datasetOwnersUrlById(id) });
if (status === ApiStatus.OK) {
return owners.map(owner => ({
@@ -90,7 +90,7 @@ export const readDatasetOwners = async (id: number): Promise> => {
* @param {Array} updatedOwners the updated list of owners for this dataset
* @return {Promise}
*/
-export const updateDatasetOwners = async (
+const updateDatasetOwners = async (
id: number,
csrfToken: string,
updatedOwners: Array
@@ -115,7 +115,7 @@ export const updateDatasetOwners = async (
* Requests party entities and if the response status is OK, resolves with an array of entities
* @return {Promise>}
*/
-export const readPartyEntities = async (): Promise> => {
+const readPartyEntities = async (): Promise> => {
const { status, userEntities = [], msg } = await getJSON({ url: partyEntitiesUrl });
return status === ApiStatus.OK ? userEntities : Promise.reject(msg);
};
@@ -127,7 +127,7 @@ export const readPartyEntities = async (): Promise> => {
* userEntitiesSource property is also lazy evaluated and cached for app lifetime.
* @type {() => Promise}
*/
-export const getUserEntities: () => Promise = (() => {
+const getUserEntities: () => Promise = (() => {
/**
* Memoized reference to the resolved value of a previous invocation to curried function in getUserEntities
* @type {{result: IPartyProps | null}}
@@ -177,18 +177,40 @@ export const getUserEntities: () => Promise = (() => {
* @param {Array} partyEntities
* @return {IUserEntityMap}
*/
-export const readPartyEntitiesMap = (partyEntities: Array): IUserEntityMap =>
+const readPartyEntitiesMap = (partyEntities: Array): IUserEntityMap =>
partyEntities.reduce(
(map: { [label: string]: string }, { label, displayName }: IPartyEntity) => ((map[label] = displayName), map),
{}
);
+/**
+ * Filters out a list of valid confirmed owners in a list of owners
+ * @param {Array} [owners=[]] the owners to filter
+ * @returns {Array}
+ */
+const validConfirmedOwners = (owners: Array = []): Array =>
+ owners.filter(
+ ({ confirmedBy, type, idType }) => confirmedBy && type === OwnerType.Owner && idType === OwnerIdType.User
+ );
+
/**
* Checks that the required minimum number of confirmed users is met with the type Owner and idType User
* @param {Array} owners the list of owners to check
* @return {boolean}
*/
-export const isRequiredMinOwnersNotConfirmed = (owners: Array = []): boolean =>
- owners.filter(
- ({ confirmedBy, type, idType }) => confirmedBy && type === OwnerType.Owner && idType === OwnerIdType.User
- ).length < minRequiredConfirmed;
+const isRequiredMinOwnersNotConfirmed = (owners: Array = []): boolean =>
+ validConfirmedOwners(owners).length < minRequiredConfirmed;
+
+export {
+ validConfirmedOwners,
+ isRequiredMinOwnersNotConfirmed,
+ readDatasetOwners,
+ readPartyEntities,
+ readPartyEntitiesMap,
+ getUserEntities,
+ updateDatasetOwners,
+ OwnerIdType,
+ OwnerType,
+ OwnerUrnNamespace,
+ OwnerSource
+};