Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 2 additions & 44 deletions frontend/.eslintrc.json
Original file line number Diff line number Diff line change
@@ -1,48 +1,6 @@
{
"parser": "babel-eslint",
"extends": ["standard", "standard-react"],
"plugins": [
"import",
"flowtype",
"babel"
],
"env": {
"jest": true,
"browser": true
},
"globals": {
"$Keys": true
},
"extends": "react-app",
"rules": {
"curly": [2, "multi", "consistent"],
"space-before-function-paren": 0, // It's ok to write both function(a) and function (a)
"no-multiple-empty-lines": 0, // It's ok to have two blank lines (e.g. after import)
"no-multi-spaces": 0, // It's ok to align code vertically and have more spaces
"quotes": 0, // Quotes don't have to be strictly single or double
"semi": 0, // Semicolons are fine
"comma-dangle": 0, // May be there, helps better diffs and easier maintainability
"indent": 0, // We indent how we like. (sometimes a custom indentation is great)
"jsx-quotes": [2, "prefer-double"],
"max-len": [2, 100],
"no-console": 2,
"key-spacing": 0, // It's ok to have spacing and align object key value pairs
"react/jsx-no-bind": 0, // We allow .bind(this) in JSX - aware of performance issues
"react/jsx-indent-props": 0, // We indent how we like. (sometimes a custom indentation is great)
"react/jsx-indent": 0, // We indent how we like. (sometimes a custom indentation is great)
"react/jsx-boolean-value": 0,
"react/no-unused-prop-types": 0, // Too many false positives ( https://github.com/yannickcr/eslint-plugin-react/issues/976 )
"standard/object-curly-even-spacing": 0,
"import/no-duplicates": 2,
"import/no-unresolved": [2, { "commonjs": true, "amd": true }],
"import/default": 2,
"import/no-absolute-path": 2,
"import/no-dynamic-require": 2,
"import/export": 2,
"import/no-mutable-exports": 2,
"import/first": 2,
"import/newline-after-import": 2,
"import/order": [2, {"newlines-between": "ignore"}],
"flowtype/boolean-style": [2, "boolean"],
"flowtype/no-weak-types": [2, { "Function": false, "Object": false, "any": false }]
"jsx-a11y/accessible-emoji": "off"
}
}
183 changes: 93 additions & 90 deletions frontend/lib/js/api/apiHelper.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,9 @@
// DAYS_OR_NO_EVENT_BEFORE,
// } from '../common/constants/timebasedQueryOperatorTypes';

import {
isEmpty
} from '../common/helpers';
import { isEmpty } from "../common/helpers";

import { type TableType } from '../standard-query-editor/types';
import { type TableType } from "../standard-query-editor/types";

export const transformTablesToApi = (tables: TableType[]) => {
if (!tables) return [];
Expand All @@ -30,90 +28,93 @@ export const transformTablesToApi = (tables: TableType[]) => {
id: table.connectorId,
filters: table.filters
? table.filters
.filter(filter => !isEmpty(filter.value)) // Only send filters with a value
.map(filter => ({
filter: filter.id,
type: filter.type,
value: filter.value instanceof Array
? filter.value.map(v => v.value ? v.value : v)
: filter.value
}))
.filter(filter => !isEmpty(filter.value)) // Only send filters with a value
.map(filter => ({
filter: filter.id,
type: filter.type,
value:
filter.value instanceof Array
? filter.value.map(v => (v.value ? v.value : v))
: filter.value
}))
: []
};
});
}

export const transformElementGroupsToApi = (elementGroups) => elementGroups.map(elements => ({
matchingType: elements.matchingType,
elements: transformElementsToApi(elements.concepts)
}));

export const transformElementsToApi = (conceptGroup) => conceptGroup.map(concept => {
const tables = concept.tables
? transformTablesToApi(concept.tables)
: [];

return {
ids: concept.ids,
type: 'CONCEPT_LIST',
label: concept.label,
tables,
};
});

const transformStandardQueryToApi = (query) =>
createConceptQuery(createQueryConcepts(query))
};
});
};

