mirror of
https://github.com/datahub-project/datahub.git
synced 2025-11-08 15:30:55 +00:00
adds browse feature for flows and metrics
This commit is contained in:
parent
5dede95d9d
commit
a9c622c832
@ -32,11 +32,11 @@ const asyncRequestBrowseData = (page, entity, urls) =>
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
const thunks = await Promise.all([
|
const thunks = await Promise.all([
|
||||||
dispatch(lazyRequestPagedFlows({ baseURL: urls.flows, page })),
|
dispatch(lazyRequestPagedFlows({ baseURL: urls.flows, query: { page } })),
|
||||||
dispatch(lazyRequestPagedMetrics({ baseURL: urls.metrics, page })),
|
dispatch(lazyRequestPagedMetrics({ baseURL: urls.metrics, query: { page } })),
|
||||||
dispatch(lazyRequestPagedDatasets({ baseURL: urls.datasets, page }))
|
dispatch(lazyRequestPagedDatasets({ baseURL: urls.datasets, query: { page } }))
|
||||||
]);
|
]);
|
||||||
const [...actions] = await Promise.all(thunks);
|
const [...actions] = thunks;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check that none of the actions has an error flag in FSA
|
* Check that none of the actions has an error flag in FSA
|
||||||
|
|||||||
@ -3,6 +3,8 @@ import { createAction } from 'redux-actions';
|
|||||||
|
|
||||||
import actionSet from 'wherehows-web/actions/action-set';
|
import actionSet from 'wherehows-web/actions/action-set';
|
||||||
import { lazyRequestUrnPagedDatasets, lazyRequestDatasetNodes } from 'wherehows-web/actions/datasets';
|
import { lazyRequestUrnPagedDatasets, lazyRequestDatasetNodes } from 'wherehows-web/actions/datasets';
|
||||||
|
import { lazyRequestNamedPagedMetrics, lazyRequestMetricNodes } from 'wherehows-web/actions/metrics';
|
||||||
|
import { lazyRequestFlowsNodes, lazyRequestPagedUrnApplicationFlows } from 'wherehows-web/actions/flows';
|
||||||
|
|
||||||
const { debug } = Ember;
|
const { debug } = Ember;
|
||||||
|
|
||||||
@ -23,29 +25,41 @@ const receiveNodeList = createAction(ActionTypes.RECEIVE_NODE_LIST);
|
|||||||
* @param {String} listURL
|
* @param {String} listURL
|
||||||
* @param {Array} queryParams current list of query parameters for the Ember route
|
* @param {Array} queryParams current list of query parameters for the Ember route
|
||||||
*/
|
*/
|
||||||
const asyncRequestNodeList = (params, listURL, { queryParams }) =>
|
const asyncRequestEntityQueryData = (params, listURL, { queryParamsKeys: queryParams }) =>
|
||||||
/**
|
/**
|
||||||
* Async thunk
|
* Async thunk
|
||||||
* @param {Function} dispatch
|
* @param {Function} dispatch
|
||||||
* @return {Promise.<*>}
|
* @return {Promise.<*>}
|
||||||
*/
|
*/
|
||||||
async function(dispatch) {
|
async function(dispatch) {
|
||||||
const { entity, page, urn } = params;
|
const { entity, page, urn, name } = params;
|
||||||
const query = { page, urn };
|
// Extract relevant query parameters into query object
|
||||||
|
const query = { page, urn, name };
|
||||||
|
|
||||||
dispatch(requestNodeList({ entity, listURL, query, queryParams }));
|
dispatch(requestNodeList({ entity, listURL, query, queryParams }));
|
||||||
|
|
||||||
|
// For each entity fetch the list of nodes and the actual entities for the given query
|
||||||
try {
|
try {
|
||||||
let nodesResult = {}, pagedEntities = {};
|
let nodesResult = {}, pagedEntities = {};
|
||||||
switch (entity) {
|
switch (entity) {
|
||||||
case 'datasets':
|
case 'datasets':
|
||||||
[nodesResult, pagedEntities] = await [
|
[nodesResult, pagedEntities] = await Promise.all([
|
||||||
dispatch(lazyRequestDatasetNodes({ listURL, query })),
|
dispatch(lazyRequestDatasetNodes({ listURL, query })),
|
||||||
dispatch(lazyRequestUrnPagedDatasets({ query }))
|
dispatch(lazyRequestUrnPagedDatasets({ query }))
|
||||||
];
|
]);
|
||||||
break;
|
break;
|
||||||
case 'metrics':
|
case 'metrics':
|
||||||
|
[nodesResult, pagedEntities] = await Promise.all([
|
||||||
|
dispatch(lazyRequestMetricNodes({ listURL, query })),
|
||||||
|
dispatch(lazyRequestNamedPagedMetrics({ query }))
|
||||||
|
]);
|
||||||
|
break;
|
||||||
case 'flows':
|
case 'flows':
|
||||||
|
[nodesResult, pagedEntities] = await Promise.all([
|
||||||
|
dispatch(lazyRequestFlowsNodes({ listURL, query })),
|
||||||
|
dispatch(lazyRequestPagedUrnApplicationFlows({ query }))
|
||||||
|
]);
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -66,4 +80,4 @@ const asyncRequestNodeList = (params, listURL, { queryParams }) =>
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export { ActionTypes, asyncRequestNodeList };
|
export { ActionTypes, asyncRequestEntityQueryData };
|
||||||
|
|||||||
@ -40,10 +40,10 @@ const createAsyncThunk = (
|
|||||||
* @return {Promise.<*>}
|
* @return {Promise.<*>}
|
||||||
*/
|
*/
|
||||||
) => async (dispatch, getState) => {
|
) => async (dispatch, getState) => {
|
||||||
const { status = 'error', data } = await asyncExecutor(getState);
|
const response = await asyncExecutor(getState);
|
||||||
|
|
||||||
if (status === 'ok') {
|
if (response.status === 'ok') {
|
||||||
return dispatch(receiverActionCreator({ data }));
|
return dispatch(receiverActionCreator(response));
|
||||||
}
|
}
|
||||||
|
|
||||||
return dispatch(receiverActionCreator(new Error(`Request failed with status ${status}`)));
|
return dispatch(receiverActionCreator(new Error(`Request failed with status ${status}`)));
|
||||||
|
|||||||
@ -46,6 +46,79 @@ const fetchPagedUrnEntities = entity => getState => {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Takes a entity and returns a function to fetch entities by an entity url with a `name` segment
|
||||||
|
* @param {String} entity
|
||||||
|
*/
|
||||||
|
const fetchNamedPagedEntities = entity => getState => {
|
||||||
|
const { [entity]: { baseURL }, browseEntity: { [entity]: { query } } = {} } = getState();
|
||||||
|
const queryCopy = Object.assign({}, query);
|
||||||
|
const name = queryCopy.name;
|
||||||
|
let baseNameUrl = baseURL;
|
||||||
|
|
||||||
|
if (name) {
|
||||||
|
baseNameUrl = `${baseNameUrl}/name/${name}`;
|
||||||
|
delete queryCopy.name;
|
||||||
|
}
|
||||||
|
|
||||||
|
const namedPageURL = Object.keys(queryCopy).reduce((url, queryKey) => {
|
||||||
|
let queryValue = queryCopy[queryKey];
|
||||||
|
if (queryValue) {
|
||||||
|
return buildUrl(url, queryKey, queryValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
return url;
|
||||||
|
}, baseNameUrl);
|
||||||
|
|
||||||
|
return fetch(namedPageURL).then(response => response.json()).then((payload = {}) => {
|
||||||
|
if (payload.status === 'ok') {
|
||||||
|
payload.data = Object.assign({}, payload.data, {
|
||||||
|
parentName: name || null
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return payload;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Takes a entity name and returns a function the fetches an entity by urn path segment
|
||||||
|
* @param {String} entity
|
||||||
|
*/
|
||||||
|
const fetchUrnPathEntities = entity => getState => {
|
||||||
|
const { [entity]: { baseURL }, browseEntity: { [entity]: { query } = {} } = {} } = getState();
|
||||||
|
const queryCopy = Object.assign({}, query);
|
||||||
|
const urn = queryCopy.urn;
|
||||||
|
let baseUrnUrl = baseURL;
|
||||||
|
|
||||||
|
// If the urn exists, append its value to the base url for the entity and remove the attribute from the local
|
||||||
|
// copy of the queried parameters
|
||||||
|
if (urn) {
|
||||||
|
baseUrnUrl = `${baseUrnUrl}/${urn}`;
|
||||||
|
delete queryCopy.urn;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Append the left over query params to the constructed url string
|
||||||
|
const urnPathUrl = Object.keys(query).reduce((url, queryKey) => {
|
||||||
|
let queryValue = query[queryKey];
|
||||||
|
if (queryValue) {
|
||||||
|
return buildUrl(url, queryKey, queryValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
return url;
|
||||||
|
}, baseUrnUrl);
|
||||||
|
|
||||||
|
return fetch(urnPathUrl).then(response => response.json()).then((payload = {}) => {
|
||||||
|
if (payload.status === 'ok') {
|
||||||
|
payload.data = Object.assign({}, payload.data, {
|
||||||
|
parentUrn: urn || null
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return payload;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Request urn child nodes/ datasets for the specified entity
|
* Request urn child nodes/ datasets for the specified entity
|
||||||
* @param entity
|
* @param entity
|
||||||
@ -75,7 +148,56 @@ const fetchNodes = entity => getState => {
|
|||||||
}, `${listURL}/${entity}`);
|
}, `${listURL}/${entity}`);
|
||||||
|
|
||||||
// TODO: DSS-7019 remove any parsing from response objects. in createLazyRequest and update all call sites
|
// TODO: DSS-7019 remove any parsing from response objects. in createLazyRequest and update all call sites
|
||||||
return fetch(nodeURL).then(response => response.json()).then(({ status, nodes: data }) => ({ status, data }));
|
return fetch(nodeURL).then(response => response.json()).then((payload = {}) => {
|
||||||
|
return Object.assign({}, payload, {
|
||||||
|
parentUrn: query.urn || null
|
||||||
|
});
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
export { fetchPagedEntities, fetchPagedUrnEntities, fetchNodes };
|
/**
|
||||||
|
* For a given entity name, fetches the nodes at the list url by appending the entity name and name path as segments
|
||||||
|
* of the request url
|
||||||
|
* @param {String} entity
|
||||||
|
*/
|
||||||
|
const fetchNamedEntityNodes = entity => getState => {
|
||||||
|
// TODO: DSS-7019 rename queryParams to queryList, don't over load the name `queryParams`
|
||||||
|
const { browseEntity: { [entity]: { listURL = '', query: { name } } = {} } = {} } = getState();
|
||||||
|
const namePath = name ? `/${name}` : '';
|
||||||
|
const nodeURL = `${listURL}/${entity}${namePath}`;
|
||||||
|
|
||||||
|
return fetch(nodeURL).then(response => response.json()).then((payload = {}) => {
|
||||||
|
return Object.assign({}, payload, {
|
||||||
|
//TODO: Should this be namedEntityNodes vs urnPathEntityNodes
|
||||||
|
parentName: name || null
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* For a given entity, fetches the entities nodes at the list url by appending the entity name and the urn path
|
||||||
|
* as segments of the request url
|
||||||
|
* @param {String} entity
|
||||||
|
*/
|
||||||
|
const fetchUrnPathEntityNodes = entity => getState => {
|
||||||
|
const { browseEntity: { [entity]: { listURL = '', query: { urn } } = {} } = {} } = getState();
|
||||||
|
const urnPath = urn ? `/${urn}` : '';
|
||||||
|
const urnListUrl = `${listURL}/${entity}${urnPath}`;
|
||||||
|
|
||||||
|
return fetch(urnListUrl).then(response => response.json()).then((payload = {}) => {
|
||||||
|
return Object.assign({}, payload, {
|
||||||
|
//TODO: Should this be namedEntityNodes vs urnPathEntityNodes
|
||||||
|
parentUrn: urn || null
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export {
|
||||||
|
fetchPagedEntities,
|
||||||
|
fetchPagedUrnEntities,
|
||||||
|
fetchNodes,
|
||||||
|
fetchNamedPagedEntities,
|
||||||
|
fetchNamedEntityNodes,
|
||||||
|
fetchUrnPathEntities,
|
||||||
|
fetchUrnPathEntityNodes
|
||||||
|
};
|
||||||
|
|||||||
@ -1,2 +1,2 @@
|
|||||||
export * from 'wherehows-web/actions/entities/entities';
|
export * from 'wherehows-web/actions/entities/entities';
|
||||||
export { fetchPagedEntities, fetchPagedUrnEntities, fetchNodes } from 'wherehows-web/actions/entities/entity-request';
|
export * from 'wherehows-web/actions/entities/entity-request';
|
||||||
|
|||||||
@ -1,5 +1,10 @@
|
|||||||
import { createAction } from 'redux-actions';
|
import { createAction } from 'redux-actions';
|
||||||
import { createLazyRequest, fetchPagedEntities } from 'wherehows-web/actions/entities';
|
import {
|
||||||
|
createLazyRequest,
|
||||||
|
fetchPagedEntities,
|
||||||
|
fetchUrnPathEntityNodes,
|
||||||
|
fetchUrnPathEntities
|
||||||
|
} from 'wherehows-web/actions/entities';
|
||||||
import actionSet from 'wherehows-web/actions/action-set';
|
import actionSet from 'wherehows-web/actions/action-set';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -9,7 +14,13 @@ import actionSet from 'wherehows-web/actions/action-set';
|
|||||||
const ActionTypes = {
|
const ActionTypes = {
|
||||||
REQUEST_PAGED_FLOWS: actionSet('REQUEST_PAGED_FLOWS'),
|
REQUEST_PAGED_FLOWS: actionSet('REQUEST_PAGED_FLOWS'),
|
||||||
SELECT_PAGED_FLOWS: actionSet('SELECT_PAGED_FLOWS'),
|
SELECT_PAGED_FLOWS: actionSet('SELECT_PAGED_FLOWS'),
|
||||||
RECEIVE_PAGED_FLOWS: actionSet('RECEIVE_PAGED_FLOWS')
|
RECEIVE_PAGED_FLOWS: actionSet('RECEIVE_PAGED_FLOWS'),
|
||||||
|
|
||||||
|
REQUEST_PAGED_URN_FLOWS: actionSet('REQUEST_PAGED_URN_FLOWS'),
|
||||||
|
RECEIVE_PAGED_URN_FLOWS: actionSet('RECEIVE_PAGED_URN_FLOWS'),
|
||||||
|
|
||||||
|
REQUEST_FLOWS_NODES: actionSet('REQUEST_FLOWS_NODES'),
|
||||||
|
RECEIVE_FLOWS_NODES: actionSet('RECEIVE_FLOWS_NODES')
|
||||||
};
|
};
|
||||||
|
|
||||||
const requestPagedFlows = createAction(ActionTypes.REQUEST_PAGED_FLOWS);
|
const requestPagedFlows = createAction(ActionTypes.REQUEST_PAGED_FLOWS);
|
||||||
@ -21,7 +32,28 @@ const receivePagedFlows = createAction(
|
|||||||
() => ({ receivedAt: Date.now() })
|
() => ({ receivedAt: Date.now() })
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const requestPagedUrnFlows = createAction(ActionTypes.REQUEST_PAGED_URN_FLOWS);
|
||||||
|
|
||||||
|
const receivePagedUrnFlows = createAction(
|
||||||
|
ActionTypes.RECEIVE_PAGED_URN_FLOWS,
|
||||||
|
({ data }) => data,
|
||||||
|
() => ({ receivedAt: Date.now() })
|
||||||
|
);
|
||||||
|
|
||||||
|
const requestFlowsNodes = createAction(ActionTypes.REQUEST_FLOWS_NODES);
|
||||||
|
const receiveFlowsNodes = createAction(
|
||||||
|
ActionTypes.RECEIVE_FLOWS_NODES,
|
||||||
|
response => response,
|
||||||
|
// meta data attached to the ActionTypes.RECEIVE_PAGED_FLOWS action
|
||||||
|
() => ({ receivedAt: Date.now() })
|
||||||
|
);
|
||||||
|
|
||||||
// async action/thunk creator for ActionTypes.REQUEST_PAGED_FLOWS
|
// async action/thunk creator for ActionTypes.REQUEST_PAGED_FLOWS
|
||||||
|
// TODO: Is this redundant since we can use name without a name queryParam supplied
|
||||||
const lazyRequestPagedFlows = createLazyRequest(requestPagedFlows, receivePagedFlows, fetchPagedEntities('flows'));
|
const lazyRequestPagedFlows = createLazyRequest(requestPagedFlows, receivePagedFlows, fetchPagedEntities('flows'));
|
||||||
|
|
||||||
export { ActionTypes, lazyRequestPagedFlows };
|
const lazyRequestFlowsNodes = createLazyRequest(requestFlowsNodes, receiveFlowsNodes, fetchUrnPathEntityNodes('flows'));
|
||||||
|
|
||||||
|
const lazyRequestPagedUrnApplicationFlows = createLazyRequest(requestPagedUrnFlows, receivePagedUrnFlows, fetchUrnPathEntities('flows'));
|
||||||
|
|
||||||
|
export { ActionTypes, lazyRequestPagedFlows, lazyRequestFlowsNodes, lazyRequestPagedUrnApplicationFlows };
|
||||||
|
|||||||
@ -1,5 +1,10 @@
|
|||||||
import { createAction } from 'redux-actions';
|
import { createAction } from 'redux-actions';
|
||||||
import { createLazyRequest, fetchPagedEntities } from 'wherehows-web/actions/entities';
|
import {
|
||||||
|
createLazyRequest,
|
||||||
|
fetchPagedEntities,
|
||||||
|
fetchNamedEntityNodes,
|
||||||
|
fetchNamedPagedEntities
|
||||||
|
} from 'wherehows-web/actions/entities';
|
||||||
import actionSet from 'wherehows-web/actions/action-set';
|
import actionSet from 'wherehows-web/actions/action-set';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -9,7 +14,13 @@ import actionSet from 'wherehows-web/actions/action-set';
|
|||||||
const ActionTypes = {
|
const ActionTypes = {
|
||||||
REQUEST_PAGED_METRICS: actionSet('REQUEST_PAGED_METRICS'),
|
REQUEST_PAGED_METRICS: actionSet('REQUEST_PAGED_METRICS'),
|
||||||
SELECT_PAGED_METRICS: actionSet('SELECT_PAGED_METRICS'),
|
SELECT_PAGED_METRICS: actionSet('SELECT_PAGED_METRICS'),
|
||||||
RECEIVE_PAGED_METRICS: actionSet('RECEIVE_PAGED_METRICS')
|
RECEIVE_PAGED_METRICS: actionSet('RECEIVE_PAGED_METRICS'),
|
||||||
|
|
||||||
|
REQUEST_PAGED_NAMED_METRICS: actionSet('REQUEST_PAGED_NAMED_METRICS'),
|
||||||
|
RECEIVE_PAGED_NAMED_METRICS: actionSet('RECEIVE_PAGED_NAMED_METRICS'),
|
||||||
|
|
||||||
|
REQUEST_METRICS_NODES: actionSet('REQUEST_METRICS_NODES'),
|
||||||
|
RECEIVE_METRICS_NODES: actionSet('RECEIVE_METRICS_NODES')
|
||||||
};
|
};
|
||||||
|
|
||||||
const requestPagedMetrics = createAction(ActionTypes.REQUEST_PAGED_METRICS);
|
const requestPagedMetrics = createAction(ActionTypes.REQUEST_PAGED_METRICS);
|
||||||
@ -23,10 +34,47 @@ const receivePagedMetrics = createAction(
|
|||||||
() => ({ receivedAt: Date.now() })
|
() => ({ receivedAt: Date.now() })
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const requestPagedNamedMetrics = createAction(ActionTypes.REQUEST_PAGED_NAMED_METRICS);
|
||||||
|
|
||||||
|
const receivePagedNamedMetrics = createAction(
|
||||||
|
ActionTypes.RECEIVE_PAGED_NAMED_METRICS,
|
||||||
|
({ data }) => data,
|
||||||
|
() => ({ receivedAt: Date.now() })
|
||||||
|
);
|
||||||
|
|
||||||
|
const requestMetricNodes = createAction(ActionTypes.REQUEST_METRICS_NODES);
|
||||||
|
const receiveMetricNodes = createAction(
|
||||||
|
ActionTypes.RECEIVE_METRICS_NODES,
|
||||||
|
response => response,
|
||||||
|
// meta data attached to the ActionTypes.RECEIVE_PAGED_METRICS action
|
||||||
|
() => ({ receivedAt: Date.now() })
|
||||||
|
);
|
||||||
// async action/thunk creator for ActionTypes.REQUEST_PAGED_METRICS
|
// async action/thunk creator for ActionTypes.REQUEST_PAGED_METRICS
|
||||||
const lazyRequestPagedMetrics = createLazyRequest(requestPagedMetrics, receivePagedMetrics, fetchPagedEntities('metrics'));
|
const lazyRequestPagedMetrics = createLazyRequest(
|
||||||
|
requestPagedMetrics,
|
||||||
|
receivePagedMetrics,
|
||||||
|
fetchPagedEntities('metrics')
|
||||||
|
);
|
||||||
|
|
||||||
// async action/thunk creator for ActionTypes.SELECT_PAGED_METRICS
|
// async action/thunk creator for ActionTypes.SELECT_PAGED_METRICS
|
||||||
const lazySelectPagedMetrics = createLazyRequest(selectPagedMetrics, receivePagedMetrics, fetchPagedEntities('metrics'));
|
const lazySelectPagedMetrics = createLazyRequest(
|
||||||
|
selectPagedMetrics,
|
||||||
|
receivePagedMetrics,
|
||||||
|
fetchPagedEntities('metrics')
|
||||||
|
);
|
||||||
|
|
||||||
export { ActionTypes, lazyRequestPagedMetrics, lazySelectPagedMetrics };
|
const lazyRequestMetricNodes = createLazyRequest(requestMetricNodes, receiveMetricNodes, fetchNamedEntityNodes('metrics'));
|
||||||
|
|
||||||
|
const lazyRequestNamedPagedMetrics = createLazyRequest(
|
||||||
|
requestPagedNamedMetrics,
|
||||||
|
receivePagedNamedMetrics,
|
||||||
|
fetchNamedPagedEntities('metrics')
|
||||||
|
);
|
||||||
|
|
||||||
|
export {
|
||||||
|
ActionTypes,
|
||||||
|
lazyRequestPagedMetrics,
|
||||||
|
lazySelectPagedMetrics,
|
||||||
|
lazyRequestMetricNodes,
|
||||||
|
lazyRequestNamedPagedMetrics
|
||||||
|
};
|
||||||
|
|||||||
@ -44,15 +44,11 @@ export default Ember.Component.extend({
|
|||||||
var url = this.get('savePath');
|
var url = this.get('savePath');
|
||||||
url = url.replace(/\{.\w+\}/, this.get('itemId'))
|
url = url.replace(/\{.\w+\}/, this.get('itemId'))
|
||||||
var method = 'POST';
|
var method = 'POST';
|
||||||
var token = $("#csrfToken").val().replace('/', '');
|
var data = {};
|
||||||
var data = {"csrfToken": token};
|
|
||||||
data[this.get('saveParam')] = this.editor.getSession().getValue()
|
data[this.get('saveParam')] = this.editor.getSession().getValue()
|
||||||
$.ajax({
|
$.ajax({
|
||||||
url: url,
|
url: url,
|
||||||
method: method,
|
method: method,
|
||||||
headers: {
|
|
||||||
'Csrf-Token': token
|
|
||||||
},
|
|
||||||
dataType: 'json',
|
dataType: 'json',
|
||||||
data: data
|
data: data
|
||||||
}).done(function (data, txt, xhr) {
|
}).done(function (data, txt, xhr) {
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import Ember from 'ember';
|
import Ember from 'ember';
|
||||||
import connect from 'ember-redux/components/connect';
|
import connect from 'ember-redux/components/connect';
|
||||||
import { urnRegex } from 'wherehows-web/utils/validators/urn';
|
import { urnRegex, specialFlowUrnRegex } from 'wherehows-web/utils/validators/urn';
|
||||||
|
|
||||||
const { Component } = Ember;
|
const { Component } = Ember;
|
||||||
|
|
||||||
@ -10,13 +10,7 @@ const { Component } = Ember;
|
|||||||
* @type {RegExp}
|
* @type {RegExp}
|
||||||
*/
|
*/
|
||||||
const pageRegex = /\/page\/([0-9]+)/i;
|
const pageRegex = /\/page\/([0-9]+)/i;
|
||||||
/**
|
const nameRegex = /\/name\/([0-9a-z()_{}\[\]\/\s]+)/i;
|
||||||
* Matches a url string path segment that optionally starts with a hash followed by forward slash,
|
|
||||||
* either datasets or flows or metrics, forward slash, number of varying length and optional trailing slash
|
|
||||||
* The number is retained
|
|
||||||
* @type {RegExp}
|
|
||||||
*/
|
|
||||||
const entityRegex = /^#?\/(?:datasets|metrics|flows)\/([0-9]+)\/?/;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Takes a node url and parses out the query params and path spec to be included in the link component
|
* Takes a node url and parses out the query params and path spec to be included in the link component
|
||||||
@ -26,6 +20,8 @@ const entityRegex = /^#?\/(?:datasets|metrics|flows)\/([0-9]+)\/?/;
|
|||||||
const nodeUrlToQueryParams = nodeUrl => {
|
const nodeUrlToQueryParams = nodeUrl => {
|
||||||
const pageMatch = nodeUrl.match(pageRegex);
|
const pageMatch = nodeUrl.match(pageRegex);
|
||||||
const urnMatch = nodeUrl.match(urnRegex);
|
const urnMatch = nodeUrl.match(urnRegex);
|
||||||
|
const flowUrnMatch = nodeUrl.match(specialFlowUrnRegex);
|
||||||
|
const nameMatch = nodeUrl.match(nameRegex);
|
||||||
let queryParams = null;
|
let queryParams = null;
|
||||||
|
|
||||||
// If we have a page match, append the page number to eventual urn object
|
// If we have a page match, append the page number to eventual urn object
|
||||||
@ -37,17 +33,41 @@ const nodeUrlToQueryParams = nodeUrl => {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (Array.isArray(nameMatch)) {
|
||||||
|
let match = nameMatch[1];
|
||||||
|
match = match.split('/page')[0];
|
||||||
|
|
||||||
|
queryParams = Object.assign({}, queryParams, {
|
||||||
|
name: match
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// If we have a urn match, append the urn to eventual query params object
|
// If we have a urn match, append the urn to eventual query params object
|
||||||
if (Array.isArray(urnMatch)) {
|
if (Array.isArray(urnMatch) || Array.isArray(flowUrnMatch)) {
|
||||||
|
const urn = urnMatch || [flowUrnMatch[1]];
|
||||||
|
|
||||||
queryParams = Object.assign({}, queryParams, {
|
queryParams = Object.assign({}, queryParams, {
|
||||||
// Extract the entire match as urn value
|
// Extract the entire match as urn value
|
||||||
urn: urnMatch[0]
|
urn: urn[0]
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return queryParams;
|
return queryParams;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const getNodes = (entity, state = {}) => query =>
|
||||||
|
({
|
||||||
|
get datasets() {
|
||||||
|
return state[entity].nodesByUrn[query];
|
||||||
|
},
|
||||||
|
get metrics() {
|
||||||
|
return state[entity].nodesByName[query];
|
||||||
|
},
|
||||||
|
get flows() {
|
||||||
|
return this.datasets;
|
||||||
|
}
|
||||||
|
}[entity]);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Selector function that takes a Redux Store to extract
|
* Selector function that takes a Redux Store to extract
|
||||||
* state props for the browser-rail
|
* state props for the browser-rail
|
||||||
@ -58,36 +78,52 @@ const stateToComputed = state => {
|
|||||||
// Extracts the current entity active in the browse view
|
// Extracts the current entity active in the browse view
|
||||||
const { browseEntity: { currentEntity = '' } = {} } = state;
|
const { browseEntity: { currentEntity = '' } = {} } = state;
|
||||||
// Retrieves properties for the current entity from the state tree
|
// Retrieves properties for the current entity from the state tree
|
||||||
const { [currentEntity]: { nodesByUrn = {}, query: { urn } = {} } } = state;
|
const { browseEntity: { [currentEntity]: { query: { urn, name } } } } = state;
|
||||||
// Removes `s` from the end of each entity name. Ember routes for individual entities are singular, and also
|
// Removes `s` from the end of each entity name. Ember routes for individual entities are singular, and also
|
||||||
// returned entities contain id prop that is the singular type name, suffixed with Id, e.g. metricId
|
// returned entities contain id prop that is the singular type name, suffixed with Id, e.g. metricId
|
||||||
// datasets -> dataset, metrics -> metric, flows -> flow
|
// datasets -> dataset, metrics -> metric, flows -> flow
|
||||||
const singularName = currentEntity.slice(0, -1);
|
const singularName = currentEntity.slice(0, -1);
|
||||||
let nodes = nodesByUrn[urn] || [];
|
const query =
|
||||||
|
{
|
||||||
|
datasets: urn,
|
||||||
|
metrics: name,
|
||||||
|
flows: urn
|
||||||
|
}[currentEntity] || null;
|
||||||
|
let nodes = getNodes(currentEntity, state)(query) || [];
|
||||||
/**
|
/**
|
||||||
* Creates dynamic query link params for each node
|
* Creates dynamic query link params for each node
|
||||||
* @type {Array} list of child nodes or datasets to render
|
* @type {Array} list of child nodes or datasets to render
|
||||||
*/
|
*/
|
||||||
nodes = nodes.map(({ nodeName, nodeUrl, [`${singularName}Id`]: id }) => {
|
nodes = nodes.map(({ nodeName, nodeUrl, [`${singularName}Id`]: id, application = '' }) => {
|
||||||
nodeUrl = String(nodeUrl);
|
nodeUrl = String(nodeUrl);
|
||||||
|
const node = {
|
||||||
|
title: nodeName,
|
||||||
|
text: nodeName
|
||||||
|
};
|
||||||
|
|
||||||
// If the id prop (datasetId|metricId|flowId) is truthy, then it is a standalone entity
|
// If the id prop (datasetId|metricId|flowId) is truthy, then it is a standalone entity
|
||||||
if (id) {
|
if (id) {
|
||||||
return {
|
let idNode = Object.assign({}, node);
|
||||||
title: nodeName,
|
|
||||||
text: nodeName,
|
if (singularName === 'flow' && application) {
|
||||||
|
idNode = Object.assign({}, idNode, {
|
||||||
|
queryParams: {
|
||||||
|
name: application
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return Object.assign({}, idNode, {
|
||||||
route: `${currentEntity}.${singularName}`,
|
route: `${currentEntity}.${singularName}`,
|
||||||
model: nodeUrl.match(entityRegex)[1]
|
model: id
|
||||||
};
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return Object.assign({}, node, {
|
||||||
title: nodeName,
|
|
||||||
text: nodeName,
|
|
||||||
route: `browse.entity`,
|
route: `browse.entity`,
|
||||||
model: currentEntity,
|
model: currentEntity,
|
||||||
queryParams: nodeUrlToQueryParams(nodeUrl)
|
queryParams: nodeUrlToQueryParams(nodeUrl)
|
||||||
};
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
return { nodes };
|
return { nodes };
|
||||||
|
|||||||
@ -3,6 +3,25 @@ import connect from 'ember-redux/components/connect';
|
|||||||
|
|
||||||
const { Component } = Ember;
|
const { Component } = Ember;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extract the childIds for an entity under a specific category
|
||||||
|
* @param entity
|
||||||
|
* @param state
|
||||||
|
*/
|
||||||
|
const getChildIds = (entity, state = {}) => query =>
|
||||||
|
({
|
||||||
|
get datasets() {
|
||||||
|
return state[entity].byUrn[query];
|
||||||
|
},
|
||||||
|
get metrics() {
|
||||||
|
return state[entity].byName[query];
|
||||||
|
},
|
||||||
|
get flows() {
|
||||||
|
// Flows are retrieved by name as well
|
||||||
|
return this.datasets;
|
||||||
|
}
|
||||||
|
}[entity]);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Selector function that takes a Redux Store to extract
|
* Selector function that takes a Redux Store to extract
|
||||||
* state props for the browser-rail
|
* state props for the browser-rail
|
||||||
@ -12,12 +31,17 @@ const stateToComputed = state => {
|
|||||||
// Extracts the current entity active in the browse view
|
// Extracts the current entity active in the browse view
|
||||||
const { browseEntity: { currentEntity = '' } = {} } = state;
|
const { browseEntity: { currentEntity = '' } = {} } = state;
|
||||||
// Retrieves properties for the current entity from the state tree
|
// Retrieves properties for the current entity from the state tree
|
||||||
let { browseEntity: { [currentEntity]: { query: { urn } } } } = state;
|
const { browseEntity: { [currentEntity]: { query: { urn, name } } } } = state;
|
||||||
// Default urn to null, which represents the top-level parent
|
// Default urn to null, which represents the top-level parent
|
||||||
urn = urn || null;
|
|
||||||
|
|
||||||
|
const query =
|
||||||
|
{
|
||||||
|
datasets: urn,
|
||||||
|
metrics: name,
|
||||||
|
flows: urn
|
||||||
|
}[currentEntity] || null;
|
||||||
// Read the list of ids child entity ids associated with the urn
|
// Read the list of ids child entity ids associated with the urn
|
||||||
const { [currentEntity]: { byUrn: { [urn]: childIds = [] } } } = state;
|
const childIds = getChildIds(currentEntity, state)(query) || [];
|
||||||
// Read the list of entities, stored in the byId property
|
// Read the list of entities, stored in the byId property
|
||||||
const { [currentEntity]: { byId: entities } } = state;
|
const { [currentEntity]: { byId: entities } } = state;
|
||||||
/**
|
/**
|
||||||
|
|||||||
@ -41,13 +41,13 @@ const datasetClassifiersKeys = Object.keys(datasetClassifiers);
|
|||||||
// TODO: DSS-6671 Extract to constants module
|
// TODO: DSS-6671 Extract to constants module
|
||||||
const successUpdating = 'Your changes have been successfully saved!';
|
const successUpdating = 'Your changes have been successfully saved!';
|
||||||
const failedUpdating = 'Oops! We are having trouble updating this dataset at the moment.';
|
const failedUpdating = 'Oops! We are having trouble updating this dataset at the moment.';
|
||||||
const missingTypes = 'Looks like some fields are marked as `Confidential` or ' +
|
const missingTypes =
|
||||||
'`Highly Confidential` but do not have a specified `Field Format`?';
|
'Looks like some fields are marked as `Confidential` or `Highly Confidential` but do not have a specified `Field Format`?';
|
||||||
const hiddenTrackingFieldsMsg = htmlSafe(
|
const hiddenTrackingFieldsMsg = htmlSafe(
|
||||||
'<p>Hey! Just a heads up that some fields in this dataset have been hidden from the table(s) below. ' +
|
'<p>Hey! Just a heads up that some fields in this dataset have been hidden from the table(s) below. ' +
|
||||||
'These are tracking fields for which we\'ve been able to predetermine the compliance classification.</p>' +
|
"These are tracking fields for which we've been able to predetermine the compliance classification.</p>" +
|
||||||
'<p>For example: <code>header.memberId</code>, <code>requestHeader</code>. ' +
|
'<p>For example: <code>header.memberId</code>, <code>requestHeader</code>. ' +
|
||||||
'Hopefully, this saves you some scrolling!</p>'
|
'Hopefully, this saves you some scrolling!</p>'
|
||||||
);
|
);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -55,8 +55,7 @@ const hiddenTrackingFieldsMsg = htmlSafe(
|
|||||||
* for now, so no need to make into a helper
|
* for now, so no need to make into a helper
|
||||||
* @param {String} string
|
* @param {String} string
|
||||||
*/
|
*/
|
||||||
const formatAsCapitalizedStringWithSpaces = string =>
|
const formatAsCapitalizedStringWithSpaces = string => string.replace(/[A-Z]/g, match => ` ${match}`).capitalize();
|
||||||
string.replace(/[A-Z]/g, match => ` ${match}`).capitalize();
|
|
||||||
|
|
||||||
export default Component.extend({
|
export default Component.extend({
|
||||||
sortColumnWithName: 'identifierField',
|
sortColumnWithName: 'identifierField',
|
||||||
@ -73,10 +72,9 @@ export default Component.extend({
|
|||||||
|
|
||||||
// Map logicalTypes to options better consumed by drop down
|
// Map logicalTypes to options better consumed by drop down
|
||||||
logicalTypes: ['', ...logicalTypes.sort()].map(value => {
|
logicalTypes: ['', ...logicalTypes.sort()].map(value => {
|
||||||
const label = value ?
|
const label = value
|
||||||
value.replace(/_/g, ' ')
|
? value.replace(/_/g, ' ').replace(/([A-Z]{3,})/g, f => f.toLowerCase().capitalize())
|
||||||
.replace(/([A-Z]{3,})/g, f => f.toLowerCase().capitalize()) :
|
: 'Not Specified';
|
||||||
'Not Specified';
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
value,
|
value,
|
||||||
@ -122,25 +120,26 @@ export default Component.extend({
|
|||||||
*/
|
*/
|
||||||
fieldNameToClass: computed(
|
fieldNameToClass: computed(
|
||||||
`${sourceClassificationKey}.{confidential,limitedDistribution,highlyConfidential}.[]`,
|
`${sourceClassificationKey}.{confidential,limitedDistribution,highlyConfidential}.[]`,
|
||||||
function () {
|
function() {
|
||||||
const sourceClasses = getWithDefault(this, sourceClassificationKey, []);
|
const sourceClasses = getWithDefault(this, sourceClassificationKey, []);
|
||||||
// Creates a lookup table of fieldNames to classification
|
// Creates a lookup table of fieldNames to classification
|
||||||
// Also, the expectation is that the association from fieldName -> classification
|
// Also, the expectation is that the association from fieldName -> classification
|
||||||
// is one-to-one hence no check to ensure a fieldName gets clobbered
|
// is one-to-one hence no check to ensure a fieldName gets clobbered
|
||||||
// in the lookup assignment
|
// in the lookup assignment
|
||||||
return Object.keys(sourceClasses)
|
return Object.keys(sourceClasses).reduce(
|
||||||
.reduce((lookup, classificationKey) =>
|
(lookup, classificationKey) =>
|
||||||
// For the provided classificationKey, iterate over it's fieldNames,
|
// For the provided classificationKey, iterate over it's fieldNames,
|
||||||
// and assign the classificationKey to the fieldName in the table
|
// and assign the classificationKey to the fieldName in the table
|
||||||
(sourceClasses[classificationKey] || []).reduce((lookup, field) => {
|
(sourceClasses[classificationKey] || []).reduce((lookup, field) => {
|
||||||
const { identifierField } = field;
|
const { identifierField } = field;
|
||||||
// cKey -> 1...fieldNameList => fieldName -> cKey
|
// cKey -> 1...fieldNameList => fieldName -> cKey
|
||||||
lookup[identifierField] = classificationKey;
|
lookup[identifierField] = classificationKey;
|
||||||
return lookup;
|
return lookup;
|
||||||
}, lookup),
|
}, lookup),
|
||||||
{}
|
{}
|
||||||
);
|
);
|
||||||
}),
|
}
|
||||||
|
),
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Lists all the dataset fields found in the `columns` api, and intersects
|
* Lists all the dataset fields found in the `columns` api, and intersects
|
||||||
@ -166,10 +165,9 @@ export default Component.extend({
|
|||||||
// assign to field, otherwise null
|
// assign to field, otherwise null
|
||||||
// Rather than assigning the default classification here, nulling gives the benefit of allowing
|
// Rather than assigning the default classification here, nulling gives the benefit of allowing
|
||||||
// subsequent consumer know that this field did not have a previous classification
|
// subsequent consumer know that this field did not have a previous classification
|
||||||
const field = classification ?
|
const field = classification
|
||||||
get(this, `${sourceClassificationKey}.${classification}`)
|
? get(this, `${sourceClassificationKey}.${classification}`).findBy('identifierField', identifierField)
|
||||||
.findBy('identifierField', identifierField) :
|
: null;
|
||||||
null;
|
|
||||||
|
|
||||||
// Extract the logicalType from the field
|
// Extract the logicalType from the field
|
||||||
const logicalType = isPresent(field) ? field.logicalType : null;
|
const logicalType = isPresent(field) ? field.logicalType : null;
|
||||||
@ -190,21 +188,22 @@ export default Component.extend({
|
|||||||
* tracking header.
|
* tracking header.
|
||||||
* Used to indicate to viewer that these fields are hidden.
|
* Used to indicate to viewer that these fields are hidden.
|
||||||
*/
|
*/
|
||||||
containsHiddenTrackingFields: computed(
|
containsHiddenTrackingFields: computed('classificationDataFieldsSansHiddenTracking.length', function() {
|
||||||
'classificationDataFieldsSansHiddenTracking.length',
|
// If their is a diff in complianceDataFields and complianceDataFieldsSansHiddenTracking,
|
||||||
function () {
|
// then we have hidden tracking fields
|
||||||
// If their is a diff in complianceDataFields and complianceDataFieldsSansHiddenTracking,
|
return (
|
||||||
// then we have hidden tracking fields
|
get(this, 'classificationDataFieldsSansHiddenTracking.length') !== get(this, 'classificationDataFields.length')
|
||||||
return get(this, 'classificationDataFieldsSansHiddenTracking.length') !== get(this, 'classificationDataFields.length');
|
);
|
||||||
}),
|
}),
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @type {Array.<Object>} Filters the mapped confidential data fields without `kafka type`
|
* @type {Array.<Object>} Filters the mapped confidential data fields without `kafka type`
|
||||||
* tracking headers
|
* tracking headers
|
||||||
*/
|
*/
|
||||||
classificationDataFieldsSansHiddenTracking: computed('classificationDataFields.[]', function () {
|
classificationDataFieldsSansHiddenTracking: computed('classificationDataFields.[]', function() {
|
||||||
return get(this, 'classificationDataFields')
|
return get(this, 'classificationDataFields').filter(
|
||||||
.filter(({ identifierField }) => !isTrackingHeaderField(identifierField));
|
({ identifierField }) => !isTrackingHeaderField(identifierField)
|
||||||
|
);
|
||||||
}),
|
}),
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -219,20 +218,19 @@ export default Component.extend({
|
|||||||
.then(({ status = 'error' }) => {
|
.then(({ status = 'error' }) => {
|
||||||
// The server api currently responds with an object containing
|
// The server api currently responds with an object containing
|
||||||
// a status when complete
|
// a status when complete
|
||||||
return status === 'ok' ?
|
return status === 'ok'
|
||||||
setProperties(this, {
|
? setProperties(this, {
|
||||||
_message: successMessage || successUpdating,
|
_message: successMessage || successUpdating,
|
||||||
_alertType: 'success'
|
_alertType: 'success'
|
||||||
}) :
|
})
|
||||||
Promise.reject(new Error(`Reason code for this is ${status}`));
|
: Promise.reject(new Error(`Reason code for this is ${status}`));
|
||||||
})
|
})
|
||||||
.catch(err => {
|
.catch(err => {
|
||||||
let _message = `${failedUpdating} \n ${err}`;
|
let _message = `${failedUpdating} \n ${err}`;
|
||||||
let _alertType = 'danger';
|
let _alertType = 'danger';
|
||||||
|
|
||||||
if (get(this, 'isNewSecuritySpecification')) {
|
if (get(this, 'isNewSecuritySpecification')) {
|
||||||
_message = 'This dataset does not have any ' +
|
_message = 'This dataset does not have any previously saved fields with a Security Classification.';
|
||||||
'previously saved fields with a Security Classification.';
|
|
||||||
_alertType = 'info';
|
_alertType = 'info';
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -264,7 +262,11 @@ export default Component.extend({
|
|||||||
const nextProps = { identifierField, logicalType };
|
const nextProps = { identifierField, logicalType };
|
||||||
// The current classification name for the candidate identifier
|
// The current classification name for the candidate identifier
|
||||||
const currentClassLookup = get(this, 'fieldNameToClass');
|
const currentClassLookup = get(this, 'fieldNameToClass');
|
||||||
const defaultClassification = getWithDefault(this, `${sourceClassificationKey}.${defaultFieldDataTypeClassification[logicalType]}`, []);
|
const defaultClassification = getWithDefault(
|
||||||
|
this,
|
||||||
|
`${sourceClassificationKey}.${defaultFieldDataTypeClassification[logicalType]}`,
|
||||||
|
[]
|
||||||
|
);
|
||||||
let currentClassificationName = currentClassLookup[identifierField];
|
let currentClassificationName = currentClassLookup[identifierField];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -278,11 +280,11 @@ export default Component.extend({
|
|||||||
const currentClassification = get(this, `${sourceClassificationKey}.${currentClassificationName}`);
|
const currentClassification = get(this, `${sourceClassificationKey}.${currentClassificationName}`);
|
||||||
|
|
||||||
if (!Array.isArray(currentClassification)) {
|
if (!Array.isArray(currentClassification)) {
|
||||||
throw new Error(`
|
throw new Error(
|
||||||
You have specified a classification object that is not a list ${currentClassification}.
|
`You have specified a classification object that is not a list ${currentClassification}.
|
||||||
Ensure that the classification for this identifierField (${identifierField}) is
|
Ensure that the classification for this identifierField (${identifierField}) is
|
||||||
set before attempting to change the logicalType.
|
set before attempting to change the logicalType.`
|
||||||
`);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const field = currentClassification.findBy('identifierField', identifierField);
|
const field = currentClassification.findBy('identifierField', identifierField);
|
||||||
@ -292,8 +294,7 @@ export default Component.extend({
|
|||||||
if (isPresent(field)) {
|
if (isPresent(field)) {
|
||||||
// Remove identifierField from list
|
// Remove identifierField from list
|
||||||
currentClassification.setObjects(
|
currentClassification.setObjects(
|
||||||
currentClassification.filter(
|
currentClassification.filter(({ identifierField: fieldName }) => fieldName !== identifierField)
|
||||||
({ identifierField: fieldName }) => fieldName !== identifierField)
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -343,24 +344,17 @@ export default Component.extend({
|
|||||||
// in any other classification lists by checking that the lookup is void
|
// in any other classification lists by checking that the lookup is void
|
||||||
if (!isBlank(currentClass)) {
|
if (!isBlank(currentClass)) {
|
||||||
// Get the current classification list
|
// Get the current classification list
|
||||||
const currentClassification = get(
|
const currentClassification = get(this, `${sourceClassificationKey}.${currentClass}`);
|
||||||
this,
|
|
||||||
`${sourceClassificationKey}.${currentClass}`
|
|
||||||
);
|
|
||||||
|
|
||||||
// Remove identifierField from list
|
// Remove identifierField from list
|
||||||
currentClassification.setObjects(
|
currentClassification.setObjects(
|
||||||
currentClassification.filter(
|
currentClassification.filter(({ identifierField: fieldName }) => fieldName !== identifierField)
|
||||||
({ identifierField: fieldName }) => fieldName !== identifierField)
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (classKey) {
|
if (classKey) {
|
||||||
// Get the candidate list
|
// Get the candidate list
|
||||||
let classification = get(
|
let classification = get(this, `${sourceClassificationKey}.${classKey}`);
|
||||||
this,
|
|
||||||
`${sourceClassificationKey}.${classKey}`
|
|
||||||
);
|
|
||||||
// In the case that the list is not pre-populated,
|
// In the case that the list is not pre-populated,
|
||||||
// the value will be the default null, array ops won't work here
|
// the value will be the default null, array ops won't work here
|
||||||
// ...so make array
|
// ...so make array
|
||||||
@ -400,9 +394,8 @@ export default Component.extend({
|
|||||||
* @type {Boolean}
|
* @type {Boolean}
|
||||||
*/
|
*/
|
||||||
const classedFieldsHaveLogicalType = classifiers.every(classifier =>
|
const classedFieldsHaveLogicalType = classifiers.every(classifier =>
|
||||||
this.ensureFieldsContainLogicalType(
|
this.ensureFieldsContainLogicalType(getWithDefault(this, `${sourceClassificationKey}.${classifier}`, []))
|
||||||
getWithDefault(this, `${sourceClassificationKey}.${classifier}`, [])
|
);
|
||||||
));
|
|
||||||
|
|
||||||
if (classedFieldsHaveLogicalType) {
|
if (classedFieldsHaveLogicalType) {
|
||||||
this.whenRequestCompletes(get(this, 'onSave')());
|
this.whenRequestCompletes(get(this, 'onSave')());
|
||||||
|
|||||||
@ -1,4 +1,22 @@
|
|||||||
import Ember from 'ember';
|
import Ember from 'ember';
|
||||||
|
|
||||||
export default Ember.Component.extend({
|
const { Component, get } = Ember;
|
||||||
|
|
||||||
|
export default Component.extend({
|
||||||
|
didInsertElement() {
|
||||||
|
this._super(...arguments);
|
||||||
|
const metric = get(this, 'model');
|
||||||
|
|
||||||
|
if (metric) {
|
||||||
|
self.initializeXEditable(
|
||||||
|
metric.id,
|
||||||
|
metric.description,
|
||||||
|
metric.dashboardName,
|
||||||
|
metric.sourceType,
|
||||||
|
metric.grain,
|
||||||
|
metric.displayFactor,
|
||||||
|
metric.displayFactorSym
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@ -23,7 +23,7 @@ export default Ember.Component.extend({
|
|||||||
}
|
}
|
||||||
}).done(function (data, txt, xhr) {
|
}).done(function (data, txt, xhr) {
|
||||||
_this.set('metric.watchId', data.watchId)
|
_this.set('metric.watchId', data.watchId)
|
||||||
_this.sendAction('getMetrics')
|
// _this.sendAction('getMetrics')
|
||||||
}).fail(function (xhr, txt, err) {
|
}).fail(function (xhr, txt, err) {
|
||||||
console.log('Error: Could not watch metric.')
|
console.log('Error: Could not watch metric.')
|
||||||
})
|
})
|
||||||
|
|||||||
@ -5,7 +5,9 @@ const { Controller } = Ember;
|
|||||||
* Handles query params for browse.entity route
|
* Handles query params for browse.entity route
|
||||||
*/
|
*/
|
||||||
export default Controller.extend({
|
export default Controller.extend({
|
||||||
queryParams: ['page', 'urn'],
|
queryParams: ['page', 'urn', 'size', 'name'],
|
||||||
page: 1,
|
page: 1,
|
||||||
urn: ''
|
urn: '',
|
||||||
|
name: '',
|
||||||
|
size: 10
|
||||||
});
|
});
|
||||||
|
|||||||
6
wherehows-web/app/controllers/flows/flow.js
Normal file
6
wherehows-web/app/controllers/flows/flow.js
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
import Ember from 'ember';
|
||||||
|
const { Controller } = Ember;
|
||||||
|
|
||||||
|
export default Controller.extend({
|
||||||
|
queryParams: ['name']
|
||||||
|
});
|
||||||
@ -1,12 +1,20 @@
|
|||||||
import {
|
import {
|
||||||
initializeState,
|
initializeState,
|
||||||
receiveNodes,
|
urnsToNodeUrn,
|
||||||
receiveEntities,
|
receiveEntities,
|
||||||
createUrnMapping,
|
createUrnMapping,
|
||||||
createPageMapping
|
createPageMapping
|
||||||
} from 'wherehows-web/reducers/entities';
|
} from 'wherehows-web/reducers/entities';
|
||||||
import { ActionTypes } from 'wherehows-web/actions/datasets';
|
import { ActionTypes } from 'wherehows-web/actions/datasets';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the default initial state for metrics slice. Appends a byName property to the shared representation.
|
||||||
|
* @type {Object}
|
||||||
|
*/
|
||||||
|
const initialState = Object.assign({}, initializeState(), {
|
||||||
|
nodesByUrn: []
|
||||||
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* datasets root reducer
|
* datasets root reducer
|
||||||
* Takes the `datasets` slice of the state tree and performs the specified reductions for each action
|
* Takes the `datasets` slice of the state tree and performs the specified reductions for each action
|
||||||
@ -15,14 +23,14 @@ import { ActionTypes } from 'wherehows-web/actions/datasets';
|
|||||||
* @prop {String} action.type actionType
|
* @prop {String} action.type actionType
|
||||||
* @return {Object}
|
* @return {Object}
|
||||||
*/
|
*/
|
||||||
export default (state = initializeState(), action = {}) => {
|
export default (state = initialState, action = {}) => {
|
||||||
switch (action.type) {
|
switch (action.type) {
|
||||||
// Action indicating a request for datasets by page
|
// Action indicating a request for datasets by page
|
||||||
case ActionTypes.SELECT_PAGED_DATASETS:
|
case ActionTypes.SELECT_PAGED_DATASETS:
|
||||||
case ActionTypes.REQUEST_PAGED_DATASETS:
|
case ActionTypes.REQUEST_PAGED_DATASETS:
|
||||||
return Object.assign({}, state, {
|
return Object.assign({}, state, {
|
||||||
query: Object.assign({}, state.query, {
|
query: Object.assign({}, state.query, {
|
||||||
page: action.payload.page
|
page: action.payload.query.page
|
||||||
}),
|
}),
|
||||||
baseURL: action.payload.baseURL || state.baseURL,
|
baseURL: action.payload.baseURL || state.baseURL,
|
||||||
isFetching: true
|
isFetching: true
|
||||||
@ -51,7 +59,7 @@ export default (state = initializeState(), action = {}) => {
|
|||||||
case ActionTypes.RECEIVE_DATASET_NODES: // Action indicating a receipt of list nodes / datasets for dataset urn
|
case ActionTypes.RECEIVE_DATASET_NODES: // Action indicating a receipt of list nodes / datasets for dataset urn
|
||||||
return Object.assign({}, state, {
|
return Object.assign({}, state, {
|
||||||
isFetching: false,
|
isFetching: false,
|
||||||
nodesByUrn: receiveNodes(state, action.payload)
|
nodesByUrn: urnsToNodeUrn(state, action.payload)
|
||||||
});
|
});
|
||||||
|
|
||||||
default:
|
default:
|
||||||
|
|||||||
@ -52,6 +52,15 @@ const appendUrnIdMap = (
|
|||||||
[parentUrn]: union(urnEntities[parentUrn], entities.mapBy('id'))
|
[parentUrn]: union(urnEntities[parentUrn], entities.mapBy('id'))
|
||||||
});
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Appends a list of child entities ids for a given name. name is null for top level entities
|
||||||
|
* @param {String} entityName
|
||||||
|
*/
|
||||||
|
const appendNamedIdMap = entityName => (nameEntities, { parentName = null, [entityName]: entities = [] }) =>
|
||||||
|
Object.assign({}, nameEntities, {
|
||||||
|
[parentName]: union(nameEntities[parentName], entities.mapBy('id'))
|
||||||
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns a curried function that receives entityName to lookup on the props object
|
* Returns a curried function that receives entityName to lookup on the props object
|
||||||
* @param {String} entityName
|
* @param {String} entityName
|
||||||
@ -79,12 +88,27 @@ const entitiesToPage = (
|
|||||||
* Maps a urn to a list of child nodes from the list api
|
* Maps a urn to a list of child nodes from the list api
|
||||||
* @param {Object} state
|
* @param {Object} state
|
||||||
* @param {Array} nodes
|
* @param {Array} nodes
|
||||||
* @return {Object}
|
* @param {String} parentUrn
|
||||||
|
* @return {*}
|
||||||
*/
|
*/
|
||||||
const urnsToNodeUrn = (state, { data: nodes = [] }) => {
|
const urnsToNodeUrn = (state, { nodes = [], parentUrn = null } = {}) => {
|
||||||
const { query: { urn }, nodesByUrn } = state;
|
const { nodesByUrn } = state;
|
||||||
return Object.assign({}, nodesByUrn, {
|
return Object.assign({}, nodesByUrn, {
|
||||||
[urn]: union(nodes)
|
[parentUrn]: union(nodes)
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Maps a name to a list of child nodes from the list api
|
||||||
|
* @param {Object} state
|
||||||
|
* @param {Array} nodes
|
||||||
|
* @param {String} parentName
|
||||||
|
* @return {*}
|
||||||
|
*/
|
||||||
|
const namesToNodeName = (state, { nodes = [], parentName = null } = {}) => {
|
||||||
|
const { nodesByName } = state;
|
||||||
|
return Object.assign({}, nodesByName, {
|
||||||
|
[parentName]: union(nodes)
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -112,20 +136,27 @@ const createUrnMapping = entityName => (state, payload = {}) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
* Curries an entityName into a function to append named entity entities
|
||||||
|
* @param {String} entityName
|
||||||
|
*/
|
||||||
|
const createNameMapping = entityName => (state, payload = {}) => {
|
||||||
|
return appendNamedIdMap(entityName)(state, payload);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Appends a list of entities to a page
|
||||||
* @param {String} entityName
|
* @param {String} entityName
|
||||||
*/
|
*/
|
||||||
const createPageMapping = entityName => (state, payload = {}) => {
|
const createPageMapping = entityName => (state, payload = {}) => {
|
||||||
return entitiesToPage(entityName)(state, payload);
|
return entitiesToPage(entityName)(state, payload);
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
export {
|
||||||
* Takes the response from the list api request and invokes a function to
|
initializeState,
|
||||||
* map a urn to child urns or nodes
|
namesToNodeName,
|
||||||
* @param {Object} state
|
urnsToNodeUrn,
|
||||||
* @param {Object} payload the response from the list endpoint/api
|
receiveEntities,
|
||||||
* @return {Object}
|
createUrnMapping,
|
||||||
*/
|
createPageMapping,
|
||||||
const receiveNodes = (state, payload = {}) => urnsToNodeUrn(state, payload);
|
createNameMapping
|
||||||
|
};
|
||||||
export { receiveNodes, initializeState, receiveEntities, createUrnMapping, createPageMapping };
|
|
||||||
|
|||||||
@ -1,6 +1,19 @@
|
|||||||
import { initializeState, createUrnMapping, receiveEntities, createPageMapping } from 'wherehows-web/reducers/entities';
|
import {
|
||||||
|
initializeState,
|
||||||
|
createUrnMapping,
|
||||||
|
receiveEntities,
|
||||||
|
createPageMapping,
|
||||||
|
urnsToNodeUrn
|
||||||
|
} from 'wherehows-web/reducers/entities';
|
||||||
import { ActionTypes } from 'wherehows-web/actions/flows';
|
import { ActionTypes } from 'wherehows-web/actions/flows';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the default initial state for flows slice. Appends a byName property to the shared representation.
|
||||||
|
* @type {Object}
|
||||||
|
*/
|
||||||
|
const initialState = Object.assign({}, initializeState(), {
|
||||||
|
nodesByUrn: []
|
||||||
|
});
|
||||||
/**
|
/**
|
||||||
* Takes the `flows` slice of the state tree and performs the specified reductions for each action
|
* Takes the `flows` slice of the state tree and performs the specified reductions for each action
|
||||||
* @param {Object} state = initialState the slice of the state object representing flows
|
* @param {Object} state = initialState the slice of the state object representing flows
|
||||||
@ -8,23 +21,41 @@ import { ActionTypes } from 'wherehows-web/actions/flows';
|
|||||||
* @prop {String} action.type actionType
|
* @prop {String} action.type actionType
|
||||||
* @return {Object}
|
* @return {Object}
|
||||||
*/
|
*/
|
||||||
export default (state = initializeState(), action = {}) => {
|
export default (state = initialState, action = {}) => {
|
||||||
switch (action.type) {
|
switch (action.type) {
|
||||||
case ActionTypes.RECEIVE_PAGED_FLOWS:
|
case ActionTypes.RECEIVE_PAGED_FLOWS:
|
||||||
|
case ActionTypes.RECEIVE_PAGED_URN_FLOWS:
|
||||||
return Object.assign({}, state, {
|
return Object.assign({}, state, {
|
||||||
isFetching: false,
|
isFetching: false,
|
||||||
byUrn: createUrnMapping('flows')(state.byUrn, action.payload),
|
|
||||||
byId: receiveEntities('flows')(state.byId, action.payload),
|
byId: receiveEntities('flows')(state.byId, action.payload),
|
||||||
byPage: createPageMapping('flows')(state.byPage, action.payload)
|
byPage: createPageMapping('flows')(state.byPage, action.payload),
|
||||||
|
byUrn: createUrnMapping('flows')(state.byUrn, action.payload)
|
||||||
|
});
|
||||||
|
|
||||||
|
case ActionTypes.REQUEST_FLOWS_NODES:
|
||||||
|
return Object.assign({}, state, {
|
||||||
|
query: Object.assign({}, state.query, action.payload.query),
|
||||||
|
isFetching: true
|
||||||
|
});
|
||||||
|
|
||||||
|
case ActionTypes.RECEIVE_FLOWS_NODES:
|
||||||
|
return Object.assign({}, state, {
|
||||||
|
isFetching: false,
|
||||||
|
nodesByUrn: urnsToNodeUrn(state, action.payload)
|
||||||
|
});
|
||||||
|
|
||||||
|
case ActionTypes.REQUEST_PAGED_URN_FLOWS:
|
||||||
|
return Object.assign({}, state, {
|
||||||
|
query: Object.assign({}, state.query, action.payload.query)
|
||||||
});
|
});
|
||||||
|
|
||||||
case ActionTypes.SELECT_PAGED_FLOWS:
|
case ActionTypes.SELECT_PAGED_FLOWS:
|
||||||
case ActionTypes.REQUEST_PAGED_FLOWS:
|
case ActionTypes.REQUEST_PAGED_FLOWS:
|
||||||
return Object.assign({}, state, {
|
return Object.assign({}, state, {
|
||||||
query: Object.assign({}, state.query, {
|
query: Object.assign({}, state.query, {
|
||||||
page: action.payload.page
|
page: action.payload.query.page
|
||||||
}),
|
}),
|
||||||
baseURL: action.payload.baseURL,
|
baseURL: action.payload.baseURL || state.baseURL,
|
||||||
isFetching: true
|
isFetching: true
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@ -1,13 +1,19 @@
|
|||||||
import { initializeState, createUrnMapping, receiveEntities, createPageMapping } from 'wherehows-web/reducers/entities';
|
import {
|
||||||
|
initializeState,
|
||||||
|
receiveEntities,
|
||||||
|
namesToNodeName,
|
||||||
|
createPageMapping,
|
||||||
|
createNameMapping
|
||||||
|
} from 'wherehows-web/reducers/entities';
|
||||||
import { ActionTypes } from 'wherehows-web/actions/metrics';
|
import { ActionTypes } from 'wherehows-web/actions/metrics';
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets the default initial state for metrics slice. Appends a byName property to the shared representation.
|
* Sets the default initial state for metrics slice. Appends a byName property to the shared representation.
|
||||||
* @type {Object}
|
* @type {Object}
|
||||||
*/
|
*/
|
||||||
const initialState = Object.assign({}, initializeState(), {
|
const initialState = Object.assign({}, initializeState(), {
|
||||||
byName: {}
|
byName: {},
|
||||||
|
nodesByName: []
|
||||||
});
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -23,19 +29,36 @@ export default (state = initialState, action = {}) => {
|
|||||||
case ActionTypes.SELECT_PAGED_METRICS:
|
case ActionTypes.SELECT_PAGED_METRICS:
|
||||||
case ActionTypes.REQUEST_PAGED_METRICS:
|
case ActionTypes.REQUEST_PAGED_METRICS:
|
||||||
return Object.assign({}, state, {
|
return Object.assign({}, state, {
|
||||||
query: Object.assign({}, state.query, {
|
query: Object.assign({}, state.query, action.payload.query),
|
||||||
page: action.payload.page
|
|
||||||
}),
|
|
||||||
baseURL: action.payload.baseURL || state.baseURL,
|
baseURL: action.payload.baseURL || state.baseURL,
|
||||||
isFetching: true
|
isFetching: true
|
||||||
});
|
});
|
||||||
|
|
||||||
// Action indicating a receipt of metrics by page
|
// Action indicating a receipt of metrics by page
|
||||||
|
case ActionTypes.REQUEST_PAGED_NAMED_METRICS:
|
||||||
|
return Object.assign({}, state, {
|
||||||
|
query: Object.assign({}, state.query, action.payload.query)
|
||||||
|
});
|
||||||
|
|
||||||
case ActionTypes.RECEIVE_PAGED_METRICS:
|
case ActionTypes.RECEIVE_PAGED_METRICS:
|
||||||
|
case ActionTypes.RECEIVE_PAGED_NAMED_METRICS:
|
||||||
return Object.assign({}, state, {
|
return Object.assign({}, state, {
|
||||||
isFetching: false,
|
isFetching: false,
|
||||||
byUrn: createUrnMapping('metrics')(state.byUrn, action.payload),
|
|
||||||
byId: receiveEntities('metrics')(state.byId, action.payload),
|
byId: receiveEntities('metrics')(state.byId, action.payload),
|
||||||
byPage: createPageMapping('metrics')(state.byPage, action.payload)
|
byPage: createPageMapping('metrics')(state.byPage, action.payload),
|
||||||
|
byName: createNameMapping('metrics')(state.byName, action.payload)
|
||||||
|
});
|
||||||
|
|
||||||
|
case ActionTypes.REQUEST_METRICS_NODES:
|
||||||
|
return Object.assign({}, state, {
|
||||||
|
query: Object.assign({}, state.query, action.payload.query),
|
||||||
|
isFetching: true
|
||||||
|
});
|
||||||
|
|
||||||
|
case ActionTypes.RECEIVE_METRICS_NODES: // Action indicating a receipt of list nodes / datasets for dataset urn
|
||||||
|
return Object.assign({}, state, {
|
||||||
|
isFetching: false,
|
||||||
|
nodesByName: namesToNodeName(state, action.payload)
|
||||||
});
|
});
|
||||||
|
|
||||||
default:
|
default:
|
||||||
|
|||||||
@ -8,9 +8,9 @@ const { Route } = Ember;
|
|||||||
// TODO: Route should transition to browse/entity, pay attention to the fact that
|
// TODO: Route should transition to browse/entity, pay attention to the fact that
|
||||||
// this route initializes store with entity metrics on entry
|
// this route initializes store with entity metrics on entry
|
||||||
const entityUrls = {
|
const entityUrls = {
|
||||||
datasets: '/api/v1/datasets?size=10',
|
datasets: '/api/v1/datasets',
|
||||||
metrics: '/api/v1/metrics?size=10',
|
metrics: '/api/v1/metrics',
|
||||||
flows: '/api/v1/flows?size=10'
|
flows: '/api/v1/flows'
|
||||||
};
|
};
|
||||||
|
|
||||||
export default route({
|
export default route({
|
||||||
|
|||||||
@ -1,15 +1,21 @@
|
|||||||
import Ember from 'ember';
|
import Ember from 'ember';
|
||||||
import route from 'ember-redux/route';
|
import route from 'ember-redux/route';
|
||||||
import AuthenticatedRouteMixin from 'ember-simple-auth/mixins/authenticated-route-mixin';
|
import AuthenticatedRouteMixin from 'ember-simple-auth/mixins/authenticated-route-mixin';
|
||||||
import { asyncRequestNodeList } from 'wherehows-web/actions/browse/entity';
|
import { asyncRequestEntityQueryData } from 'wherehows-web/actions/browse/entity';
|
||||||
|
|
||||||
const { Route } = Ember;
|
const { Route } = Ember;
|
||||||
|
|
||||||
|
// TODO: DSS-6581 Create URL retrieval module
|
||||||
const listUrl = '/api/v1/list';
|
const listUrl = '/api/v1/list';
|
||||||
const queryParams = ['page', 'urn'];
|
const queryParamsKeys = ['page', 'urn', 'name'];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a route handler for browse.entity route
|
||||||
|
* entity can be any (datasets|metrics|flows)
|
||||||
|
* @type {Ember.Route}
|
||||||
|
*/
|
||||||
const BrowseEntityRoute = Route.extend(AuthenticatedRouteMixin, {
|
const BrowseEntityRoute = Route.extend(AuthenticatedRouteMixin, {
|
||||||
queryParams: queryParams.reduce(
|
queryParams: queryParamsKeys.reduce(
|
||||||
(queryParams, param) =>
|
(queryParams, param) =>
|
||||||
Object.assign({}, queryParams, {
|
Object.assign({}, queryParams, {
|
||||||
[param]: { refreshModel: true }
|
[param]: { refreshModel: true }
|
||||||
@ -18,6 +24,15 @@ const BrowseEntityRoute = Route.extend(AuthenticatedRouteMixin, {
|
|||||||
)
|
)
|
||||||
});
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ember Redux decorator wraps incoming route object and injects the redux store.dispatch method as the
|
||||||
|
* first argument
|
||||||
|
*/
|
||||||
export default route({
|
export default route({
|
||||||
model: (dispatch, params) => dispatch(asyncRequestNodeList(params, listUrl, { queryParams }))
|
/**
|
||||||
|
*
|
||||||
|
* @param dispatch
|
||||||
|
* @param params
|
||||||
|
*/
|
||||||
|
model: (dispatch, params) => dispatch(asyncRequestEntityQueryData(params, listUrl, { queryParamsKeys }))
|
||||||
})(BrowseEntityRoute);
|
})(BrowseEntityRoute);
|
||||||
|
|||||||
@ -1,4 +1,44 @@
|
|||||||
import Ember from 'ember';
|
import Ember from 'ember';
|
||||||
|
import fetch from 'ember-network/fetch';
|
||||||
|
|
||||||
export default Ember.Route.extend({
|
const { Route, setProperties } = Ember;
|
||||||
|
|
||||||
|
const flowUrlRoot = 'api/v1/flow';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Takes an object representing a flow and generates a list of breadcrumb items for navigating this hierarchy
|
||||||
|
* @param {Object} props properties for the current flow
|
||||||
|
* @return {[*,*,*]}
|
||||||
|
*/
|
||||||
|
const makeFlowsBreadcrumbs = (props = {}) => {
|
||||||
|
const { name: application, id, flow } = props;
|
||||||
|
return [
|
||||||
|
{ crumb: 'Flows', name: '', urn: '' },
|
||||||
|
{ crumb: application, name: application, urn: '' },
|
||||||
|
{ crumb: flow, flow_id: id, urn: application }
|
||||||
|
];
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Route.extend({
|
||||||
|
setupController: function(controller, model = {}) {
|
||||||
|
setProperties(controller, {
|
||||||
|
model,
|
||||||
|
breadcrumbs: makeFlowsBreadcrumbs(model.data)
|
||||||
|
});
|
||||||
|
|
||||||
|
},
|
||||||
|
|
||||||
|
model: async (params = {}) => {
|
||||||
|
const { flow_id, name } = params;
|
||||||
|
const flowsUrl = `${flowUrlRoot}/${name}/${flow_id}`;
|
||||||
|
let response = await fetch(flowsUrl).then(response => response.json());
|
||||||
|
|
||||||
|
if (response && response.status === 'ok') {
|
||||||
|
response.data = Object.assign({}, response.data, {
|
||||||
|
name
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return response;
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@ -1,111 +1,48 @@
|
|||||||
import Ember from 'ember';
|
import Ember from 'ember';
|
||||||
|
import fetch from 'ember-network/fetch';
|
||||||
|
|
||||||
export default Ember.Route.extend({
|
const { Route, setProperties } = Ember;
|
||||||
setupController: function (controller, model) {
|
|
||||||
const metricsController = this.controllerFor('metrics');
|
|
||||||
|
|
||||||
if (metricsController) {
|
const metricsUrlRoot = '/api/v1/metrics';
|
||||||
metricsController.set('detailview', true);
|
|
||||||
}
|
|
||||||
currentTab = 'Metrics';
|
|
||||||
updateActiveTab();
|
|
||||||
var name;
|
|
||||||
var id = 0;
|
|
||||||
if (model && model.id) {
|
|
||||||
var url = 'api/v1/metrics/' + model.id;
|
|
||||||
id = model.id;
|
|
||||||
if (model.category) {
|
|
||||||
name = '{' + model.category + '} ' + model.name;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
name = model.name;
|
|
||||||
}
|
|
||||||
var breadcrumbs;
|
|
||||||
$.get(url, function (data) {
|
|
||||||
if (data && data.status == "ok") {
|
|
||||||
controller.set("model", data.metric);
|
|
||||||
var dashboard = data.metric.dashboardName;
|
|
||||||
if (!dashboard) {
|
|
||||||
dashboard = '(Other)';
|
|
||||||
}
|
|
||||||
var group = data.metric.group;
|
|
||||||
if (!group) {
|
|
||||||
group = '(Other)';
|
|
||||||
}
|
|
||||||
breadcrumbs = [{"title": "METRICS_ROOT", "urn": "page/1"},
|
|
||||||
{"title": dashboard, "urn": "name/" + dashboard + "/page/1"},
|
|
||||||
{"title": group, "urn": "name/" + dashboard + "/" + group + "/page/1"},
|
|
||||||
{"title": data.metric.name, "urn": model.id}];
|
|
||||||
controller.set('breadcrumbs', breadcrumbs);
|
|
||||||
setTimeout(initializeXEditable(id,
|
|
||||||
data.metric.description,
|
|
||||||
data.metric.dashboardName,
|
|
||||||
data.metric.sourceType,
|
|
||||||
data.metric.grain,
|
|
||||||
data.metric.displayFactor,
|
|
||||||
data.metric.displayFactorSym), 500);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
else if (model && model.metric) {
|
|
||||||
id = model.metric.id;
|
|
||||||
var dashboard = model.metric.dashboardName;
|
|
||||||
if (!dashboard) {
|
|
||||||
dashboard = '(Other)';
|
|
||||||
}
|
|
||||||
var group = model.metric.group;
|
|
||||||
if (!group) {
|
|
||||||
group = '(Other)';
|
|
||||||
}
|
|
||||||
breadcrumbs = [{"title": "METRICS_ROOT", "urn": "page/1"},
|
|
||||||
{"title": name, "urn": "name/" + dashboard + "/page/1"},
|
|
||||||
{"title": group, "urn": "name/" + dashboard + "/" + group + "/page/1"},
|
|
||||||
{"title": model.metric.name, "urn": model.id}];
|
|
||||||
controller.set('breadcrumbs', breadcrumbs);
|
|
||||||
if (model.metric.category) {
|
|
||||||
name = '{' + model.metric.category + '} ' + model.metric.name;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
name = model.metric.name;
|
|
||||||
}
|
|
||||||
setTimeout(initializeXEditable(id,
|
|
||||||
model.metric.description,
|
|
||||||
model.metric.dashboardName,
|
|
||||||
model.metric.sourceType,
|
|
||||||
model.metric.grain,
|
|
||||||
model.metric.displayFactor,
|
|
||||||
model.metric.displayFactorSym), 500);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Takes an object representing a metric and generates a list of breadcrumb items for each level in the
|
||||||
|
* hierarchy
|
||||||
|
* @param {Object} metric properties for the current metric
|
||||||
|
* @return {[*,*,*,*]}
|
||||||
|
*/
|
||||||
|
const makeMetricsBreadcrumbs = (metric = {}) => {
|
||||||
|
let { id, dashboardName, group, category, name } = metric;
|
||||||
|
dashboardName || (dashboardName = '(Other)');
|
||||||
|
group || (group = '(Other)');
|
||||||
|
name = category ? `{${category}} ${name}` : name;
|
||||||
|
|
||||||
var listUrl = 'api/v1/list/metric/' + id;
|
return [
|
||||||
$.get(listUrl, function (data) {
|
{ crumb: 'Metrics', name: '' },
|
||||||
if (data && data.status == "ok") {
|
{ crumb: dashboardName, name: dashboardName },
|
||||||
// renderMetricListView(data.nodes, id);
|
{ crumb: group, name: `${dashboardName}/${group}` },
|
||||||
}
|
{ crumb: name, name: id }
|
||||||
|
];
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Route.extend({
|
||||||
|
setupController(controller, model) {
|
||||||
|
const { metric } = model;
|
||||||
|
|
||||||
|
// Set the metric as the model and create breadcrumbs
|
||||||
|
setProperties(controller, {
|
||||||
|
model: metric,
|
||||||
|
breadcrumbs: makeMetricsBreadcrumbs(metric)
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
actions: {
|
|
||||||
getMetrics: function () {
|
|
||||||
var id = this.get('controller.model.id');
|
|
||||||
var listUrl = 'api/v1/list/metrics/' + id;
|
|
||||||
|
|
||||||
$.get(listUrl, function (data) {
|
/**
|
||||||
if (data && data.status == "ok") {
|
* Fetches the metric with the id specified in the route
|
||||||
// renderMetricListView(data.nodes, id);
|
* @param metric_id
|
||||||
}
|
* @return {Thenable<V, void>|Promise<V, X>}
|
||||||
});
|
*/
|
||||||
|
model({ metric_id }) {
|
||||||
var url = 'api/v1/metrics/' + this.get('controller.model.id')
|
const metricsUrl = `${metricsUrlRoot}/${metric_id}`;
|
||||||
var _this = this
|
return fetch(metricsUrl).then(response => response.json());
|
||||||
currentTab = 'Metrics';
|
|
||||||
updateActiveTab();
|
|
||||||
$.get(url, function (data) {
|
|
||||||
if (data && data.status == "ok") {
|
|
||||||
_this.set('controller.model', data.metric)
|
|
||||||
_this.set('controller.detailview', true);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@ -9,7 +9,6 @@
|
|||||||
*/
|
*/
|
||||||
.ember-radio-button {
|
.ember-radio-button {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
width: 60px;
|
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -78,7 +78,8 @@ $pad-width: 16px;
|
|||||||
.ember-radio-button {
|
.ember-radio-button {
|
||||||
position: relative;
|
position: relative;
|
||||||
left: 26px;
|
left: 26px;
|
||||||
width: 60px;
|
min-width: 60px;
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
&:before,
|
&:before,
|
||||||
&:after {
|
&:after {
|
||||||
|
|||||||
@ -36,6 +36,9 @@
|
|||||||
'& tr:nth-child(odd)': (
|
'& tr:nth-child(odd)': (
|
||||||
background-color: restyle-var(zebra)
|
background-color: restyle-var(zebra)
|
||||||
)
|
)
|
||||||
|
),
|
||||||
|
'with variable width': (
|
||||||
|
table-layout: auto
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
));
|
));
|
||||||
@ -50,4 +53,8 @@
|
|||||||
&--stripped {
|
&--stripped {
|
||||||
@include restyle(table with stripped rows);
|
@include restyle(table with stripped rows);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&--dynamic {
|
||||||
|
@include restyle(table with variable width);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -3,7 +3,7 @@
|
|||||||
{{#each browseData as |entity|}}
|
{{#each browseData as |entity|}}
|
||||||
{{#nav-link
|
{{#nav-link
|
||||||
"browse.entity" entity.entity
|
"browse.entity" entity.entity
|
||||||
(query-params page=1 urn="")
|
(query-params page=1 urn="" name="")
|
||||||
tagName="li"
|
tagName="li"
|
||||||
class="col-md-4 browse-nav__entity"}}
|
class="col-md-4 browse-nav__entity"}}
|
||||||
<div class="browse-nav__item">
|
<div class="browse-nav__item">
|
||||||
|
|||||||
@ -1,30 +1,102 @@
|
|||||||
{{#dataset-table
|
{{#if (eq currentEntity 'flows')}}
|
||||||
fields=entities as |table|}}
|
{{#dataset-table
|
||||||
{{#table.body as |body|}}
|
fields=entities as |table|}}
|
||||||
{{#each
|
{{#table.head as |head|}}
|
||||||
table.data as |entity|}}
|
{{#head.column}}
|
||||||
{{#body.row as |row|}}
|
Flow Group
|
||||||
{{#row.cell}}
|
{{/head.column}}
|
||||||
{{#link-to entityRoute entity.id}}
|
|
||||||
<span class="entity-list__title">
|
|
||||||
{{entity.name}}
|
|
||||||
</span>
|
|
||||||
{{/link-to}}
|
|
||||||
|
|
||||||
{{dataset-owner-list owners=entity.owners datasetName=entity.name}}
|
{{#head.column}}
|
||||||
|
Flow Name
|
||||||
|
{{/head.column}}
|
||||||
|
|
||||||
{{#if entity.formatedModified}}
|
{{#head.column}}
|
||||||
<span>Last Modified:</span>
|
Flow Level
|
||||||
|
{{/head.column}}
|
||||||
|
|
||||||
<span title="{{entity.formatedModified}}">
|
{{#head.column}}
|
||||||
{{moment-from-now entity.formatedModified }}
|
Job Count
|
||||||
</span>
|
{{/head.column}}
|
||||||
{{/if}}
|
|
||||||
{{/row.cell}}
|
{{#head.column}}
|
||||||
{{#row.cell}}
|
Creation Time
|
||||||
{{datasets/dataset-actions actionItems=actionItems}}
|
{{/head.column}}
|
||||||
{{/row.cell}}
|
|
||||||
{{/body.row}}
|
{{#head.column}}
|
||||||
{{/each}}
|
Modified Time
|
||||||
{{/table.body}}
|
{{/head.column}}
|
||||||
{{/dataset-table}}
|
{{/table.head}}
|
||||||
|
|
||||||
|
{{#table.body as |body|}}
|
||||||
|
{{#each
|
||||||
|
table.data as |flow|}}
|
||||||
|
{{#body.row as |row|}}
|
||||||
|
{{#row.cell}}
|
||||||
|
{{flow.group}}
|
||||||
|
{{/row.cell}}
|
||||||
|
|
||||||
|
{{#row.cell}}
|
||||||
|
{{#link-to entityRoute flow.id (query-params name=flow.appCode)}}
|
||||||
|
{{flow.name}}
|
||||||
|
{{/link-to}}
|
||||||
|
{{/row.cell}}
|
||||||
|
|
||||||
|
{{#row.cell}}
|
||||||
|
{{flow.level}}
|
||||||
|
{{/row.cell}}
|
||||||
|
|
||||||
|
{{#row.cell}}
|
||||||
|
{{flow.jobCount}}
|
||||||
|
{{/row.cell}}
|
||||||
|
|
||||||
|
{{#row.cell}}
|
||||||
|
{{moment-calendar flow.created sameElse="MMM Do YYYY, h:mm a"}}
|
||||||
|
{{/row.cell}}
|
||||||
|
|
||||||
|
{{#row.cell}}
|
||||||
|
{{moment-calendar flow.modified sameElse="MMM Do YYYY, h:mm a"}}
|
||||||
|
{{/row.cell}}
|
||||||
|
{{/body.row}}
|
||||||
|
{{/each}}
|
||||||
|
{{/table.body}}
|
||||||
|
{{/dataset-table}}
|
||||||
|
{{else}}
|
||||||
|
{{#dataset-table
|
||||||
|
fields=entities as |table|}}
|
||||||
|
{{#table.body as |body|}}
|
||||||
|
{{#each
|
||||||
|
table.data as |entity|}}
|
||||||
|
{{#body.row as |row|}}
|
||||||
|
{{#row.cell}}
|
||||||
|
{{#link-to entityRoute entity.id}}
|
||||||
|
<span class="entity-list__title">
|
||||||
|
{{entity.name}}
|
||||||
|
</span>
|
||||||
|
{{/link-to}}
|
||||||
|
<br>
|
||||||
|
{{#if (eq currentEntity 'metrics')}}
|
||||||
|
{{entity.group}} - {{entity.dashboardName}}
|
||||||
|
<br>
|
||||||
|
{{entity.description}}
|
||||||
|
{{/if}}
|
||||||
|
|
||||||
|
{{#if (eq currentEntity 'datasests')}}
|
||||||
|
{{dataset-owner-list owners=entity.owners datasetName=entity.name}}
|
||||||
|
{{/if}}
|
||||||
|
|
||||||
|
{{#if entity.formatedModified}}
|
||||||
|
<span>Last Modified:</span>
|
||||||
|
|
||||||
|
<span title="{{entity.formatedModified}}">
|
||||||
|
{{moment-from-now entity.formatedModified }}
|
||||||
|
</span>
|
||||||
|
{{/if}}
|
||||||
|
{{/row.cell}}
|
||||||
|
{{#row.cell}}
|
||||||
|
{{datasets/dataset-actions actionItems=actionItems}}
|
||||||
|
{{/row.cell}}
|
||||||
|
{{/body.row}}
|
||||||
|
{{/each}}
|
||||||
|
{{/table.body}}
|
||||||
|
{{/dataset-table}}
|
||||||
|
{{/if}}
|
||||||
|
|||||||
@ -1,238 +1,228 @@
|
|||||||
<div id="metric" class="container-fluid">
|
<div class="container-fluid">
|
||||||
<div class="row-fluid">
|
<div class="row">
|
||||||
<div class="col-xs-6">
|
<div class="col-xs-6">
|
||||||
<h3>{{ model.name }}</h3>
|
<h3>{{ model.name }}</h3>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="col-xs-6 text-right">
|
<div class="col-xs-6 text-right">
|
||||||
<ul class="datasetDetailsLinks">
|
<ul class="datasetDetailsLinks">
|
||||||
<li>
|
<li>
|
||||||
<i class="fa fa-share-alt"></i>
|
<i class="fa fa-share-alt"></i>
|
||||||
<span class="hidden-sm hidden-xs">
|
<span class="hidden-sm hidden-xs">Share</span>
|
||||||
Share
|
|
||||||
</span>
|
|
||||||
</li>
|
</li>
|
||||||
|
|
||||||
<li>
|
<li>
|
||||||
{{#metric-watch metric=model showText=true getMetrics='getMetrics'}}
|
{{metric-watch metric=model showText=true}}
|
||||||
{{/metric-watch}}
|
|
||||||
</li>
|
</li>
|
||||||
|
|
||||||
{{#if showLineage}}
|
{{#if showLineage}}
|
||||||
<li>
|
<li>
|
||||||
<a target="_blank" href={{lineageUrl}}>
|
<a target="_blank" href={{lineageUrl}}>
|
||||||
<i class="fa fa-sitemap"></i>
|
<i class="fa fa-sitemap" aria-label="View Lineage"></i>
|
||||||
<span class="hidden-sm hidden-xs">
|
<span class="hidden-sm hidden-xs">View Lineage</span>
|
||||||
View Lineage
|
|
||||||
</span>
|
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
{{/if}}
|
{{/if}}
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="col-xs-12">
|
<div class="col-xs-12">
|
||||||
Metric Description:
|
Metric Description:
|
||||||
<a
|
<a
|
||||||
href="#"
|
href="#"
|
||||||
data-name="description"
|
data-name="description"
|
||||||
data-pk="{{model.id}}"
|
data-pk="{{model.id}}"
|
||||||
class="xeditable"
|
class="xeditable"
|
||||||
data-type="text"
|
data-type="text"
|
||||||
data-placement="right"
|
data-placement="right"
|
||||||
data-title="Enter description"
|
data-title="Enter description"
|
||||||
data-emptytext="Please Input"
|
data-emptytext="Please Input"
|
||||||
data-placeholder="Please Input"
|
data-placeholder="Please Input">
|
||||||
>
|
|
||||||
{{model.description}}
|
{{model.description}}
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<table class="tree table table-bordered">
|
|
||||||
|
<table class="nacho-table nacho-table--bordered nacho-table--dynamic">
|
||||||
<tbody>
|
<tbody>
|
||||||
<tr class="result">
|
<tr class="result">
|
||||||
<td class="span2" style="min-width:200px;">Dashboard Name</td>
|
<td class="span2" style="min-width:200px;">Dashboard Name</td>
|
||||||
<td>
|
<td>
|
||||||
<a
|
<a
|
||||||
href="#"
|
href="#"
|
||||||
data-name="dashboardName"
|
data-name="dashboardName"
|
||||||
data-pk="{{model.id}}"
|
data-pk="{{model.id}}"
|
||||||
class="xeditable"
|
class="xeditable"
|
||||||
data-type="text"
|
data-type="text"
|
||||||
data-placement="right"
|
data-placement="right"
|
||||||
data-title="Enter dashboard name"
|
data-title="Enter dashboard name"
|
||||||
data-defaultValue="Please Input"
|
data-defaultValue="Please Input"
|
||||||
data-emptytext="Please Input"
|
data-emptytext="Please Input"
|
||||||
data-value="{{model.dashboardName}}"
|
data-value="{{model.dashboardName}}">
|
||||||
>
|
|
||||||
{{model.dashboardName}}
|
{{model.dashboardName}}
|
||||||
</a>
|
</a>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
|
||||||
<tr class="result">
|
<tr class="result">
|
||||||
<td>Metric Category</td>
|
<td>Metric Category</td>
|
||||||
<td>
|
<td>
|
||||||
<a
|
<a
|
||||||
href="#"
|
href="#"
|
||||||
data-name="category"
|
data-name="category"
|
||||||
data-pk="{{model.id}}"
|
data-pk="{{model.id}}"
|
||||||
class="xeditable"
|
class="xeditable"
|
||||||
data-type="text"
|
data-type="text"
|
||||||
data-placement="right"
|
data-placement="right"
|
||||||
data-title="Enter metric category"
|
data-title="Enter metric category"
|
||||||
data-placement="right"
|
data-emptytext="Please Input">
|
||||||
data-emptytext="Please Input"
|
|
||||||
>
|
|
||||||
{{model.category}}
|
{{model.category}}
|
||||||
</a>
|
</a>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
|
||||||
<tr class="result">
|
<tr class="result">
|
||||||
<td>Metric Group</td>
|
<td>Metric Group</td>
|
||||||
<td>
|
<td>
|
||||||
<a
|
<a
|
||||||
href="#"
|
href="#"
|
||||||
data-name="group"
|
data-name="group"
|
||||||
data-pk="{{model.id}}"
|
data-pk="{{model.id}}"
|
||||||
class="xeditable"
|
class="xeditable"
|
||||||
data-type="text"
|
data-type="text"
|
||||||
data-placement="right"
|
data-placement="right"
|
||||||
data-title="Enter group"
|
data-title="Enter group"
|
||||||
data-placement="right"
|
data-emptytext="Please Input">
|
||||||
data-emptytext="Please Input"
|
|
||||||
>
|
|
||||||
{{model.group}}
|
{{model.group}}
|
||||||
</a>
|
</a>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
|
||||||
<tr class="result">
|
<tr class="result">
|
||||||
<td>Metric Type</td>
|
<td>Metric Type</td>
|
||||||
<td>
|
<td>
|
||||||
<a
|
<a
|
||||||
href="#"
|
href="#"
|
||||||
data-name="refIDType"
|
data-name="refIDType"
|
||||||
data-pk="{{model.id}}"
|
data-pk="{{model.id}}"
|
||||||
class="xeditable"
|
class="xeditable"
|
||||||
data-type="text"
|
data-type="text"
|
||||||
data-placement="right"
|
data-placement="right"
|
||||||
data-title="Enter Type"
|
data-title="Enter Type"
|
||||||
data-placement="right"
|
data-emptytext="Please Input"
|
||||||
data-emptytext="Please Input"
|
data-value={{model.refIDType}}>
|
||||||
data-value={{model.refIDType}}
|
|
||||||
>
|
|
||||||
</a>
|
</a>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
|
||||||
<tr class="result">
|
<tr class="result">
|
||||||
<td>Metric Grain</td>
|
<td>Metric Grain</td>
|
||||||
<td>
|
<td>
|
||||||
<a
|
<a
|
||||||
href="#"
|
href="#"
|
||||||
data-name="grain"
|
data-name="grain"
|
||||||
data-pk="{{model.id}}"
|
data-pk="{{model.id}}"
|
||||||
class="xeditable"
|
class="xeditable"
|
||||||
data-type="text"
|
data-type="text"
|
||||||
data-placement="right"
|
data-placement="right"
|
||||||
data-title="Enter grain"
|
data-title="Enter grain"
|
||||||
data-placement="right"
|
data-emptytext="Please Input">
|
||||||
data-emptytext="Please Input"
|
|
||||||
>
|
|
||||||
{{model.grain}}
|
{{model.grain}}
|
||||||
</a>
|
</a>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
|
||||||
<tr class="result">
|
<tr class="result">
|
||||||
<td>Metric Formula</td>
|
<td>Metric Formula</td>
|
||||||
<td>
|
<td>
|
||||||
{{ace-editor content=model.formula itemId=model.id savePath="/api/v1/metrics/{id}/update" saveParam="formula"}}
|
{{ace-editor content=model.formula itemId=model.id savePath="/api/v1/metrics/{id}/update" saveParam="formula"}}
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
|
||||||
<tr class="result">
|
<tr class="result">
|
||||||
<td>Metric Display Factor</td>
|
<td>Metric Display Factor</td>
|
||||||
<td>
|
<td>
|
||||||
<a
|
<a
|
||||||
href="#"
|
href="#"
|
||||||
data-name="displayFactory"
|
data-name="displayFactory"
|
||||||
data-pk="{{model.id}}"
|
data-pk="{{model.id}}"
|
||||||
class="xeditable"
|
class="xeditable"
|
||||||
data-type="text"
|
data-type="text"
|
||||||
data-placement="right"
|
data-placement="right"
|
||||||
data-title="Enter display factor"
|
data-title="Enter display factor"
|
||||||
data-placement="right"
|
data-emptytext="Please Input">
|
||||||
data-emptytext="Please Input"
|
|
||||||
>
|
|
||||||
{{model.displayFactory}}
|
{{model.displayFactory}}
|
||||||
</a>
|
</a>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
|
||||||
<tr class="result">
|
<tr class="result">
|
||||||
<td>Metric Display Factor Sym</td>
|
<td>Metric Display Factor Sym</td>
|
||||||
<td>
|
<td>
|
||||||
<a
|
<a
|
||||||
href="#"
|
href="#"
|
||||||
data-name="displayFactorSym"
|
data-name="displayFactorSym"
|
||||||
data-pk="{{model.id}}"
|
data-pk="{{model.id}}"
|
||||||
class="xeditable"
|
class="xeditable"
|
||||||
data-type="text"
|
data-type="text"
|
||||||
data-placement="right"
|
data-placement="right"
|
||||||
data-title="Enter display factor symbol"
|
data-title="Enter display factor symbol"
|
||||||
data-placement="right"
|
data-emptytext="Please Input">
|
||||||
data-emptytext="Please Input"
|
|
||||||
>
|
|
||||||
{{model.displayFactorSym}}
|
{{model.displayFactorSym}}
|
||||||
</a>
|
</a>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
|
||||||
<tr class="result">
|
<tr class="result">
|
||||||
<td>Metric Sub Category</td>
|
<td>Metric Sub Category</td>
|
||||||
<td>
|
<td>
|
||||||
<a
|
<a
|
||||||
href="#"
|
href="#"
|
||||||
data-name="subCategory"
|
data-name="subCategory"
|
||||||
data-pk="{{model.id}}"
|
data-pk="{{model.id}}"
|
||||||
class="xeditable"
|
class="xeditable"
|
||||||
data-type="text"
|
data-type="text"
|
||||||
data-placement="right"
|
data-placement="right"
|
||||||
data-title="Enter sub category"
|
data-title="Enter sub category"
|
||||||
data-placement="right"
|
data-emptytext="Please Input">
|
||||||
data-emptytext="Please Input"
|
|
||||||
>
|
|
||||||
{{model.subCategory}}
|
{{model.subCategory}}
|
||||||
</a>
|
</a>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
|
||||||
<tr class="result">
|
<tr class="result">
|
||||||
<td>Metric Source</td>
|
<td>Metric Source</td>
|
||||||
<td>
|
<td>
|
||||||
<a
|
<a
|
||||||
href="#"
|
href="#"
|
||||||
data-name="source"
|
data-name="source"
|
||||||
data-pk="{{model.id}}"
|
data-pk="{{model.id}}"
|
||||||
class="xeditable"
|
class="xeditable"
|
||||||
data-type="text"
|
data-type="text"
|
||||||
data-placement="right"
|
data-placement="right"
|
||||||
data-title="Enter source"
|
data-title="Enter source"
|
||||||
data-placement="right"
|
data-emptytext="Please Input"
|
||||||
data-emptytext="Please Input"
|
data-value={{model.source}}>
|
||||||
data-value={{model.source}}
|
|
||||||
>
|
|
||||||
</a>
|
</a>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
|
||||||
<tr class="result">
|
<tr class="result">
|
||||||
<td>Metric Source Type</td>
|
<td>Metric Source Type</td>
|
||||||
<td>
|
<td>
|
||||||
<a
|
<a
|
||||||
href="#"
|
href="#"
|
||||||
data-name="sourceType"
|
data-name="sourceType"
|
||||||
data-pk="{{model.id}}"
|
data-pk="{{model.id}}"
|
||||||
class="xeditable"
|
class="xeditable"
|
||||||
data-type="text"
|
data-type="text"
|
||||||
data-placement="right"
|
data-placement="right"
|
||||||
data-title="Enter source type"
|
data-title="Enter source type"
|
||||||
data-placement="right"
|
data-emptytext="Please Input"
|
||||||
data-emptytext="Please Input"
|
data-value={{model.sourceType}}>
|
||||||
data-value={{model.sourceType}}
|
|
||||||
>
|
|
||||||
</a>
|
</a>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -69,6 +69,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{{dataset-owner-list owners=owners datasetName=model.name}}
|
{{dataset-owner-list owners=owners datasetName=model.name}}
|
||||||
|
|
||||||
{{#if hasinstances}}
|
{{#if hasinstances}}
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<span class="col-xs-1">Instances:</span>
|
<span class="col-xs-1">Instances:</span>
|
||||||
@ -91,6 +92,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{{/if}}
|
{{/if}}
|
||||||
|
|
||||||
{{#if hasversions}}
|
{{#if hasversions}}
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<span class="col-xs-1">Versions:</span>
|
<span class="col-xs-1">Versions:</span>
|
||||||
@ -114,6 +116,7 @@
|
|||||||
</div>
|
</div>
|
||||||
{{/if}}
|
{{/if}}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<ul class="nav nav-tabs nav-justified tabbed-navigation-list">
|
<ul class="nav nav-tabs nav-justified tabbed-navigation-list">
|
||||||
{{#unless isPinot}}
|
{{#unless isPinot}}
|
||||||
<li id="properties">
|
<li id="properties">
|
||||||
@ -168,6 +171,7 @@
|
|||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
<div class="tab-content">
|
<div class="tab-content">
|
||||||
{{#unless isPinot}}
|
{{#unless isPinot}}
|
||||||
<div id="propertiestab" class="tab-pane">
|
<div id="propertiestab" class="tab-pane">
|
||||||
|
|||||||
@ -85,11 +85,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{{else}}
|
{{else}}
|
||||||
<div id="pagedJobs">
|
<div class="row">
|
||||||
<div class="row">
|
{{outlet}}
|
||||||
<div class="col-xs-12">
|
|
||||||
{{outlet}}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
{{/if}}
|
{{/if}}
|
||||||
|
|||||||
@ -1 +1,51 @@
|
|||||||
|
<div class="container">
|
||||||
|
{{!--TODO: Make into Component--}}
|
||||||
|
<ul class="nacho-breadcrumbs">
|
||||||
|
{{#each breadcrumbs as |crumb|}}
|
||||||
|
<li class="nacho-breadcrumbs__crumb">
|
||||||
|
{{link-to crumb.crumb "browse.entity" "flows" (query-params page=1 urn=crumb.urn name=crumb.name)
|
||||||
|
class="nacho-breadcrumbs__crumb__grain"}}
|
||||||
|
</li>
|
||||||
|
{{/each}}
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
{{#dataset-table
|
||||||
|
fields=model.data.jobs as |table|}}
|
||||||
|
{{#table.head as |head|}}
|
||||||
|
{{#head.column columnName="name"}}Job Path{{/head.column}}
|
||||||
|
{{#head.column columnName="type"}}Job Type{{/head.column}}
|
||||||
|
{{#head.column}}Creation Time{{/head.column}}
|
||||||
|
{{#head.column}}Modified Time{{/head.column}}
|
||||||
|
{{/table.head}}
|
||||||
|
|
||||||
|
{{#table.body as |body|}}
|
||||||
|
{{#each
|
||||||
|
(sort-by table.sortBy table.data) as |job|}}
|
||||||
|
{{#body.row as |row|}}
|
||||||
|
{{#row.cell}}
|
||||||
|
{{#if job.refFlowId}}
|
||||||
|
{{#link-to 'flows.flow' job.refFlowId (query-params name=model.data.name)}}
|
||||||
|
{{job.name}}
|
||||||
|
{{/link-to}}
|
||||||
|
{{else}}
|
||||||
|
{{job.name}}
|
||||||
|
{{/if}}
|
||||||
|
{{/row.cell}}
|
||||||
|
|
||||||
|
{{#row.cell}}
|
||||||
|
{{job.type}}
|
||||||
|
{{/row.cell}}
|
||||||
|
|
||||||
|
{{#row.cell}}
|
||||||
|
{{moment-calendar job.created sameElse="MMM Do YYYY, h:mm a"}}
|
||||||
|
{{/row.cell}}
|
||||||
|
|
||||||
|
{{#row.cell}}
|
||||||
|
{{moment-calendar job.modified sameElse="MMM Do YYYY, h:mm a"}}
|
||||||
|
{{/row.cell}}
|
||||||
|
{{/body.row}}
|
||||||
|
{{/each}}
|
||||||
|
{{/table.body}}
|
||||||
|
{{/dataset-table}}
|
||||||
|
</div>
|
||||||
{{outlet}}
|
{{outlet}}
|
||||||
|
|||||||
@ -1 +1,13 @@
|
|||||||
{{outlet}}
|
<div class="container">
|
||||||
|
{{!--TODO: Make into Component--}}
|
||||||
|
<ul class="nacho-breadcrumbs">
|
||||||
|
{{#each breadcrumbs as |crumb|}}
|
||||||
|
<li class="nacho-breadcrumbs__crumb">
|
||||||
|
{{link-to crumb.crumb "browse.entity" "metrics" (query-params page=1 name=crumb.name)
|
||||||
|
class="nacho-breadcrumbs__crumb__grain"}}
|
||||||
|
</li>
|
||||||
|
{{/each}}
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
{{metric-detail showLineage=showLineage model=model}}
|
||||||
|
</div>
|
||||||
|
|||||||
@ -5,10 +5,16 @@
|
|||||||
* @type {RegExp}
|
* @type {RegExp}
|
||||||
*/
|
*/
|
||||||
const urnRegex = /([a-z_]+):\/{3}([a-z0-9_\-\/\{\}]*)/i;
|
const urnRegex = /([a-z_]+):\/{3}([a-z0-9_\-\/\{\}]*)/i;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Matches urn's that occur in flow urls
|
||||||
|
* @type {RegExp}
|
||||||
|
*/
|
||||||
|
const specialFlowUrnRegex = /(?:\?urn=)([a-z0-9_\-\/{}\s]+)/i;
|
||||||
/**
|
/**
|
||||||
* Asserts that a provided string matches the urn pattern above
|
* Asserts that a provided string matches the urn pattern above
|
||||||
* @param {String} candidateUrn the string to test on
|
* @param {String} candidateUrn the string to test on
|
||||||
*/
|
*/
|
||||||
export default candidateUrn => urnRegex.test(String(candidateUrn));
|
export default candidateUrn => urnRegex.test(String(candidateUrn));
|
||||||
|
|
||||||
export { urnRegex };
|
export { urnRegex, specialFlowUrnRegex };
|
||||||
|
|||||||
@ -96,6 +96,9 @@ module.exports = function(defaults) {
|
|||||||
app.import(
|
app.import(
|
||||||
'bower_components/jsondiffpatch/public/formatters-styles/annotated.css'
|
'bower_components/jsondiffpatch/public/formatters-styles/annotated.css'
|
||||||
);
|
);
|
||||||
|
app.import(
|
||||||
|
'bower_components/x-editable/dist/bootstrap3-editable/css/bootstrap-editable.css'
|
||||||
|
);
|
||||||
app.import('vendor/legacy_styles/main.css');
|
app.import('vendor/legacy_styles/main.css');
|
||||||
app.import('vendor/legacy_styles/comments.css');
|
app.import('vendor/legacy_styles/comments.css');
|
||||||
app.import('vendor/legacy_styles/wherehows.css');
|
app.import('vendor/legacy_styles/wherehows.css');
|
||||||
@ -129,9 +132,9 @@ module.exports = function(defaults) {
|
|||||||
app.import('vendor/CsvToMarkdown.js');
|
app.import('vendor/CsvToMarkdown.js');
|
||||||
app.import('vendor/typeahead.jquery.js');
|
app.import('vendor/typeahead.jquery.js');
|
||||||
app.import('bower_components/marked/marked.min.js');
|
app.import('bower_components/marked/marked.min.js');
|
||||||
app.import('bower_components/ace-builds/src-min/ace.js');
|
app.import('bower_components/ace-builds/src-noconflict/ace.js');
|
||||||
app.import('bower_components/ace-builds/src-min/theme-github.js');
|
app.import('bower_components/ace-builds/src-noconflict/theme-github.js');
|
||||||
app.import('bower_components/ace-builds/src-min/mode-sql.js');
|
app.import('bower_components/ace-builds/src-noconflict/mode-sql.js');
|
||||||
app.import('bower_components/toastr/toastr.min.js');
|
app.import('bower_components/toastr/toastr.min.js');
|
||||||
app.import('bower_components/highcharts/highcharts.js');
|
app.import('bower_components/highcharts/highcharts.js');
|
||||||
app.import(
|
app.import(
|
||||||
@ -140,6 +143,9 @@ module.exports = function(defaults) {
|
|||||||
app.import(
|
app.import(
|
||||||
'bower_components/jsondiffpatch/public/build/jsondiffpatch-formatters.min.js'
|
'bower_components/jsondiffpatch/public/build/jsondiffpatch-formatters.min.js'
|
||||||
);
|
);
|
||||||
|
app.import(
|
||||||
|
'bower_components/x-editable/dist/bootstrap3-editable/js/bootstrap-editable.min.js'
|
||||||
|
);
|
||||||
|
|
||||||
return app.toTree(new MergeTrees([faFontTree, bsFontTree, treegridImgTree]));
|
return app.toTree(new MergeTrees([faFontTree, bsFontTree, treegridImgTree]));
|
||||||
};
|
};
|
||||||
|
|||||||
12
wherehows-web/tests/unit/controllers/flows/flow-test.js
Normal file
12
wherehows-web/tests/unit/controllers/flows/flow-test.js
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
import { moduleFor, test } from 'ember-qunit';
|
||||||
|
|
||||||
|
moduleFor('controller:flows/flow', 'Unit | Controller | flows/flow', {
|
||||||
|
// Specify the other units that are required for this test.
|
||||||
|
// needs: ['controller:foo']
|
||||||
|
});
|
||||||
|
|
||||||
|
// Replace this with your real tests.
|
||||||
|
test('it exists', function(assert) {
|
||||||
|
let controller = this.subject();
|
||||||
|
assert.ok(controller);
|
||||||
|
});
|
||||||
Loading…
x
Reference in New Issue
Block a user