const createConceptQuery = (children) => ({
type: 'CONCEPT_QUERY',
export const transformElementGroupsToApi = elementGroups =>
elementGroups.map(elements => ({
matchingType: elements.matchingType,
elements: transformElementsToApi(elements.concepts)
}));

export const transformElementsToApi = conceptGroup =>
conceptGroup.map(concept => {
const tables = concept.tables ? transformTablesToApi(concept.tables) : [];

return {
ids: concept.ids,
type: "CONCEPT_LIST",
label: concept.label,
tables
};
});

const transformStandardQueryToApi = query =>
createConceptQuery(createQueryConcepts(query));

const createConceptQuery = children => ({
type: "CONCEPT_QUERY",
root: {
type: "AND",
children: children
}
})
});

const createNegation = (group) => ({
type: "NEGATION",
child: group
})
const createNegation = group => ({
type: "NEGATION",
child: group
});

const createDateRestriction = (dateRange, concept) => ({
type: "DATE_RESTRICTION",
dateRange: dateRange,
child: concept
})
type: "DATE_RESTRICTION",
dateRange: dateRange,
child: concept
});

const createSavedQuery = (conceptId) => ({
type: 'SAVED_QUERY',
query: conceptId,
})
const createSavedQuery = conceptId => ({
type: "SAVED_QUERY",
query: conceptId
});

const createQueryConcept = (concept) =>
const createQueryConcept = concept =>
concept.isPreviousQuery
? createSavedQuery(concept.id)
: createConcept(concept)
: createConcept(concept);

const createConcept = (concept) => ({
type: 'CONCEPT',
const createConcept = concept => ({
type: "CONCEPT",
ids: concept.ids,
label: concept.label,
tables: transformTablesToApi(concept.tables)
})
});

const createQueryConcepts = (query) => {
const createQueryConcepts = query => {
return query.map(group => {
const concepts = group.dateRange
? group.elements.map(concept =>
createDateRestriction(group.dateRange, createQueryConcept(concept)))
: group.elements.map(concept => createQueryConcept(concept))
createDateRestriction(group.dateRange, createQueryConcept(concept))
)
: group.elements.map(concept => createQueryConcept(concept));

var result = group.elements.length > 1
? { type: "OR", children: [...concepts]}
: concepts.reduce((acc, curr) => ({ ...acc, ...curr }), {});
var result =
group.elements.length > 1
? { type: "OR", children: [...concepts] }
: concepts.reduce((acc, curr) => ({ ...acc, ...curr }), {});

return group.exclude ? createNegation(result) : result;
})
}
});
};

// TODO: Use, once feature is complete
// const getDayRange = (condition) => {
Expand All @@ -132,42 +133,44 @@ const createQueryConcepts = (query) => {
// return [{}, {}];
// };

const transformTimebasedQueryToApi = (query) => ({
type: "CONCEPT_QUERY",
root: {
type: "AND",
children: query.conditions.map(condition => {
// TODO: Use, once feature is complete
// const [ minDays, maxDays ] = getDayRange(condition);
return {
type: condition.operator,
sampler: "EARLIEST",
index: createSavedQuery(condition.result0.id),
preceding: createSavedQuery(condition.result1.id)
}
})
}
const transformTimebasedQueryToApi = query => ({
type: "CONCEPT_QUERY",
root: {
type: "AND",
children: query.conditions.map(condition => {
// TODO: Use, once feature is complete
// const [ minDays, maxDays ] = getDayRange(condition);
return {
type: condition.operator,
sampler: "EARLIEST",
index: createSavedQuery(condition.result0.id),
preceding: createSavedQuery(condition.result1.id)
};
})
}
});

const transformExternalQueryToApi = (query) => createConceptQuery(createExternal(query))
const transformExternalQueryToApi = query =>
createConceptQuery(createExternal(query));

const createExternal = (query: any) => ({
type: "EXTERNAL",
format: query.data[0],
values: [query.data.slice(1)],
})

values: [query.data.slice(1)]
});

// The query state already contains the query.
// But small additions are made (properties whitelisted), empty things filtered out
// to make it compatible with the backend API
export const transformQueryToApi = (query: Object, queryType: string) => {
switch (queryType) {
case 'timebased':
return transformTimebasedQueryToApi(query)
case 'standard':
return transformStandardQueryToApi(query);
case 'external':
return transformExternalQueryToApi(query);
}
case "timebased":
return transformTimebasedQueryToApi(query);
case "standard":
return transformStandardQueryToApi(query);
case "external":
return transformExternalQueryToApi(query);
default:
return null;
}
};
39 changes: 22 additions & 17 deletions frontend/lib/js/common/helpers/dateHelper.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,48 +3,53 @@
import moment from "moment";

export const formatDate = (dateString: String) => {
return moment(dateString).format(moment.localeData().longDateFormat("L"))
return moment(dateString).format(moment.localeData().longDateFormat("L"));
};

export const duration = (value, units: string, format: string) => {
return moment.duration(value, units).format(format, {trim: false});
return moment.duration(value, units).format(format, { trim: false });
};

const DATE_PATTERN = {
year: /^\.(\d{4})$/,
quarter_year: /^[q]([1-4]).(\d{4})$/,
month_year: /^[m](1[0-2]|[1-9]).(\d{4})$/,
date: /^\d{8}$/
}
};

export const specificDatePattern = (value) => {
export const specificDatePattern = value => {
var minDate, maxDate, year, match;
switch (true) {
case DATE_PATTERN.year.test(value):
year = parseInt(DATE_PATTERN.year.exec(value)[1]);
minDate = moment([year, 0, 1]);
maxDate = moment([year, 11, 31]);
break;
break;
case DATE_PATTERN.quarter_year.test(value):
match = DATE_PATTERN.quarter_year.exec(value)
match = DATE_PATTERN.quarter_year.exec(value);
const quarter = parseInt(match[1]);
year = parseInt(match[2]);
minDate = moment([year, 0, 1]).quarter(quarter);
maxDate = moment([year, 0, 1]).quarter(quarter + 1).subtract(1, "day");
break;
maxDate = moment([year, 0, 1])
.quarter(quarter + 1)
.subtract(1, "day");
break;
case DATE_PATTERN.month_year.test(value):
match = DATE_PATTERN.month_year.exec(value)
match = DATE_PATTERN.month_year.exec(value);
const month = parseInt(match[1]);
year = parseInt(match[2]);
minDate = moment([year, 0, 1]).month(month - 1);
maxDate = moment([year, 0, 1]).month(month).subtract(1, "day");
break;
}
maxDate = moment([year, 0, 1])
.month(month)
.subtract(1, "day");
break;
default:
break;
}

return { minDate, maxDate }
}
return { minDate, maxDate };
};

export const parseDatePattern = (value, pattern) => {
if (value && DATE_PATTERN.date.test(value))
return moment(value, "DDMMYYYY")
}
if (value && DATE_PATTERN.date.test(value)) return moment(value, "DDMMYYYY");
};
17 changes: 7 additions & 10 deletions frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
"moment": "^2.17.0",
"moment-duration-format": "^2.2.2",
"mustache": "^2.3.0",
"prettier": "^1.16.4",
"query-string": "^5.1.0",
"react": "^16.8.1",
"react-addons-test-utils": "^15.3.1",
Expand Down Expand Up @@ -72,16 +73,12 @@
"cross-env": "^5.1.3",
"css-loader": "^2.1.0",
"decimal.js": "^10.0.1",
"eslint": "^4.18.1",
"eslint-config-standard": "^11.0.0",
"eslint-config-standard-react": "^6.0.0",
"eslint-plugin-babel": "^4.1.2",
"eslint-plugin-flowtype": "^2.6.0",
"eslint-plugin-import": "^2.9.0",
"eslint-plugin-node": "^6.0.0",
"eslint-plugin-promise": "^3.6.0",
"eslint-plugin-react": "^7.7.0",
"eslint-plugin-standard": "^3.0.1",
"eslint": "^5.13.0",
"eslint-config-react-app": "^3.0.7",
"eslint-plugin-flowtype": "^3.4.1",
"eslint-plugin-import": "^2.16.0",
"eslint-plugin-jsx-a11y": "^6.2.1",
"eslint-plugin-react": "^7.12.4",
"expect": "^22.4.0",
"express": "^4.16.2",
"file-loader": "^3.0.1",
Expand Down
Loading