diff --git a/dist/cdc/CDCFilterDialog.d.ts b/dist/cdc/CDCFilterDialog.d.ts new file mode 100644 index 000000000..46fd1cbf7 --- /dev/null +++ b/dist/cdc/CDCFilterDialog.d.ts @@ -0,0 +1,15 @@ +/// +import { IUploadAlert, ICDCConfiguration } from './interfaces'; +interface ICDCFilterDialogProps { + cdcConfig: { + [cdcId: string]: ICDCConfiguration; + }; +} +export declare const CDC_DEFAULT_ALERT_DATA: () => IUploadAlert; +export declare function CDCFilterDialog({ cdcConfig }: ICDCFilterDialogProps): JSX.Element; +export declare class CDCFilterDialogClass { + private node; + constructor(parent: HTMLElement); + private init; +} +export {}; diff --git a/dist/cdc/CDCFilterDialog.js b/dist/cdc/CDCFilterDialog.js new file mode 100644 index 000000000..50fd9cb2f --- /dev/null +++ b/dist/cdc/CDCFilterDialog.js @@ -0,0 +1,155 @@ +import * as React from 'react'; +import ReactDOM from 'react-dom'; +import { BSModal, useAsync } from '../hooks'; +import { getAlerts, runAllAlerts } from './api'; +import { CDCGroupingFilterId, CDCGroupingFilter, createCDCGroupingFilter, CDCCheckboxFilter, CDCCheckboxFilterId, createCDCCheckboxFilter, CDCRangeFilter, CDCRangeFilterId, createCDCRangeFilter } from './filter'; +import { v4 as uuidv4 } from 'uuid'; +import { CDCTextFilter, CDCTextFilterId, createCDCTextFilter } from './filter/CDCTextFilter'; +import { CDCAlertView } from './alert/CDCAlertView'; +import { ErrorMessage } from './common'; +export const CDC_DEFAULT_ALERT_DATA = () => ({ name: '', enable_mail_notification: false, cdc_id: 'JSONPlaceholderUserCDC', filter: createCDCGroupingFilter(uuidv4()), compare_columns: null }); +export function CDCFilterDialog({ cdcConfig }) { + const [selectedAlert, setSelectedAlert] = React.useState(null); + const [creationMode, setCreationMode] = React.useState(false); + const [alertData, setAlertData] = React.useState(null); + const [dialogNode, setDialogNode] = React.useState(null); + const { status: alertStatus, error: alertError, execute: fetchAlerts, value: alerts } = useAsync(getAlerts, []); + const onAlertChanged = async (id) => { + //refetches alerts and makes new selection + fetchAlerts().then((alerts) => { + //if no id there is no need to iterate through alerts + if (!id) { + setSelectedAlert(null); + } + else { + setSelectedAlert(alerts.find((alert) => alert.id === id)); + } + }); + }; + const { status: syncStatus, error: syncError, execute: doSync } = useAsync(async () => { + const result = await runAllAlerts(); + if (result.error.length > 0) { + throw new Error(`Alert(s) could not be synchronized!`); + } + onAlertChanged(selectedAlert === null || selectedAlert === void 0 ? void 0 : selectedAlert.id); + }); + const onCreateButtonClick = () => { + setCreationMode(true); + setSelectedAlert(null); + setAlertData(CDC_DEFAULT_ALERT_DATA()); + }; + const onAlertClick = async (alert) => { + setAlertData(alert); + setCreationMode(false); + setSelectedAlert(alert); + }; + const reviewStatus = (alert) => { + var _a, _b; + switch (true) { + case (alert.latest_error != null): + return `Error from Sync: ${(_a = new Date(alert.latest_error_date)) === null || _a === void 0 ? void 0 : _a.toLocaleDateString()}`; + case (alert.latest_diff != null): + return 'Pending data revision'; + case (alert.confirmation_date != null): + return `Last confirmed: ${(_b = new Date(alert.confirmation_date)) === null || _b === void 0 ? void 0 : _b.toLocaleDateString()}`; + default: + return 'No data revision yet'; + } + }; + const openDialog = () => { + const dialog = document.createElement('div'); + document.body.appendChild(dialog); + setDialogNode(dialog); + }; + return React.createElement(React.Fragment, null, + React.createElement("a", { style: { color: 'white', cursor: 'pointer' }, onClick: () => openDialog() }, + React.createElement("i", { className: "fas fa-filter" })), + dialogNode ? ReactDOM.createPortal(React.createElement(BSModal, { show: !!dialogNode, setShow: (show) => { + if (!show) { + setDialogNode(null); + dialogNode.remove(); + } + else if (!dialogNode) { + openDialog(); + } + } }, + React.createElement("div", { className: "modal fade", tabIndex: -1 }, + React.createElement("div", { className: "modal-dialog", style: { maxWidth: '90%' } }, + React.createElement("div", { className: "modal-content", style: { height: '90vh' } }, + React.createElement("div", { className: "modal-header" }, + React.createElement("h5", { className: "modal-title" }, "Alerts"), + React.createElement("button", { type: "button", className: "btn-close", "data-bs-dismiss": "modal", "aria-label": "Close" })), + React.createElement("div", { className: "modal-body overflow-auto container" }, + React.createElement("div", { className: "row h-100 overflow-auto" }, + React.createElement("div", { className: `col-3 overflow-auto h-100 position-relative ${alertStatus === 'pending' ? 'tdp-busy-overlay' : ''}` }, + syncError ? React.createElement(ErrorMessage, { error: syncError }) : null, + React.createElement("div", { className: "d-md-flex justify-content-md-end mb-1 mt-1" }, + React.createElement("button", { type: "button", disabled: syncStatus === 'pending', title: "Synchronize all alerts", className: "btn btn-text-secondary", onClick: () => doSync() }, + React.createElement("i", { className: `fas fa-sync ${syncStatus === 'pending' ? 'fa-spin' : ''}` })), + React.createElement("button", { className: "btn btn-text-secondary", onClick: () => onCreateButtonClick() }, + React.createElement("i", { className: "fas fa-plus" }))), + alertError ? React.createElement(ErrorMessage, { error: alertError, onRetry: () => fetchAlerts() }) : null, + alertStatus === 'success' ? React.createElement("div", { className: "list-group" }, alerts.sort((a, b) => (b.latest_diff ? 1 : 0) - (a.latest_diff ? 1 : 0)).map((alert) => React.createElement("div", { key: alert.id }, + React.createElement("a", { href: "#", className: `list-group-item list-group-item-action ${(selectedAlert === null || selectedAlert === void 0 ? void 0 : selectedAlert.id) === (alert === null || alert === void 0 ? void 0 : alert.id) ? 'border-primary' : ''}`, onClick: () => onAlertClick(alert), "aria-current": "true" }, + React.createElement("div", { className: "d-flex w-100 justify-content-between" }, + React.createElement("h6", { title: `${alert.name} for ${alert.cdc_id}`, className: "mb-1 overflow-hidden" }, + alert.name, + " ", + React.createElement("small", { className: "text-muted" }, + "for ", + alert.cdc_id)), + React.createElement("small", null, + (alert === null || alert === void 0 ? void 0 : alert.latest_error) ? React.createElement("i", { className: "fas fa-exclamation-triangle text-danger", title: alert.latest_error }) : null, + (alert === null || alert === void 0 ? void 0 : alert.latest_diff) && !(alert === null || alert === void 0 ? void 0 : alert.latest_error) ? React.createElement("i", { className: "fas fa-circle text-primary" }) : null)), + React.createElement("small", null, reviewStatus(alert)))))) : null), + React.createElement("div", { className: "col-9 overflow-auto h-100 position-relative d-flex flex-column" }, selectedAlert || creationMode ? + React.createElement(CDCAlertView, { alertData: alertData, setAlertData: setAlertData, onAlertChanged: onAlertChanged, setCreationMode: setCreationMode, selectedAlert: selectedAlert, creationMode: creationMode, cdcConfig: cdcConfig }) + : null))))))), dialogNode) : null); +} +export class CDCFilterDialogClass { + constructor(parent) { + this.node = document.createElement('div'); + parent.appendChild(this.node); + this.init(); + } + init() { + ReactDOM.render(React.createElement(CDCFilterDialog, { cdcConfig: { + 'JSONPlaceholderUserCDC': { + filters: [ + createCDCGroupingFilter(uuidv4()), + createCDCTextFilter(uuidv4(), 'Select...', null), + createCDCCheckboxFilter(uuidv4(), {}), + createCDCRangeFilter(uuidv4(), 'id', { min: 1, max: 10 }) + ], + components: { + [CDCGroupingFilterId]: { component: CDCGroupingFilter }, + [CDCTextFilterId]: { component: CDCTextFilter, config: [{ field: 'address.city', options: ['Gwenborough', 'Wisokyburgh', 'McKenziehaven', 'Roscoeview', 'Aliyaview', 'Howemouth'] }, { field: 'address.zipcode', options: ['33263', '23505-1337', '58804-1099'] }, { field: 'name', options: ['Leanne Graham', 'Ervin Howell', 'Glenna Reichert', 'Clementina DuBuque'] }] }, + [CDCCheckboxFilterId]: { component: CDCCheckboxFilter, config: { fields: ['Eins', 'Zwei', 'Drei'] } }, + [CDCRangeFilterId]: { component: CDCRangeFilter, config: { minValue: 1, maxValue: 10 } } + }, + compareColumns: ['id', 'name', 'address.street', 'address.city', 'address.zipcode'] + }, + 'JSONPlaceholderPostsCDC': { + filters: [ + createCDCGroupingFilter(uuidv4()), + createCDCRangeFilter(uuidv4(), 'id', { min: 1, max: 100 }) + ], + components: { + [CDCGroupingFilterId]: { component: CDCGroupingFilter }, + [CDCRangeFilterId]: { component: CDCRangeFilter, config: { minValue: 1, maxValue: 100 } } + }, + compareColumns: ['title', 'body'] + }, + 'CortellisTrialsCDC': { + filters: [ + createCDCGroupingFilter(uuidv4()) + ], + components: { + [CDCGroupingFilterId]: { component: CDCGroupingFilter }, + }, + compareColumns: ['phase', 'indications', 'enrollmentrount', 'numberofsites'] + } + } }), this.node); + } +} +//# sourceMappingURL=CDCFilterDialog.js.map \ No newline at end of file diff --git a/dist/cdc/CDCFilterDialog.js.map b/dist/cdc/CDCFilterDialog.js.map new file mode 100644 index 000000000..ca802840f --- /dev/null +++ b/dist/cdc/CDCFilterDialog.js.map @@ -0,0 +1 @@ +{"version":3,"file":"CDCFilterDialog.js","sourceRoot":"","sources":["../../src/cdc/CDCFilterDialog.tsx"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/dist/cdc/alert/CDCAlertView.d.ts b/dist/cdc/alert/CDCAlertView.d.ts new file mode 100644 index 000000000..4b69777b4 --- /dev/null +++ b/dist/cdc/alert/CDCAlertView.d.ts @@ -0,0 +1,15 @@ +/// +import { IAlert, IUploadAlert, ICDCConfiguration } from '../interfaces'; +interface ICDCEditAlert { + alertData: IUploadAlert; + setAlertData: (formData: IUploadAlert) => void; + onAlertChanged: (id?: number) => void; + selectedAlert?: IAlert; + creationMode: boolean; + setCreationMode: (mode: boolean) => void; + cdcConfig: { + [cdcId: string]: ICDCConfiguration; + }; +} +export declare function CDCAlertView({ alertData, setAlertData, onAlertChanged, selectedAlert, setCreationMode, creationMode, cdcConfig }: ICDCEditAlert): JSX.Element; +export {}; diff --git a/dist/cdc/alert/CDCAlertView.js b/dist/cdc/alert/CDCAlertView.js new file mode 100644 index 000000000..38c6b87b9 --- /dev/null +++ b/dist/cdc/alert/CDCAlertView.js @@ -0,0 +1,123 @@ +import React from 'react'; +import Select from 'react-select'; +import { ErrorMessage } from '../common'; +import { useAsync } from '../../hooks'; +import { deleteAlert, editAlert, saveAlert } from '../api'; +import { CDCFilterCreator } from '../creator'; +import { CDCDataChangeTable } from './CDCDataChangeTable'; +export function CDCAlertView({ alertData, setAlertData, onAlertChanged, selectedAlert, setCreationMode, creationMode, cdcConfig }) { + var _a, _b, _c, _d; + const [editMode, setEditMode] = React.useState(false); + const [deleteMode, setDeleteMode] = React.useState(false); + const [validFilter, setValidFilter] = React.useState(true); + const [validName, setValidName] = React.useState(true); + const [validCompareColumns, setValidCompareColumns] = React.useState(true); + const [activePage, setActivePage] = React.useState('info'); + const { status: deleteStatus, error: deleteError, execute: doDelete } = useAsync(async () => { + setEditMode(false); + await deleteAlert(selectedAlert.id); + onAlertChanged(); + }); + const { status: saveStatus, error: saveError, execute: doSave } = useAsync(async () => { + var _a, _b; + const valFilter = !!(alertData === null || alertData === void 0 ? void 0 : alertData.filter); + const valName = ((_a = alertData === null || alertData === void 0 ? void 0 : alertData.name) === null || _a === void 0 ? void 0 : _a.trim().length) > 0; + const valCompareColumns = ((_b = alertData === null || alertData === void 0 ? void 0 : alertData.compare_columns) === null || _b === void 0 ? void 0 : _b.length) > 0; + if (valFilter && valName && valCompareColumns) { + let newAlert; + if (selectedAlert) { + newAlert = await editAlert(selectedAlert.id, { ...alertData }); + setEditMode(false); + } + else { + newAlert = await saveAlert({ ...alertData }); + setCreationMode(false); + } + onAlertChanged(newAlert.id); + } + setValidFilter(valFilter); + setValidName(valName); + setValidCompareColumns(valCompareColumns); + }); + // TODO: CDCs are more complex than just filters, i.e. they also have fields. + const cdcs = Object.keys(cdcConfig); + const filterSelection = (_a = cdcConfig[alertData === null || alertData === void 0 ? void 0 : alertData.cdc_id]) === null || _a === void 0 ? void 0 : _a.filters; + const compareColumns = (_b = cdcConfig[alertData === null || alertData === void 0 ? void 0 : alertData.cdc_id]) === null || _b === void 0 ? void 0 : _b.compareColumns; + const filterComponents = (_c = cdcConfig[alertData === null || alertData === void 0 ? void 0 : alertData.cdc_id]) === null || _c === void 0 ? void 0 : _c.components; + React.useEffect(() => { + if (selectedAlert) { + setEditMode(false); + setDeleteMode(false); + setActivePage('info'); + } + }, [selectedAlert]); + const onDiscard = () => { + setEditMode(false); + setAlertData(selectedAlert); + }; + return (React.createElement("div", { className: `${saveStatus === 'pending' ? 'tdp-busy-overlay' : ''}` }, + (selectedAlert === null || selectedAlert === void 0 ? void 0 : selectedAlert.latest_error) ? + React.createElement(ErrorMessage, { error: new Error(`In the sync from ${new Date(selectedAlert.latest_error_date)} an error occured: ${selectedAlert.latest_error}`) }) + : deleteError ? + React.createElement(ErrorMessage, { error: new Error(`While deleting an error occured: ${deleteError}`) }) + : saveError ? + React.createElement(ErrorMessage, { error: new Error(`While saving an error occured: ${saveError}`) }) + : null, + React.createElement("ul", { className: "nav nav-tabs" }, + React.createElement("li", { className: "nav-item" }, + React.createElement("a", { className: `nav-link ${activePage === 'info' ? 'active' : ''}`, href: "#", onClick: (e) => { + e.preventDefault(); + setActivePage('info'); + } }, "Information")), + !editMode && !creationMode ? React.createElement("li", { className: "nav-item" }, + React.createElement("a", { className: `nav-link ${activePage === 'data' ? 'active' : ''}`, href: "#", onClick: (e) => { + e.preventDefault(); + setActivePage('data'); + } }, (selectedAlert === null || selectedAlert === void 0 ? void 0 : selectedAlert.latest_diff) ? 'Data revision from: ' + ((_d = new Date(selectedAlert.latest_compare_date)) === null || _d === void 0 ? void 0 : _d.toLocaleDateString()) : 'Data')) : null, + React.createElement("small", { className: "d-flex justify-content-end flex-grow-1" }, saveStatus === 'pending' || deleteStatus === 'pending' ? (React.createElement("i", { className: "fas fa-spinner fa-spin" })) : !editMode && !deleteMode && !creationMode ? (React.createElement(React.Fragment, null, + React.createElement("button", { title: "Edit Alert", className: "btn btn-text-secondary", onClick: () => setEditMode(true) }, + React.createElement("i", { className: "fas fa-pencil-alt" })), + React.createElement("button", { title: "Delete Alert", className: "btn btn-text-secondary", onClick: () => setDeleteMode(true) }, + React.createElement("i", { className: "fas fa-trash" })))) : (editMode || creationMode ? React.createElement(React.Fragment, null, + React.createElement("button", { title: "Save changes", className: "btn btn-text-secondary", onClick: () => doSave() }, + React.createElement("i", { className: "fas fa-save" })), + React.createElement("button", { title: "Discard changes", className: "btn btn-text-secondary ms-1", onClick: editMode ? () => onDiscard() : () => setCreationMode(false) }, + React.createElement("i", { className: "fas fa-times" }))) : React.createElement(React.Fragment, null, + React.createElement("button", { title: "Delete", className: "btn btn-text-secondary", onClick: () => doDelete() }, + React.createElement("i", { className: "fas fa-check" })), + React.createElement("button", { title: "No Delete", className: "btn btn-text-secondary ms-1", onClick: () => setDeleteMode(false) }, + React.createElement("i", { className: "fas fa-times" })))))), + React.createElement("div", { className: "overflow-auto h-100 d-flex flex-column container" }, + activePage === 'data' && !editMode && !creationMode ? React.createElement(CDCDataChangeTable, { selectedAlert: selectedAlert, onAlertChanged: onAlertChanged }) : null, + activePage === 'info' ? React.createElement(React.Fragment, null, + React.createElement("div", { className: "row mb-3 mt-3" }, + React.createElement("div", { className: "mb-3 col" }, + React.createElement("label", { className: "form-label" }, "Name"), + !creationMode && !editMode ? + React.createElement("h6", null, alertData.name) + : + React.createElement(React.Fragment, null, + React.createElement("input", { type: "text", className: `form-control${validName ? '' : ' is-invalid'}`, value: alertData.name, onChange: (e) => setAlertData({ ...alertData, name: e.target.value }), required: true }), + validName ? null : + React.createElement("div", { className: "invalid-feedback" }, "Name must not be empty!"))), + React.createElement("div", { className: "mb-3 col pe-2" }, + React.createElement("label", { className: "form-label" }, "CDC"), + React.createElement(Select, { isDisabled: !creationMode && !editMode, options: cdcs.map((c) => ({ label: c, value: c })), value: { label: alertData.cdc_id, value: alertData.cdc_id }, onChange: (e) => setAlertData({ ...alertData, cdc_id: e.value, compare_columns: null }) })), + React.createElement("div", { className: "mb-3 col pe-2" }, + React.createElement("label", { className: "form-label" }, "Change Fields"), + React.createElement(Select, { isMulti: true, className: `${validCompareColumns ? '' : 'form-control is-invalid'}`, isDisabled: !creationMode && !editMode, closeMenuOnSelect: false, options: compareColumns === null || compareColumns === void 0 ? void 0 : compareColumns.map((c) => ({ label: c, value: c })), + //check for compare_columns because otherwise it would not reset the selection after the cdc_id was changed + value: alertData.compare_columns ? alertData.compare_columns.map((c) => ({ label: c, value: c })) : null, onChange: (e) => setAlertData({ ...alertData, compare_columns: e.map((col) => col.value) }) }), + validCompareColumns ? null : + React.createElement("div", { className: "invalid-feedback" }, "Change fields must not be empty!")), + React.createElement("div", { className: "mb-3 col" }, + React.createElement("label", { className: "form-label" }, "Email notification"), + React.createElement("div", { className: "form-check" }, + React.createElement("input", { className: "form-check-input", id: "CDCAlertViewMailCheckbox", type: "checkbox", disabled: !creationMode && !editMode, checked: alertData.enable_mail_notification, onChange: (e) => setAlertData({ ...alertData, enable_mail_notification: e.target.checked }) }), + React.createElement("label", { className: "form-check-label ms-2", htmlFor: "CDCAlertViewMailCheckbox" }, "Send me an email")))), + React.createElement("div", null, filterSelection && (alertData === null || alertData === void 0 ? void 0 : alertData.filter) ? + React.createElement(CDCFilterCreator, { filterSelection: !creationMode && !editMode ? null : filterSelection, filterComponents: filterComponents, filter: alertData.filter, setFilter: (filter) => setAlertData({ ...alertData, filter }), isInvalid: !validFilter }) + : + React.createElement("p", null, "No filters available for this cdc"))) : null))); +} +//# sourceMappingURL=CDCAlertView.js.map \ No newline at end of file diff --git a/dist/cdc/alert/CDCAlertView.js.map b/dist/cdc/alert/CDCAlertView.js.map new file mode 100644 index 000000000..628379ccc --- /dev/null +++ b/dist/cdc/alert/CDCAlertView.js.map @@ -0,0 +1 @@ +{"version":3,"file":"CDCAlertView.js","sourceRoot":"","sources":["../../../src/cdc/alert/CDCAlertView.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAC1B,OAAO,MAAM,MAAM,cAAc,CAAC;AAClC,OAAO,EAAC,YAAY,EAAC,MAAM,WAAW,CAAC;AACvC,OAAO,EAAC,QAAQ,EAAC,MAAM,aAAa,CAAC;AACrC,OAAO,EAAC,WAAW,EAAE,SAAS,EAAE,SAAS,EAAC,MAAM,QAAQ,CAAC;AACzD,OAAO,EAAC,gBAAgB,EAAC,MAAM,YAAY,CAAC;AAE5C,OAAO,EAAC,kBAAkB,EAAC,MAAM,sBAAsB,CAAC;AAYxD,MAAM,UAAU,YAAY,CAAC,EAAC,SAAS,EAAE,YAAY,EAAE,cAAc,EAAE,aAAa,EAAE,eAAe,EAAE,YAAY,EAAE,SAAS,EAAgB;;IAC5I,MAAM,CAAC,QAAQ,EAAE,WAAW,CAAC,GAAG,KAAK,CAAC,QAAQ,CAAU,KAAK,CAAC,CAAC;IAC/D,MAAM,CAAC,UAAU,EAAE,aAAa,CAAC,GAAG,KAAK,CAAC,QAAQ,CAAU,KAAK,CAAC,CAAC;IACnE,MAAM,CAAC,WAAW,EAAE,cAAc,CAAC,GAAG,KAAK,CAAC,QAAQ,CAAU,IAAI,CAAC,CAAC;IACpE,MAAM,CAAC,SAAS,EAAE,YAAY,CAAC,GAAG,KAAK,CAAC,QAAQ,CAAU,IAAI,CAAC,CAAC;IAChE,MAAM,CAAC,mBAAmB,EAAE,sBAAsB,CAAC,GAAG,KAAK,CAAC,QAAQ,CAAU,IAAI,CAAC,CAAC;IACpF,MAAM,CAAC,UAAU,EAAE,aAAa,CAAC,GAAG,KAAK,CAAC,QAAQ,CAAkB,MAAM,CAAC,CAAC;IAE5E,MAAM,EAAC,MAAM,EAAE,YAAY,EAAE,KAAK,EAAE,WAAW,EAAE,OAAO,EAAE,QAAQ,EAAC,GAAG,QAAQ,CAAC,KAAK,IAAI,EAAE;QACxF,WAAW,CAAC,KAAK,CAAC,CAAC;QACnB,MAAM,WAAW,CAAC,aAAa,CAAC,EAAE,CAAC,CAAC;QACpC,cAAc,EAAE,CAAC;IACnB,CAAC,CAAC,CAAC;IAEH,MAAM,EAAC,MAAM,EAAE,UAAU,EAAE,KAAK,EAAE,SAAS,EAAE,OAAO,EAAE,MAAM,EAAC,GAAG,QAAQ,CAAC,KAAK,IAAI,EAAE;;QAClF,MAAM,SAAS,GAAG,CAAC,EAAC,SAAS,aAAT,SAAS,uBAAT,SAAS,CAAE,MAAM,CAAA,CAAC;QACtC,MAAM,OAAO,GAAG,OAAA,SAAS,aAAT,SAAS,uBAAT,SAAS,CAAE,IAAI,0CAAE,IAAI,GAAG,MAAM,IAAG,CAAC,CAAC;QACnD,MAAM,iBAAiB,GAAG,OAAA,SAAS,aAAT,SAAS,uBAAT,SAAS,CAAE,eAAe,0CAAE,MAAM,IAAG,CAAC,CAAC;QACjE,IAAI,SAAS,IAAI,OAAO,IAAI,iBAAiB,EAAE;YAC7C,IAAI,QAAQ,CAAC;YACb,IAAI,aAAa,EAAE;gBACjB,QAAQ,GAAG,MAAM,SAAS,CAAC,aAAa,CAAC,EAAE,EAAE,EAAC,GAAG,SAAS,EAAC,CAAC,CAAC;gBAC7D,WAAW,CAAC,KAAK,CAAC,CAAC;aACpB;iBAAM;gBACL,QAAQ,GAAG,MAAM,SAAS,CAAC,EAAC,GAAG,SAAS,EAAC,CAAC,CAAC;gBAC3C,eAAe,CAAC,KAAK,CAAC,CAAC;aACxB;YACD,cAAc,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC;SAC7B;QACD,cAAc,CAAC,SAAS,CAAC,CAAC;QAC1B,YAAY,CAAC,OAAO,CAAC,CAAC;QACtB,sBAAsB,CAAC,iBAAiB,CAAC,CAAC;IAC5C,CAAC,CAAC,CAAC;IAEH,6EAA6E;IAC7E,MAAM,IAAI,GAAG,MAAM,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;IAEpC,MAAM,eAAe,SAAG,SAAS,CAAC,SAAS,aAAT,SAAS,uBAAT,SAAS,CAAE,MAAM,CAAC,0CAAE,OAAO,CAAC;IAC9D,MAAM,cAAc,SAAG,SAAS,CAAC,SAAS,aAAT,SAAS,uBAAT,SAAS,CAAE,MAAM,CAAC,0CAAE,cAAc,CAAC;IACpE,MAAM,gBAAgB,SAAG,SAAS,CAAC,SAAS,aAAT,SAAS,uBAAT,SAAS,CAAE,MAAM,CAAC,0CAAE,UAAU,CAAC;IAElE,KAAK,CAAC,SAAS,CAAC,GAAG,EAAE;QACnB,IAAI,aAAa,EAAE;YACjB,WAAW,CAAC,KAAK,CAAC,CAAC;YACnB,aAAa,CAAC,KAAK,CAAC,CAAC;YACrB,aAAa,CAAC,MAAM,CAAC,CAAC;SACvB;IACH,CAAC,EAAE,CAAC,aAAa,CAAC,CAAC,CAAC;IAGpB,MAAM,SAAS,GAAG,GAAG,EAAE;QACrB,WAAW,CAAC,KAAK,CAAC,CAAC;QACnB,YAAY,CAAC,aAAa,CAAC,CAAC;IAC9B,CAAC,CAAC;IAEF,OAAO,CAAC,6BAAK,SAAS,EAAE,GAAG,UAAU,KAAK,SAAS,CAAC,CAAC,CAAC,kBAAkB,CAAC,CAAC,CAAC,EAAE,EAAE;QAC5E,CAAA,aAAa,aAAb,aAAa,uBAAb,aAAa,CAAE,YAAY,EAAC,CAAC;YAC5B,oBAAC,YAAY,IAAC,KAAK,EAAE,IAAI,KAAK,CAAC,oBAAoB,IAAI,IAAI,CAAC,aAAa,CAAC,iBAAiB,CAAC,sBAAsB,aAAa,CAAC,YAAY,EAAE,CAAC,GAAI;YACnJ,CAAC,CAAC,WAAW,CAAC,CAAC;gBACb,oBAAC,YAAY,IAAC,KAAK,EAAE,IAAI,KAAK,CAAC,oCAAoC,WAAW,EAAE,CAAC,GAAI;gBACrF,CAAC,CAAC,SAAS,CAAC,CAAC;oBACX,oBAAC,YAAY,IAAC,KAAK,EAAE,IAAI,KAAK,CAAC,kCAAkC,SAAS,EAAE,CAAC,GAAI;oBACjF,CAAC,CAAC,IAAI;QAEZ,4BAAI,SAAS,EAAC,cAAc;YAC1B,4BAAI,SAAS,EAAC,UAAU;gBACtB,2BAAG,SAAS,EAAE,YAAY,UAAU,KAAK,MAAM,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,EAAE,EAAE,EAAE,IAAI,EAAC,GAAG,EAAC,OAAO,EAAE,CAAC,CAAC,EAAE,EAAE;wBACzF,CAAC,CAAC,cAAc,EAAE,CAAC;wBACnB,aAAa,CAAC,MAAM,CAAC,CAAC;oBACxB,CAAC,kBAAiB,CACf;YACJ,CAAC,QAAQ,IAAI,CAAC,YAAY,CAAC,CAAC,CAAC,4BAAI,SAAS,EAAC,UAAU;gBACpD,2BAAG,SAAS,EAAE,YAAY,UAAU,KAAK,MAAM,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,EAAE,EAAE,EAAE,IAAI,EAAC,GAAG,EAAC,OAAO,EAAE,CAAC,CAAC,EAAE,EAAE;wBACzF,CAAC,CAAC,cAAc,EAAE,CAAC;wBACnB,aAAa,CAAC,MAAM,CAAC,CAAC;oBACxB,CAAC,IAAG,CAAA,aAAa,aAAb,aAAa,uBAAb,aAAa,CAAE,WAAW,EAAC,CAAC,CAAC,sBAAsB,UAAG,IAAI,IAAI,CAAC,aAAa,CAAC,mBAAmB,CAAC,0CAAE,kBAAkB,GAAE,CAAC,CAAC,CAAC,MAAM,CAAK,CACtI,CAAC,CAAC,CAAC,IAAI;YACZ,+BAAO,SAAS,EAAC,wCAAwC,IACtD,UAAU,KAAK,SAAS,IAAI,YAAY,KAAK,SAAS,CAAC,CAAC,CAAC,CACxD,2BAAG,SAAS,EAAC,wBAAwB,GAAG,CACzC,CAAC,CAAC,CAAC,CAAC,QAAQ,IAAI,CAAC,UAAU,IAAI,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC;gBAC/C,gCAAQ,KAAK,EAAC,YAAY,EAAC,SAAS,EAAC,wBAAwB,EAAC,OAAO,EAAE,GAAG,EAAE,CAAC,WAAW,CAAC,IAAI,CAAC;oBAAE,2BAAG,SAAS,EAAC,mBAAmB,GAAK,CAAS;gBAC9I,gCAAQ,KAAK,EAAC,cAAc,EAAC,SAAS,EAAC,wBAAwB,EAAC,OAAO,EAAE,GAAG,EAAE,CAAC,aAAa,CAAC,IAAI,CAAC;oBAAE,2BAAG,SAAS,EAAC,cAAc,GAAK,CAAS,CAC5I,CAAC,CAAC,CAAC,CAAC,CAAC,QAAQ,IAAI,YAAY,CAAC,CAAC,CAAC;gBACjC,gCAAQ,KAAK,EAAC,cAAc,EAAC,SAAS,EAAC,wBAAwB,EAAC,OAAO,EAAE,GAAG,EAAE,CAAC,MAAM,EAAE;oBAAE,2BAAG,SAAS,EAAC,aAAa,GAAK,CAAS;gBACjI,gCAAQ,KAAK,EAAC,iBAAiB,EAAC,SAAS,EAAC,6BAA6B,EAAC,OAAO,EAAE,QAAQ,CAAC,CAAC,CAAC,GAAG,EAAE,CAAC,SAAS,EAAE,CAAC,CAAC,CAAC,GAAG,EAAE,CAAC,eAAe,CAAC,KAAK,CAAC;oBAAE,2BAAG,SAAS,EAAC,cAAc,GAAK,CAAS,CACtL,CAAC,CAAC,CAAC;gBACJ,gCAAQ,KAAK,EAAC,QAAQ,EAAC,SAAS,EAAC,wBAAwB,EAAC,OAAO,EAAE,GAAG,EAAE,CAAC,QAAQ,EAAE;oBAAE,2BAAG,SAAS,EAAC,cAAc,GAAK,CAAS;gBAC9H,gCAAQ,KAAK,EAAC,WAAW,EAAC,SAAS,EAAC,6BAA6B,EAAC,OAAO,EAAE,GAAG,EAAE,CAAC,aAAa,CAAC,KAAK,CAAC;oBAAE,2BAAG,SAAS,EAAC,cAAc,GAAK,CAAS,CAC/I,CAAC,CACE,CACL;QACL,6BAAK,SAAS,EAAC,kDAAkD;YAC9D,UAAU,KAAK,MAAM,IAAI,CAAC,QAAQ,IAAI,CAAC,YAAY,CAAC,CAAC,CAAC,oBAAC,kBAAkB,IAAC,aAAa,EAAE,aAAa,EAAE,cAAc,EAAE,cAAc,GAAI,CAAC,CAAC,CAAC,IAAI;YACjJ,UAAU,KAAK,MAAM,CAAC,CAAC,CAAC;gBACzB,6BAAK,SAAS,EAAC,eAAe;oBACxB,6BAAK,SAAS,EAAC,UAAU;wBACvB,+BAAO,SAAS,EAAC,YAAY,WAAa;wBACzC,CAAC,YAAY,IAAI,CAAC,QAAQ,CAAC,CAAC;4BAC3B,gCAAK,SAAS,CAAC,IAAI,CAAM;4BACzB,CAAC;gCACD;oCAAE,+BAAO,IAAI,EAAC,MAAM,EAAC,SAAS,EAAE,eAAe,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,aAAa,EAAE,EAAE,KAAK,EAAE,SAAS,CAAC,IAAI,EAAE,QAAQ,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,YAAY,CAAC,EAAC,GAAG,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC,MAAM,CAAC,KAAK,EAAC,CAAC,EAAE,QAAQ,SAAG;oCACrL,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;wCACjB,6BAAK,SAAS,EAAC,kBAAkB,8BAE3B,CAAI,CAEZ;oBACN,6BAAK,SAAS,EAAC,eAAe;wBAC5B,+BAAO,SAAS,EAAC,YAAY,UAAY;wBACzC,oBAAC,MAAM,IACL,UAAU,EAAE,CAAC,YAAY,IAAI,CAAC,QAAQ,EACtC,OAAO,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,EAAC,KAAK,EAAE,CAAC,EAAE,KAAK,EAAE,CAAC,EAAC,CAAC,CAAC,EAChD,KAAK,EAAE,EAAC,KAAK,EAAE,SAAS,CAAC,MAAM,EAAE,KAAK,EAAE,SAAS,CAAC,MAAM,EAAC,EACzD,QAAQ,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,YAAY,CAAC,EAAC,GAAG,SAAS,EAAE,MAAM,EAAE,CAAC,CAAC,KAAK,EAAE,eAAe,EAAE,IAAI,EAAC,CAAC,GACrF,CACE;oBACN,6BAAK,SAAS,EAAC,eAAe;wBAC5B,+BAAO,SAAS,EAAC,YAAY,oBAAsB;wBACnD,oBAAC,MAAM,IACL,OAAO,QACP,SAAS,EAAE,GAAG,mBAAmB,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,yBAAyB,EAAE,EACpE,UAAU,EAAE,CAAC,YAAY,IAAI,CAAC,QAAQ,EACtC,iBAAiB,EAAE,KAAK,EACxB,OAAO,EAAE,cAAc,aAAd,cAAc,uBAAd,cAAc,CAAE,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,EAAC,KAAK,EAAE,CAAC,EAAE,KAAK,EAAE,CAAC,EAAC,CAAC;4BAC1D,2GAA2G;4BAC3G,KAAK,EAAE,SAAS,CAAC,eAAe,CAAC,CAAC,CAAC,SAAS,CAAC,eAAe,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,EAAC,KAAK,EAAE,CAAC,EAAE,KAAK,EAAE,CAAC,EAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,EACtG,QAAQ,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,YAAY,CAAC,EAAC,GAAG,SAAS,EAAE,eAAe,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,GAAG,CAAC,KAAK,CAAC,EAAC,CAAC,GACzF;wBACD,mBAAmB,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;4BAC3B,6BAAK,SAAS,EAAC,kBAAkB,uCAE3B,CAEJ;oBACN,6BAAK,SAAS,EAAC,UAAU;wBACvB,+BAAO,SAAS,EAAC,YAAY,yBAA2B;wBACxD,6BAAK,SAAS,EAAC,YAAY;4BACzB,+BAAO,SAAS,EAAC,kBAAkB,EAAC,EAAE,EAAC,0BAA0B,EAAC,IAAI,EAAC,UAAU,EAAC,QAAQ,EAAE,CAAC,YAAY,IAAI,CAAC,QAAQ,EAAE,OAAO,EAAE,SAAS,CAAC,wBAAwB,EAAE,QAAQ,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,YAAY,CAAC,EAAC,GAAG,SAAS,EAAE,wBAAwB,EAAE,CAAC,CAAC,MAAM,CAAC,OAAO,EAAC,CAAC,GAAI;4BAClQ,+BAAO,SAAS,EAAC,uBAAuB,EAAC,OAAO,EAAC,0BAA0B,uBAAyB,CAChG,CACF,CACF;gBACN,iCACG,eAAe,KAAI,SAAS,aAAT,SAAS,uBAAT,SAAS,CAAE,MAAM,CAAA,CAAC,CAAC;oBACrC,oBAAC,gBAAgB,IACf,eAAe,EAAE,CAAC,YAAY,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,eAAe,EACpE,gBAAgB,EAAE,gBAAgB,EAClC,MAAM,EAAE,SAAS,CAAC,MAAM,EACxB,SAAS,EAAE,CAAC,MAAM,EAAE,EAAE,CAAC,YAAY,CAAC,EAAC,GAAG,SAAS,EAAE,MAAM,EAAC,CAAC,EAC3D,SAAS,EAAE,CAAC,WAAW,GAAI;oBAC7B,CAAC;wBACD,mEAAwC,CACtC,CACP,CAAC,CAAC,CAAC,IAAI,CACN,CACF,CAAC,CAAC;AACV,CAAC"} \ No newline at end of file diff --git a/dist/cdc/alert/CDCDataChangeTable.d.ts b/dist/cdc/alert/CDCDataChangeTable.d.ts new file mode 100644 index 000000000..770a6081a --- /dev/null +++ b/dist/cdc/alert/CDCDataChangeTable.d.ts @@ -0,0 +1,8 @@ +/// +import { IAlert } from '../interfaces'; +interface ICDCDataChangeTable { + selectedAlert: IAlert; + onAlertChanged: (id?: number) => void; +} +export declare function CDCDataChangeTable({ selectedAlert, onAlertChanged }: ICDCDataChangeTable): JSX.Element; +export {}; diff --git a/dist/cdc/alert/CDCDataChangeTable.js b/dist/cdc/alert/CDCDataChangeTable.js new file mode 100644 index 000000000..f47ed2d04 --- /dev/null +++ b/dist/cdc/alert/CDCDataChangeTable.js @@ -0,0 +1,81 @@ +import { get } from 'lodash'; +import React, { useEffect } from 'react'; +import { useAsync } from '../../hooks'; +import { confirmAlertById } from '../api'; +import { ErrorMessage } from '../common/ErrorMessage'; +export function CDCDataChangeTable({ selectedAlert, onAlertChanged }) { + var _a, _b, _c, _d; + const [dataChange, setDataChange] = React.useState(new Map()); + useEffect(() => { + var _a, _b, _c; + if ((_a = selectedAlert === null || selectedAlert === void 0 ? void 0 : selectedAlert.latest_diff) === null || _a === void 0 ? void 0 : _a.values_changed) { + const change = new Map(); + (_c = (_b = selectedAlert.latest_diff) === null || _b === void 0 ? void 0 : _b.values_changed) === null || _c === void 0 ? void 0 : _c.map((d) => { + const nestedField = d.field.map((f) => f).join('.'); + if (change.has(d.id)) { + change.set(d.id, change.get(d.id).set(nestedField, { old: d.old_value, new: d.new_value })); + } + else { + change.set(d.id, new Map().set(nestedField, { old: d.old_value, new: d.new_value })); + } + }); + setDataChange(change); + } + else { + setDataChange(new Map()); + } + }, [selectedAlert]); + const { status: confirmStatus, error: confirmError, execute: doConfirm } = useAsync(async () => { + const alert = await confirmAlertById(selectedAlert.id); + onAlertChanged(alert.id); + }); + return (React.createElement(React.Fragment, null, confirmStatus === 'pending' ? + React.createElement("i", { className: "fas fa-spinner fa-spin" }) + : React.createElement(React.Fragment, null, selectedAlert.latest_diff || selectedAlert.confirmed_data ? (React.createElement(React.Fragment, null, + React.createElement("table", { className: "table mb-0 d-block overflow-auto" }, + React.createElement("thead", { className: "position-sticky table-light top-0" }, + React.createElement("tr", null, + React.createElement("th", { scope: "col" }, "ID"), + selectedAlert.compare_columns.map((field, i) => React.createElement("th", { key: field, scope: "col" }, field)), + React.createElement("th", { scope: "col" }, "Status"))), + React.createElement("tbody", { className: "overflow-auto" }, + selectedAlert.latest_diff ? React.createElement(React.Fragment, null, (_b = (_a = selectedAlert.latest_diff) === null || _a === void 0 ? void 0 : _a.dictionary_item_added) === null || _b === void 0 ? void 0 : + _b.map((d) => { + const data = selectedAlert.latest_fetched_data.find((a) => a._cdc_compare_id === d); + return (React.createElement("tr", { key: d, className: "table-success" }, + React.createElement("td", { scope: "row" }, data._cdc_compare_id), + selectedAlert.compare_columns.map((field, i) => React.createElement("td", { key: field }, get(data, field))), + React.createElement("td", null, "Added"))); + }), (_d = (_c = selectedAlert.latest_diff) === null || _c === void 0 ? void 0 : _c.dictionary_item_removed) === null || _d === void 0 ? void 0 : + _d.map((d) => { + const data = selectedAlert.confirmed_data.find((a) => a._cdc_compare_id === d); + return (React.createElement("tr", { key: d, className: "table-danger" }, + React.createElement("td", { scope: "row" }, data._cdc_compare_id), + selectedAlert.compare_columns.map((field, i) => React.createElement("td", { key: field }, get(data, field))), + React.createElement("td", null, "Removed"))); + })) : null, + selectedAlert.confirmed_data ? React.createElement(React.Fragment, null, selectedAlert.confirmed_data + // Only show entries which are not already shown above + .filter((item) => { var _a, _b, _c, _d; return !((_b = (_a = selectedAlert.latest_diff) === null || _a === void 0 ? void 0 : _a.dictionary_item_added) === null || _b === void 0 ? void 0 : _b.includes(item._cdc_compare_id)) && !((_d = (_c = selectedAlert.latest_diff) === null || _c === void 0 ? void 0 : _c.dictionary_item_removed) === null || _d === void 0 ? void 0 : _d.includes(item._cdc_compare_id)); }) + // Sort such that rows with changes are on top + .sort((a, b) => (dataChange.has(b._cdc_compare_id) ? 1 : 0) - (dataChange.has(a._cdc_compare_id) ? 1 : 0)).map((d) => { + var _a, _b, _c, _d; + const id = d._cdc_compare_id; + const hasChanged = dataChange.has(id); + // TODO: All these .find() and .includes() should be refactored as they are O(n). + const isAlreadyHandled = ((_b = (_a = selectedAlert.latest_diff) === null || _a === void 0 ? void 0 : _a.dictionary_item_added) === null || _b === void 0 ? void 0 : _b.includes(id)) || ((_d = (_c = selectedAlert.latest_diff) === null || _c === void 0 ? void 0 : _c.dictionary_item_removed) === null || _d === void 0 ? void 0 : _d.includes(id)); + return (isAlreadyHandled ? null : + React.createElement("tr", { key: id, className: `${hasChanged ? 'table-primary' : ''}` }, + React.createElement("td", { scope: "row" }, d._cdc_compare_id), + selectedAlert.compare_columns.map((field) => (React.createElement(React.Fragment, { key: field }, hasChanged ? (dataChange.get(id).has(field) ? (React.createElement("td", null, + React.createElement("s", null, dataChange.get(id).get(field).old), + " ", + dataChange.get(id).get(field).new)) : (React.createElement("td", null, get(d, field)))) : (React.createElement("td", { key: field }, get(d, field)))))), + React.createElement("td", null, hasChanged ? React.createElement(React.Fragment, null, "Changed") : null))); + })) : null)), + selectedAlert.latest_diff ? React.createElement("div", { className: "p-1" }, + confirmError ? React.createElement(ErrorMessage, { error: new Error(`While confirming an error occured: ${confirmError}`) }) : null, + React.createElement("div", { className: "d-md-flex justify-content-md-end" }, + React.createElement("button", { title: "Confirm changes", className: "btn btn-primary", onClick: () => doConfirm() }, "Confirm"))) : null)) : React.createElement("p", { className: "m-3" }, "No new data available")))); +} +//# sourceMappingURL=CDCDataChangeTable.js.map \ No newline at end of file diff --git a/dist/cdc/alert/CDCDataChangeTable.js.map b/dist/cdc/alert/CDCDataChangeTable.js.map new file mode 100644 index 000000000..bab33da8b --- /dev/null +++ b/dist/cdc/alert/CDCDataChangeTable.js.map @@ -0,0 +1 @@ +{"version":3,"file":"CDCDataChangeTable.js","sourceRoot":"","sources":["../../../src/cdc/alert/CDCDataChangeTable.tsx"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/dist/cdc/alert/index.d.ts b/dist/cdc/alert/index.d.ts new file mode 100644 index 000000000..13dae734d --- /dev/null +++ b/dist/cdc/alert/index.d.ts @@ -0,0 +1 @@ +export * from './CDCAlertView'; diff --git a/dist/cdc/alert/index.js b/dist/cdc/alert/index.js new file mode 100644 index 000000000..e0181ad3d --- /dev/null +++ b/dist/cdc/alert/index.js @@ -0,0 +1,2 @@ +export * from './CDCAlertView'; +//# sourceMappingURL=index.js.map \ No newline at end of file diff --git a/dist/cdc/alert/index.js.map b/dist/cdc/alert/index.js.map new file mode 100644 index 000000000..8b59d24e0 --- /dev/null +++ b/dist/cdc/alert/index.js.map @@ -0,0 +1 @@ +{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/cdc/alert/index.ts"],"names":[],"mappings":"AAAA,cAAc,gBAAgB,CAAC"} \ No newline at end of file diff --git a/dist/cdc/api.d.ts b/dist/cdc/api.d.ts new file mode 100644 index 000000000..ea1db11fc --- /dev/null +++ b/dist/cdc/api.d.ts @@ -0,0 +1,22 @@ +/********************************************************* + * Copyright (c) 2021 datavisyn GmbH, http://datavisyn.io + * + * This file is property of datavisyn. + * Code and any other files associated with this community + * may not be copied and/or distributed without permission. + * + * Proprietary and confidential. No warranty. + * + *********************************************************/ +import { IAlert, IUploadAlert } from './interfaces'; +export declare function getAlerts(): Promise; +export declare function getAlertsById(id: number): Promise; +export declare function editAlert(id: number, alert: Partial): Promise; +export declare function deleteAlert(id: number): Promise; +export declare function saveAlert(alert: IUploadAlert): Promise; +export declare function runAlertById(id: number): Promise; +export declare function confirmAlertById(id: number): Promise; +export declare function runAllAlerts(): Promise<{ + success: string[]; + error: string[]; +} | null>; diff --git a/dist/cdc/api.js b/dist/cdc/api.js new file mode 100644 index 000000000..f7873afd0 --- /dev/null +++ b/dist/cdc/api.js @@ -0,0 +1,37 @@ +/********************************************************* + * Copyright (c) 2021 datavisyn GmbH, http://datavisyn.io + * + * This file is property of datavisyn. + * Code and any other files associated with this community + * may not be copied and/or distributed without permission. + * + * Proprietary and confidential. No warranty. + * + *********************************************************/ +import { AppContext } from '../app'; +import { Ajax } from '../base'; +export async function getAlerts() { + return AppContext.getInstance().getAPIJSON(`/tdp/cdc/alert`); +} +export async function getAlertsById(id) { + return AppContext.getInstance().getAPIJSON(`/tdp/cdc/alert/${id}`); +} +export async function editAlert(id, alert) { + return Ajax.send(AppContext.getInstance().api2absURL(`/tdp/cdc/alert/${id}`), alert, 'PUT', 'JSON', 'application/json'); +} +export async function deleteAlert(id) { + return Ajax.send(AppContext.getInstance().api2absURL(`/tdp/cdc/alert/${id}`), null, 'DELETE'); +} +export async function saveAlert(alert) { + return Ajax.send(AppContext.getInstance().api2absURL(`/tdp/cdc/alert`), alert, 'POST', 'JSON', 'application/json'); +} +export async function runAlertById(id) { + return AppContext.getInstance().getAPIJSON(`/tdp/cdc/alert/${id}/run`); +} +export async function confirmAlertById(id) { + return AppContext.getInstance().getAPIJSON(`/tdp/cdc/alert/${id}/confirm`); +} +export async function runAllAlerts() { + return AppContext.getInstance().getAPIJSON(`/tdp/cdc/alert/run`); +} +//# sourceMappingURL=api.js.map \ No newline at end of file diff --git a/dist/cdc/api.js.map b/dist/cdc/api.js.map new file mode 100644 index 000000000..5119609a3 --- /dev/null +++ b/dist/cdc/api.js.map @@ -0,0 +1 @@ +{"version":3,"file":"api.js","sourceRoot":"","sources":["../../src/cdc/api.ts"],"names":[],"mappings":"AAAA;;;;;;;;;2DAS2D;AAG3D,OAAO,EAAC,UAAU,EAAC,MAAM,QAAQ,CAAC;AAClC,OAAO,EAAC,IAAI,EAAC,MAAM,SAAS,CAAC;AAE7B,MAAM,CAAC,KAAK,UAAU,SAAS;IAC7B,OAAO,UAAU,CAAC,WAAW,EAAE,CAAC,UAAU,CAAC,gBAAgB,CAAC,CAAC;AAC/D,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,aAAa,CAAC,EAAU;IAC5C,OAAO,UAAU,CAAC,WAAW,EAAE,CAAC,UAAU,CAAC,kBAAkB,EAAE,EAAE,CAAC,CAAC;AACrE,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,SAAS,CAAC,EAAU,EAAE,KAAsB;IAChE,OAAO,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,WAAW,EAAE,CAAC,UAAU,CAAC,kBAAkB,EAAE,EAAE,CAAC,EAAE,KAAK,EAAE,KAAK,EAAE,MAAM,EAAE,kBAAkB,CAAC,CAAC;AAC1H,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,WAAW,CAAC,EAAU;IAC1C,OAAO,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,WAAW,EAAE,CAAC,UAAU,CAAC,kBAAkB,EAAE,EAAE,CAAC,EAAE,IAAI,EAAE,QAAQ,CAAC,CAAC;AAChG,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,SAAS,CAAC,KAAmB;IACjD,OAAO,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,WAAW,EAAE,CAAC,UAAU,CAAC,gBAAgB,CAAC,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,kBAAkB,CAAC,CAAC;AACrH,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,YAAY,CAAC,EAAU;IAC3C,OAAO,UAAU,CAAC,WAAW,EAAE,CAAC,UAAU,CAAC,kBAAkB,EAAE,MAAM,CAAC,CAAC;AACzE,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,gBAAgB,CAAC,EAAU;IAC/C,OAAO,UAAU,CAAC,WAAW,EAAE,CAAC,UAAU,CAAC,kBAAkB,EAAE,UAAU,CAAC,CAAC;AAC7E,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,YAAY;IAChC,OAAO,UAAU,CAAC,WAAW,EAAE,CAAC,UAAU,CAAC,oBAAoB,CAAC,CAAC;AACnE,CAAC"} \ No newline at end of file diff --git a/dist/cdc/common/ErrorMessage.d.ts b/dist/cdc/common/ErrorMessage.d.ts new file mode 100644 index 000000000..6a1a1cc07 --- /dev/null +++ b/dist/cdc/common/ErrorMessage.d.ts @@ -0,0 +1,5 @@ +/// +export declare function ErrorMessage({ error, onRetry }: { + error: Error | null; + onRetry?: () => void; +}): JSX.Element; diff --git a/dist/cdc/common/ErrorMessage.js b/dist/cdc/common/ErrorMessage.js new file mode 100644 index 000000000..b842377f5 --- /dev/null +++ b/dist/cdc/common/ErrorMessage.js @@ -0,0 +1,8 @@ +import * as React from 'react'; +export function ErrorMessage({ error, onRetry }) { + return error ? (React.createElement("div", { className: "alert alert-danger flex-fill d-flex align-items-center", role: "alert" }, + React.createElement("i", { className: "fas fa-exclamation-triangle text-danger me-1" }), + React.createElement("div", { className: "flex-fill" }, error.message), + onRetry ? (React.createElement("button", { type: "button", className: "btn btn-sm btn-outline-danger", onClick: onRetry }, "Retry")) : null)) : null; +} +//# sourceMappingURL=ErrorMessage.js.map \ No newline at end of file diff --git a/dist/cdc/common/ErrorMessage.js.map b/dist/cdc/common/ErrorMessage.js.map new file mode 100644 index 000000000..93420ff40 --- /dev/null +++ b/dist/cdc/common/ErrorMessage.js.map @@ -0,0 +1 @@ +{"version":3,"file":"ErrorMessage.js","sourceRoot":"","sources":["../../../src/cdc/common/ErrorMessage.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAE/B,MAAM,UAAU,YAAY,CAAC,EAAC,KAAK,EAAE,OAAO,EAA8C;IACxF,OAAO,KAAK,CAAC,CAAC,CAAC,CACb,6BAAK,SAAS,EAAC,wDAAwD,EAAC,IAAI,EAAC,OAAO;QAClF,2BAAG,SAAS,EAAC,8CAA8C,GAAE;QAC7D,6BAAK,SAAS,EAAC,WAAW,IAAE,KAAK,CAAC,OAAO,CAAO;QAC/C,OAAO,CAAC,CAAC,CAAC,CACT,gCAAQ,IAAI,EAAC,QAAQ,EAAC,SAAS,EAAC,+BAA+B,EAAC,OAAO,EAAE,OAAO,YAEvE,CACV,CAAC,CAAC,CAAC,IAAI,CACJ,CACP,CAAC,CAAC,CAAC,IAAI,CAAC;AACX,CAAC"} \ No newline at end of file diff --git a/dist/cdc/common/index.d.ts b/dist/cdc/common/index.d.ts new file mode 100644 index 000000000..3dee9621e --- /dev/null +++ b/dist/cdc/common/index.d.ts @@ -0,0 +1 @@ +export * from './ErrorMessage'; diff --git a/dist/cdc/common/index.js b/dist/cdc/common/index.js new file mode 100644 index 000000000..fd897c741 --- /dev/null +++ b/dist/cdc/common/index.js @@ -0,0 +1,2 @@ +export * from './ErrorMessage'; +//# sourceMappingURL=index.js.map \ No newline at end of file diff --git a/dist/cdc/common/index.js.map b/dist/cdc/common/index.js.map new file mode 100644 index 000000000..41b652e15 --- /dev/null +++ b/dist/cdc/common/index.js.map @@ -0,0 +1 @@ +{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/cdc/common/index.ts"],"names":[],"mappings":"AAAA,cAAc,gBAAgB,CAAC"} \ No newline at end of file diff --git a/dist/cdc/creator/CDCFilterCard.d.ts b/dist/cdc/creator/CDCFilterCard.d.ts new file mode 100644 index 000000000..b2cd4daac --- /dev/null +++ b/dist/cdc/creator/CDCFilterCard.d.ts @@ -0,0 +1,24 @@ +/// +import { IFilter, IFilterComponent } from '../interfaces'; +interface IFilterCardProps { + filter: IFilter; + onDrop?: (item: IFilter, { target, index }: { + target: IFilter; + index: number; + }) => void; + onDelete?: (filter: IFilter) => void; + onChange?: (filter: IFilter, changeFunc: (filter: IFilter) => void) => void; + onValueChanged?: (filter: IFilter, value: any, field: string) => void; + filterComponents: { + [key: string]: { + component: IFilterComponent; + config?: any; + }; + }; + disableFilter: boolean; + isInvalid?: boolean; + disableRemoving?: boolean; + disableDragging?: boolean; +} +export declare function FilterCard({ filter, onDrop, onDelete, onChange, onValueChanged, filterComponents, disableFilter, isInvalid, disableDragging, disableRemoving }: IFilterCardProps): JSX.Element; +export {}; diff --git a/dist/cdc/creator/CDCFilterCard.js b/dist/cdc/creator/CDCFilterCard.js new file mode 100644 index 000000000..9e6b75a44 --- /dev/null +++ b/dist/cdc/creator/CDCFilterCard.js @@ -0,0 +1,50 @@ +import * as React from 'react'; +import { ITEM_TYPES } from '../interfaces'; +import { useDrag } from 'react-dnd'; +import { DropZone } from './DropZone'; +export function FilterCard({ filter, onDrop, onDelete, onChange, onValueChanged, filterComponents, disableFilter, isInvalid, disableDragging, disableRemoving }) { + var _a, _b, _c; + const [{ isDragging, draggedItem }, drag, preview] = useDrag(() => ({ + type: ITEM_TYPES.FILTERCARD, + item: filter, + collect: (monitor) => ({ + isDragging: !!monitor.isDragging(), + draggedItem: monitor.getItem() + }) + }), [filter]); + const hasChildren = filter.children && filter.children.length >= 0; + const filterComponent = filterComponents[filter.type]; + if (!filterComponent) { + return React.createElement(React.Fragment, null, "ERROR!!"); + } + return (React.createElement(React.Fragment, null, + React.createElement("div", { className: `card mb-2 ${isDragging ? 'bg-light' : ''}${isInvalid ? ' form-control is-invalid' : ''}`, ref: preview, style: disableRemoving && disableDragging ? { height: '93%' } : {} }, + React.createElement("div", { className: "card-body" }, + React.createElement("div", { className: "row" }, + React.createElement("div", { className: "col-1 pe-0", style: disableDragging || disableFilter ? {} : { cursor: 'move' }, ref: disableDragging || disableFilter ? undefined : drag }, disableDragging || disableFilter ? null : (React.createElement("i", { style: { marginRight: 5 }, className: "fas fa-arrows-alt" }))), + React.createElement("div", { className: "col-10" }, (filterComponent === null || filterComponent === void 0 ? void 0 : filterComponent.component) ? (React.createElement("div", null, + React.createElement(filterComponent.component.clazz, { disabled: disableFilter, value: filter.value, config: filterComponent.config, field: filter.field, onValueChanged: onValueChanged + ? (value, field) => onValueChanged(filter, value, field || filter.field) + : undefined }), + filter.type === 'group' ? + React.createElement("div", { className: "input-group d-flex w-100 justify-content-between" }, + React.createElement("h6", null, "Group Filter"), + onChange && hasChildren && ((_a = filter === null || filter === void 0 ? void 0 : filter.children) === null || _a === void 0 ? void 0 : _a.length) > 1 ? (React.createElement("small", null, + React.createElement("select", { className: "form-select form-select-sm", style: { width: '6em' }, value: filter.operator || 'AND', disabled: disableFilter, onChange: (e) => { + onChange(filter, (f) => { + f.operator = e.currentTarget.value; + }); + } }, + React.createElement("option", { value: "AND" }, "AND"), + React.createElement("option", { value: "OR" }, "OR")))) : null) : null)) : null), + React.createElement("div", { className: "col-1 ps-0" }, !disableRemoving && onDelete && !disableFilter ? (React.createElement("button", { className: "btn btn-text-secondary btn-sm", onClick: () => onDelete(filter) }, + React.createElement("i", { className: "fas fa-times" }))) : null)), + onDrop && (hasChildren || !filterComponent.component.disableDropping) && !disableFilter ? (React.createElement(DropZone, { onDrop: onDrop, filter: filter, index: 0, canDrop: draggedItem !== ((_b = filter.children) === null || _b === void 0 ? void 0 : _b[0]) })) : null, (_c = filter.children) === null || _c === void 0 ? void 0 : + _c.map((child, i, allChildren) => (React.createElement(React.Fragment, { key: child.id }, + React.createElement(FilterCard, { key: child.id, filter: child, onDrop: onDrop, onDelete: onDelete, onValueChanged: onValueChanged, onChange: onChange, filterComponents: filterComponents, disableFilter: disableFilter }), + onDrop && hasChildren && !disableFilter ? (React.createElement(DropZone, { onDrop: onDrop, filter: filter, index: i + 1, canDrop: draggedItem !== allChildren[i + 1] && draggedItem !== child && !disableFilter })) : null))))), + isInvalid ? + React.createElement("div", { className: "invalid-feedback mb-2" }, "Filter must not be empty!") : + null)); +} +//# sourceMappingURL=CDCFilterCard.js.map \ No newline at end of file diff --git a/dist/cdc/creator/CDCFilterCard.js.map b/dist/cdc/creator/CDCFilterCard.js.map new file mode 100644 index 000000000..69f0812b3 --- /dev/null +++ b/dist/cdc/creator/CDCFilterCard.js.map @@ -0,0 +1 @@ +{"version":3,"file":"CDCFilterCard.js","sourceRoot":"","sources":["../../../src/cdc/creator/CDCFilterCard.tsx"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/dist/cdc/creator/CDCFilterCreator.d.ts b/dist/cdc/creator/CDCFilterCreator.d.ts new file mode 100644 index 000000000..86d2b4ae4 --- /dev/null +++ b/dist/cdc/creator/CDCFilterCreator.d.ts @@ -0,0 +1,15 @@ +/// +import { IFilter, IFilterComponent } from '../interfaces'; +export declare function CDCFilterCreator({ filterSelection, filter, setFilter, disableFilter, isInvalid, filterComponents }: { + filterComponents?: { + [key: string]: { + component: IFilterComponent; + config?: any; + }; + }; + filterSelection?: IFilter[]; + filter: IFilter; + setFilter: (value: IFilter) => void; + disableFilter?: boolean; + isInvalid?: boolean; +}): JSX.Element; diff --git a/dist/cdc/creator/CDCFilterCreator.js b/dist/cdc/creator/CDCFilterCreator.js new file mode 100644 index 000000000..b09f56051 --- /dev/null +++ b/dist/cdc/creator/CDCFilterCreator.js @@ -0,0 +1,91 @@ +import produce from 'immer'; +import React from 'react'; +import { DndProvider } from 'react-dnd'; +import { HTML5Backend } from 'react-dnd-html5-backend'; +import { FilterCard } from './CDCFilterCard'; +import { getFilterFromTree } from '../interfaces'; +import { v4 as uuidv4 } from 'uuid'; +export function CDCFilterCreator({ filterSelection, filter, setFilter, disableFilter, isInvalid, filterComponents }) { + const onDelete = (newFilter) => { + setFilter(produce(filter, (nextFilter) => { + const { current, parent } = getFilterFromTree(nextFilter, newFilter.id); + if (current && parent && parent.children) { + // Find the index of the current element in the parents children + const deleteIndex = parent.children.indexOf(current); + // Remove it from the current parent + if (deleteIndex !== -1) { + parent.children.splice(deleteIndex, 1); + } + } + })); + }; + const onDrop = (item, { target, index }) => { + // Add item to target children array + setFilter(produce(filter, (nextFilter) => { + // DANGER: BE SURE TO ONLY REFERENCE SOMETHING FROM nextFilter, + // AND NOTHING FROM 'OUTSIDE' LIKE item, or target. THESE REFERENCES + // ARE NOT UP-TO-DATE! + var _a, _b, _c; + // Find target in nextFilter + const dropTarget = getFilterFromTree(nextFilter, target.id); + const dropItem = getFilterFromTree(nextFilter, item.id); + // Check if the dropped item is part of the tree already + if (dropItem.current) { + // If we have a parent to remove us from... + if ((_a = dropItem.parent) === null || _a === void 0 ? void 0 : _a.children) { + // Find the index of the current element in the parents children + const deleteIndex = dropItem.parent.children.indexOf(dropItem.current); + // Remove it from the current parent + if (deleteIndex !== -1) { + (_c = (_b = dropItem.parent) === null || _b === void 0 ? void 0 : _b.children) === null || _c === void 0 ? void 0 : _c.splice(deleteIndex, 1); + } + } + } + else { + // Otherwise, it is a new item to be added in the next step + dropItem.current = { ...item, id: uuidv4() }; + } + if (dropTarget.current) { + // Next, add it as target child + if (!dropTarget.current.children) { + dropTarget.current.children = []; + } + dropTarget.current.children.splice(index, 0, dropItem.current); + } + else { + console.error('Something is wrong'); + } + })); + }; + const onChange = (newFilter, changeFunc) => { + setFilter(produce(filter, (nextFilter) => { + const { current, parent } = getFilterFromTree(nextFilter, newFilter.id); + if (current) { + changeFunc(current); + } + })); + }; + if (filter.type !== 'group') { + throw Error('First filter always has to be a group filter!'); + } + return (React.createElement(DndProvider, { backend: HTML5Backend }, + React.createElement("div", { className: "row" }, + React.createElement("div", { className: "col-md" }, + React.createElement("h6", null, "Your filters"), + React.createElement(FilterCard, { filter: filter, onDrop: onDrop, onDelete: onDelete, onChange: onChange, onValueChanged: (filter, value, field) => { + onChange(filter, (f) => { + if (value !== undefined) { + f.value = value; + } + if (field !== undefined) { + f.field = field; + } + }); + }, filterComponents: filterComponents, disableFilter: disableFilter, isInvalid: isInvalid, disableDragging: true, disableRemoving: true })), + filterSelection ? + React.createElement("div", { className: "col-md" }, + React.createElement("h6", null, "New filters"), + filterSelection.map((f) => (React.createElement(FilterCard, { key: f.id, filter: f, filterComponents: filterComponents, disableFilter: disableFilter })))) + : null))); +} +//# sourceMappingURL=CDCFilterCreator.js.map \ No newline at end of file diff --git a/dist/cdc/creator/CDCFilterCreator.js.map b/dist/cdc/creator/CDCFilterCreator.js.map new file mode 100644 index 000000000..862c6e8f0 --- /dev/null +++ b/dist/cdc/creator/CDCFilterCreator.js.map @@ -0,0 +1 @@ +{"version":3,"file":"CDCFilterCreator.js","sourceRoot":"","sources":["../../../src/cdc/creator/CDCFilterCreator.tsx"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/dist/cdc/creator/DropZone.d.ts b/dist/cdc/creator/DropZone.d.ts new file mode 100644 index 000000000..a3370c5a1 --- /dev/null +++ b/dist/cdc/creator/DropZone.d.ts @@ -0,0 +1,13 @@ +/// +import { IFilter } from '../interfaces'; +interface IDropZoneProps { + onDrop: (item: IFilter, { target, index }: { + target: IFilter; + index: number; + }) => void; + canDrop: boolean; + filter: IFilter; + index: number; +} +export declare function DropZone({ canDrop, onDrop, filter, index }: IDropZoneProps): JSX.Element; +export {}; diff --git a/dist/cdc/creator/DropZone.js b/dist/cdc/creator/DropZone.js new file mode 100644 index 000000000..e3638e009 --- /dev/null +++ b/dist/cdc/creator/DropZone.js @@ -0,0 +1,25 @@ +import * as React from 'react'; +import { getFilterFromTree, ITEM_TYPES } from '../interfaces'; +import { useDrop } from 'react-dnd'; +export function DropZone({ canDrop, onDrop, filter, index }) { + const [{ isOver, draggedItem }, drop] = useDrop(() => ({ + accept: ITEM_TYPES.FILTERCARD, + drop: (item) => { + onDrop(item, { target: filter, index }); + }, + canDrop: () => canDrop, + collect: (monitor) => ({ + isOver: !!monitor.isOver(), + draggedItem: monitor.getItem() + }) + }), [canDrop, onDrop, filter, index]); + // Check if the dragged item is actually our parent, because then we are no target + const isDraggedItemParent = React.useMemo(() => { + // If we can find the current filter given our dragged item as parent, we are a child + return draggedItem && !!getFilterFromTree(draggedItem, filter.id).current; + }, [draggedItem, filter]); + const isVisible = !isDraggedItemParent && draggedItem !== filter && draggedItem && canDrop; + return (React.createElement("div", { ref: drop, style: { opacity: 0.1 }, className: `border mt-1 mb-1 ${isVisible && isOver ? 'bg-primary' : isVisible ? 'bg-dark' : ''}` }, + React.createElement(React.Fragment, null, "\u00A0"))); +} +//# sourceMappingURL=DropZone.js.map \ No newline at end of file diff --git a/dist/cdc/creator/DropZone.js.map b/dist/cdc/creator/DropZone.js.map new file mode 100644 index 000000000..ec75e2863 --- /dev/null +++ b/dist/cdc/creator/DropZone.js.map @@ -0,0 +1 @@ +{"version":3,"file":"DropZone.js","sourceRoot":"","sources":["../../../src/cdc/creator/DropZone.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAC/B,OAAO,EAAE,iBAAiB,EAAW,UAAU,EAAE,MAAM,eAAe,CAAC;AACvE,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AASpC,MAAM,UAAU,QAAQ,CAAC,EAAC,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,KAAK,EAAiB;IACvE,MAAM,CAAC,EAAE,MAAM,EAAE,WAAW,EAAE,EAAE,IAAI,CAAC,GAAG,OAAO,CAI7C,GAAG,EAAE,CAAC,CAAC;QACL,MAAM,EAAE,UAAU,CAAC,UAAU;QAC7B,IAAI,EAAE,CAAC,IAAI,EAAE,EAAE;YACb,MAAM,CAAC,IAAI,EAAE,EAAE,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC,CAAC;QAC1C,CAAC;QACD,OAAO,EAAE,GAAG,EAAE,CAAC,OAAO;QACtB,OAAO,EAAE,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC;YACrB,MAAM,EAAE,CAAC,CAAC,OAAO,CAAC,MAAM,EAAE;YAC1B,WAAW,EAAE,OAAO,CAAC,OAAO,EAAE;SAC/B,CAAC;KACH,CAAC,EACF,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,KAAK,CAAC,CACjC,CAAC;IAEF,kFAAkF;IAClF,MAAM,mBAAmB,GAAG,KAAK,CAAC,OAAO,CAAC,GAAG,EAAE;QAC7C,qFAAqF;QACrF,OAAO,WAAW,IAAI,CAAC,CAAC,iBAAiB,CAAC,WAAW,EAAE,MAAM,CAAC,EAAE,CAAC,CAAC,OAAO,CAAC;IAC5E,CAAC,EAAE,CAAC,WAAW,EAAE,MAAM,CAAC,CAAC,CAAC;IAE1B,MAAM,SAAS,GACb,CAAC,mBAAmB,IAAI,WAAW,KAAK,MAAM,IAAI,WAAW,IAAI,OAAO,CAAC;IAE3E,OAAO,CACL,6BACE,GAAG,EAAE,IAAI,EACT,KAAK,EAAE,EAAE,OAAO,EAAE,GAAG,EAAE,EACvB,SAAS,EAAE,oBACT,SAAS,IAAI,MAAM,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,EAC/D,EAAE;QAEF,mDAAW,CACP,CACP,CAAC;AACJ,CAAC"} \ No newline at end of file diff --git a/dist/cdc/creator/index.d.ts b/dist/cdc/creator/index.d.ts new file mode 100644 index 000000000..7b7c37bbf --- /dev/null +++ b/dist/cdc/creator/index.d.ts @@ -0,0 +1,2 @@ +export * from './CDCFilterCard'; +export * from './CDCFilterCreator'; diff --git a/dist/cdc/creator/index.js b/dist/cdc/creator/index.js new file mode 100644 index 000000000..424fac1af --- /dev/null +++ b/dist/cdc/creator/index.js @@ -0,0 +1,4 @@ +export * from './CDCFilterCard'; +export * from './CDCFilterCreator'; +// export * from './DropZone'; +//# sourceMappingURL=index.js.map \ No newline at end of file diff --git a/dist/cdc/creator/index.js.map b/dist/cdc/creator/index.js.map new file mode 100644 index 000000000..dd13c0835 --- /dev/null +++ b/dist/cdc/creator/index.js.map @@ -0,0 +1 @@ +{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/cdc/creator/index.ts"],"names":[],"mappings":"AAAA,cAAc,iBAAiB,CAAC;AAChC,cAAc,oBAAoB,CAAC;AACnC,8BAA8B"} \ No newline at end of file diff --git a/dist/cdc/filter/CDCCheckboxFilter.d.ts b/dist/cdc/filter/CDCCheckboxFilter.d.ts new file mode 100644 index 000000000..fa061d2d9 --- /dev/null +++ b/dist/cdc/filter/CDCCheckboxFilter.d.ts @@ -0,0 +1,10 @@ +/// +import { IFilter, IFilterComponent, IFilterComponentProps } from '../interfaces'; +interface ICDCCheckboxFilterValue { + [field: string]: boolean; +} +export declare const CDCCheckboxFilterId = "checkbox"; +export declare const CDCCheckboxFilter: IFilterComponent; +export declare function createCDCCheckboxFilter(id: string, value: ICDCCheckboxFilterValue): IFilter; +export declare function CDCCheckboxFilterComponent({ value, onValueChanged, disabled, config }: IFilterComponentProps): JSX.Element; +export {}; diff --git a/dist/cdc/filter/CDCCheckboxFilter.js b/dist/cdc/filter/CDCCheckboxFilter.js new file mode 100644 index 000000000..6ee971ff9 --- /dev/null +++ b/dist/cdc/filter/CDCCheckboxFilter.js @@ -0,0 +1,39 @@ +import * as React from 'react'; +import { uniqueId } from 'lodash'; +/* tslint:disable-next-line:variable-name */ +export const CDCCheckboxFilterId = 'checkbox'; +/* tslint:disable-next-line:variable-name */ +export const CDCCheckboxFilter = { + clazz: CDCCheckboxFilterComponent, + disableDropping: true +}; +export function createCDCCheckboxFilter(id, value) { + return { + id, + type: CDCCheckboxFilterId, + value + }; +} +export function CDCCheckboxFilterComponent({ value, onValueChanged, disabled, config }) { + const id = React.useMemo(() => uniqueId('CDCCheckboxFilterComponent'), []); + return React.createElement(React.Fragment, null, config.fields.map((field, i) => { + return (React.createElement("div", { key: i, className: "input-group m-1" }, + React.createElement("div", { className: "form-check" }, + React.createElement("input", { className: "form-check-input", type: "checkbox", value: "", id: `${id}-${field}`, disabled: disabled, checked: value[field], onChange: (e) => { + if (value[field] === false) { + const newVal = {}; + Object.keys(value).forEach((key) => { + if (key !== field) { + newVal[key] = value[key]; + } + }); + onValueChanged === null || onValueChanged === void 0 ? void 0 : onValueChanged(newVal); + } + else { + onValueChanged === null || onValueChanged === void 0 ? void 0 : onValueChanged({ ...value, [field]: e.target.checked }); + } + } }), + React.createElement("label", { className: "form-check-label", htmlFor: `${id}-${field}` }, field)))); + })); +} +//# sourceMappingURL=CDCCheckboxFilter.js.map \ No newline at end of file diff --git a/dist/cdc/filter/CDCCheckboxFilter.js.map b/dist/cdc/filter/CDCCheckboxFilter.js.map new file mode 100644 index 000000000..b3fb84dba --- /dev/null +++ b/dist/cdc/filter/CDCCheckboxFilter.js.map @@ -0,0 +1 @@ +{"version":3,"file":"CDCCheckboxFilter.js","sourceRoot":"","sources":["../../../src/cdc/filter/CDCCheckboxFilter.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAC/B,OAAO,EAAC,QAAQ,EAAC,MAAM,QAAQ,CAAC;AAMhC,4CAA4C;AAC5C,MAAM,CAAC,MAAM,mBAAmB,GAAG,UAAU,CAAC;AAC9C,4CAA4C;AAC5C,MAAM,CAAC,MAAM,iBAAiB,GAA2B;IACvD,KAAK,EAAE,0BAA0B;IACjC,eAAe,EAAE,IAAI;CACtB,CAAC;AAEF,MAAM,UAAU,uBAAuB,CAAC,EAAU,EAAE,KAA8B;IAChF,OAAO;QACL,EAAE;QACF,IAAI,EAAE,mBAAmB;QACzB,KAAK;KACN,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,0BAA0B,CAAC,EAAC,KAAK,EAAE,cAAc,EAAE,QAAQ,EAAE,MAAM,EAAiD;IAClI,MAAM,EAAE,GAAG,KAAK,CAAC,OAAO,CAAC,GAAG,EAAE,CAAC,QAAQ,CAAC,4BAA4B,CAAC,EAAE,EAAE,CAAC,CAAC;IAE3E,OAAO,0CACJ,MAAM,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,CAAC,EAAE,EAAE;QAC9B,OAAO,CACL,6BAAK,GAAG,EAAE,CAAC,EAAE,SAAS,EAAC,iBAAiB;YACtC,6BAAK,SAAS,EAAC,YAAY;gBACzB,+BAAO,SAAS,EAAC,kBAAkB,EAAC,IAAI,EAAC,UAAU,EAAC,KAAK,EAAC,EAAE,EAAC,EAAE,EAAE,GAAG,EAAE,IAAI,KAAK,EAAE,EAC/E,QAAQ,EAAE,QAAQ,EAClB,OAAO,EAAE,KAAK,CAAC,KAAK,CAAC,EACrB,QAAQ,EAAE,CAAC,CAAC,EAAE,EAAE;wBACd,IAAI,KAAK,CAAC,KAAK,CAAC,KAAK,KAAK,EAAE;4BAC1B,MAAM,MAAM,GAAG,EAAE,CAAC;4BAClB,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,OAAO,CAAC,CAAC,GAAG,EAAE,EAAE;gCACjC,IAAI,GAAG,KAAK,KAAK,EAAE;oCACjB,MAAM,CAAC,GAAG,CAAC,GAAG,KAAK,CAAC,GAAG,CAAC,CAAC;iCAC1B;4BACH,CAAC,CAAC,CAAC;4BACH,cAAc,aAAd,cAAc,uBAAd,cAAc,CAAG,MAAM,EAAE;yBAC1B;6BAAM;4BACL,cAAc,aAAd,cAAc,uBAAd,cAAc,CAAG,EAAC,GAAG,KAAK,EAAE,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC,MAAM,CAAC,OAAO,EAAC,EAAE;yBACzD;oBACH,CAAC,GAAI;gBACP,+BACE,SAAS,EAAC,kBAAkB,EAC5B,OAAO,EAAE,GAAG,EAAE,IAAI,KAAK,EAAE,IAExB,KAAK,CACA,CACJ,CACF,CACP,CAAC;IACJ,CAAC,CAAC,CACD,CAAC;AACN,CAAC"} \ No newline at end of file diff --git a/dist/cdc/filter/CDCGroupingFilter.d.ts b/dist/cdc/filter/CDCGroupingFilter.d.ts new file mode 100644 index 000000000..7af3d9bf9 --- /dev/null +++ b/dist/cdc/filter/CDCGroupingFilter.d.ts @@ -0,0 +1,4 @@ +import { IFilter, IFilterComponent } from '../interfaces'; +export declare const CDCGroupingFilterId = "group"; +export declare const CDCGroupingFilter: IFilterComponent; +export declare function createCDCGroupingFilter(id: string): IFilter; diff --git a/dist/cdc/filter/CDCGroupingFilter.js b/dist/cdc/filter/CDCGroupingFilter.js new file mode 100644 index 000000000..567823601 --- /dev/null +++ b/dist/cdc/filter/CDCGroupingFilter.js @@ -0,0 +1,21 @@ +import * as React from 'react'; +/* tslint:disable-next-line:variable-name */ +export const CDCGroupingFilterId = 'group'; +/* tslint:disable-next-line:variable-name */ +export const CDCGroupingFilter = { + clazz: CDCGroupingFilterComponent, + disableDropping: true +}; +export function createCDCGroupingFilter(id) { + return { + id, + operator: 'AND', + children: [], + type: CDCGroupingFilterId, + }; +} +function CDCGroupingFilterComponent() { + return React.createElement("div", null, + React.createElement("br", null)); +} +//# sourceMappingURL=CDCGroupingFilter.js.map \ No newline at end of file diff --git a/dist/cdc/filter/CDCGroupingFilter.js.map b/dist/cdc/filter/CDCGroupingFilter.js.map new file mode 100644 index 000000000..33982660a --- /dev/null +++ b/dist/cdc/filter/CDCGroupingFilter.js.map @@ -0,0 +1 @@ +{"version":3,"file":"CDCGroupingFilter.js","sourceRoot":"","sources":["../../../src/cdc/filter/CDCGroupingFilter.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAE/B,4CAA4C;AAC5C,MAAM,CAAC,MAAM,mBAAmB,GAAG,OAAO,CAAC;AAC3C,4CAA4C;AAC5C,MAAM,CAAC,MAAM,iBAAiB,GAA2B;IACvD,KAAK,EAAE,0BAA0B;IACjC,eAAe,EAAE,IAAI;CACtB,CAAC;AAEF,MAAM,UAAU,uBAAuB,CAAC,EAAU;IAChD,OAAO;QACL,EAAE;QACF,QAAQ,EAAE,KAAK;QACf,QAAQ,EAAE,EAAE;QACZ,IAAI,EAAE,mBAAmB;KAC1B,CAAC;AACJ,CAAC;AAED,SAAS,0BAA0B;IACjC,OAAO;QAAK,+BAAM,CAAM,CAAC;AAC3B,CAAC"} \ No newline at end of file diff --git a/dist/cdc/filter/CDCRangeFilter.d.ts b/dist/cdc/filter/CDCRangeFilter.d.ts new file mode 100644 index 000000000..b8dba4b1c --- /dev/null +++ b/dist/cdc/filter/CDCRangeFilter.d.ts @@ -0,0 +1,8 @@ +import { IFilter, IFilterComponent } from '../interfaces'; +export interface ICDCRangeFilterValue { + min: number; + max: number; +} +export declare const CDCRangeFilterId = "range"; +export declare const CDCRangeFilter: IFilterComponent; +export declare function createCDCRangeFilter(id: string, field: string, value: ICDCRangeFilterValue): IFilter; diff --git a/dist/cdc/filter/CDCRangeFilter.js b/dist/cdc/filter/CDCRangeFilter.js new file mode 100644 index 000000000..af6b9cf4e --- /dev/null +++ b/dist/cdc/filter/CDCRangeFilter.js @@ -0,0 +1,25 @@ +import * as React from 'react'; +import InputRange from 'react-input-range'; +/* tslint:disable-next-line:variable-name */ +export const CDCRangeFilterId = 'range'; +/* tslint:disable-next-line:variable-name */ +export const CDCRangeFilter = { + clazz: CDCRangeFilterComponent, + disableDropping: true +}; +export function createCDCRangeFilter(id, field, value) { + return { + id, + type: CDCRangeFilterId, + field, + value, + }; +} +function CDCRangeFilterComponent({ value, onValueChanged, disabled, config, field }) { + return React.createElement("div", { className: "t360-input-range-wrapper row", style: { margin: '10px', paddingTop: '10px', minHeight: '50px' } }, + React.createElement("div", { className: "col-2 px-0" }, + React.createElement("h6", null, field)), + React.createElement("div", { className: "col-10 px-0" }, + React.createElement(InputRange, { disabled: !onValueChanged || disabled, minValue: config.minValue, maxValue: config.maxValue, value: value, onChange: (e) => typeof e === 'number' ? undefined : onValueChanged === null || onValueChanged === void 0 ? void 0 : onValueChanged(e) }))); +} +//# sourceMappingURL=CDCRangeFilter.js.map \ No newline at end of file diff --git a/dist/cdc/filter/CDCRangeFilter.js.map b/dist/cdc/filter/CDCRangeFilter.js.map new file mode 100644 index 000000000..774438926 --- /dev/null +++ b/dist/cdc/filter/CDCRangeFilter.js.map @@ -0,0 +1 @@ +{"version":3,"file":"CDCRangeFilter.js","sourceRoot":"","sources":["../../../src/cdc/filter/CDCRangeFilter.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAC/B,OAAO,UAAU,MAAM,mBAAmB,CAAC;AAO3C,4CAA4C;AAC5C,MAAM,CAAC,MAAM,gBAAgB,GAAG,OAAO,CAAC;AACxC,4CAA4C;AAC5C,MAAM,CAAC,MAAM,cAAc,GAA2B;IACpD,KAAK,EAAE,uBAAuB;IAC9B,eAAe,EAAE,IAAI;CACtB,CAAC;AAEF,MAAM,UAAU,oBAAoB,CAAC,EAAU,EAAE,KAAa,EAAE,KAA2B;IACzF,OAAO;QACL,EAAE;QACF,IAAI,EAAE,gBAAgB;QACtB,KAAK;QACL,KAAK;KACN,CAAC;AACJ,CAAC;AAED,SAAS,uBAAuB,CAAC,EAAC,KAAK,EAAE,cAAc,EAAE,QAAQ,EAAE,MAAM,EAAE,KAAK,EAA8C;IAC5H,OAAO,6BAAK,SAAS,EAAC,8BAA8B,EAAC,KAAK,EAAE,EAAC,MAAM,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAC;QACjH,6BAAK,SAAS,EAAC,YAAY;YACzB,gCAAK,KAAK,CAAM,CACZ;QACN,6BAAK,SAAS,EAAC,aAAa;YAC1B,oBAAC,UAAU,IACT,QAAQ,EAAE,CAAC,cAAc,IAAI,QAAQ,EACrC,QAAQ,EAAE,MAAM,CAAC,QAAQ,EACzB,QAAQ,EAAE,MAAM,CAAC,QAAQ,EACzB,KAAK,EAAE,KAAK,EACZ,QAAQ,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,OAAO,CAAC,KAAK,QAAQ,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,cAAc,aAAd,cAAc,uBAAd,cAAc,CAAG,CAAC,CAAC,GACxE,CACE,CACF,CAAC;AACT,CAAC"} \ No newline at end of file diff --git a/dist/cdc/filter/CDCTextFilter.d.ts b/dist/cdc/filter/CDCTextFilter.d.ts new file mode 100644 index 000000000..67c814835 --- /dev/null +++ b/dist/cdc/filter/CDCTextFilter.d.ts @@ -0,0 +1,6 @@ +/// +import { IFilter, IFilterComponent, IFilterComponentProps } from '../interfaces'; +export declare const CDCTextFilterId = "text"; +export declare const CDCTextFilter: IFilterComponent; +export declare function createCDCTextFilter(id: string, field: string, value: string[]): IFilter; +export declare function CDCTextFilterComponent({ value, onValueChanged, disabled, field, config }: IFilterComponentProps): JSX.Element; diff --git a/dist/cdc/filter/CDCTextFilter.js b/dist/cdc/filter/CDCTextFilter.js new file mode 100644 index 000000000..5837b6216 --- /dev/null +++ b/dist/cdc/filter/CDCTextFilter.js @@ -0,0 +1,29 @@ +import * as React from 'react'; +import Select from 'react-select'; +/* tslint:disable-next-line:variable-name */ +export const CDCTextFilterId = 'text'; +/* tslint:disable-next-line:variable-name */ +export const CDCTextFilter = { + clazz: CDCTextFilterComponent, + disableDropping: true +}; +export function createCDCTextFilter(id, field, value) { + return { + id, + type: CDCTextFilterId, + field, + value, + }; +} +export function CDCTextFilterComponent({ value, onValueChanged, disabled, field, config }) { + var _a; + return React.createElement(React.Fragment, null, + React.createElement("div", { className: "input-group m-1 row" }, + React.createElement("div", { className: "col-4 p-0" }, + React.createElement(Select, { isDisabled: !onValueChanged || disabled, value: { label: field, value: field }, maxMenuHeight: 200, menuPlacement: "auto", options: [...config === null || config === void 0 ? void 0 : config.map((conf) => { return { label: conf.field, value: conf.field }; })], onChange: (e) => { + onValueChanged === null || onValueChanged === void 0 ? void 0 : onValueChanged([], e.value); + } })), + React.createElement("div", { className: "col-8 p-0" }, + React.createElement(Select, { closeMenuOnSelect: false, isDisabled: !onValueChanged || disabled || !field, maxMenuHeight: 200, menuPlacement: "auto", isMulti: true, value: value === null || value === void 0 ? void 0 : value.map((v) => { return { label: v, value: v }; }), options: (_a = config === null || config === void 0 ? void 0 : config.find((f) => (f === null || f === void 0 ? void 0 : f.field) === field)) === null || _a === void 0 ? void 0 : _a.options.map((o) => { return { label: o, value: o }; }), onChange: (e) => onValueChanged === null || onValueChanged === void 0 ? void 0 : onValueChanged([...e.map((v) => v.value)]) })))); +} +//# sourceMappingURL=CDCTextFilter.js.map \ No newline at end of file diff --git a/dist/cdc/filter/CDCTextFilter.js.map b/dist/cdc/filter/CDCTextFilter.js.map new file mode 100644 index 000000000..57f574020 --- /dev/null +++ b/dist/cdc/filter/CDCTextFilter.js.map @@ -0,0 +1 @@ +{"version":3,"file":"CDCTextFilter.js","sourceRoot":"","sources":["../../../src/cdc/filter/CDCTextFilter.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAC/B,OAAO,MAAM,MAAM,cAAc,CAAC;AAElC,4CAA4C;AAC5C,MAAM,CAAC,MAAM,eAAe,GAAG,MAAM,CAAC;AACtC,4CAA4C;AAC5C,MAAM,CAAC,MAAM,aAAa,GAA2B;IACnD,KAAK,EAAE,sBAAsB;IAC7B,eAAe,EAAE,IAAI;CACtB,CAAC;AAEF,MAAM,UAAU,mBAAmB,CAAC,EAAU,EAAE,KAAa,EAAE,KAAe;IAC5E,OAAO;QACL,EAAE;QACF,IAAI,EAAE,eAAe;QACrB,KAAK;QACL,KAAK;KACN,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,sBAAsB,CAAC,EAAC,KAAK,EAAE,cAAc,EAAE,QAAQ,EAAE,KAAK,EAAE,MAAM,EAAkC;;IACtH,OAAO;QACL,6BAAK,SAAS,EAAC,qBAAqB;YAClC,6BAAK,SAAS,EAAC,WAAW;gBACxB,oBAAC,MAAM,IACL,UAAU,EAAE,CAAC,cAAc,IAAI,QAAQ,EACvC,KAAK,EAAE,EAAC,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK,EAAC,EACnC,aAAa,EAAE,GAAG,EAClB,aAAa,EAAC,MAAM,EACpB,OAAO,EAAE,CAAC,GAAG,MAAM,aAAN,MAAM,uBAAN,MAAM,CAAE,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,GAAE,OAAO,EAAC,KAAK,EAAE,IAAI,CAAC,KAAK,EAAE,KAAK,EAAE,IAAI,CAAC,KAAK,EAAC,CAAC,CAAA,CAAC,CAAC,CAAC,EACrF,QAAQ,EAAE,CAAC,CAAC,EAAE,EAAE;wBACd,cAAc,aAAd,cAAc,uBAAd,cAAc,CAAG,EAAE,EAAE,CAAC,CAAC,KAAK,EAAE;oBAChC,CAAC,GACD,CACE;YACN,6BAAK,SAAS,EAAC,WAAW;gBACxB,oBAAC,MAAM,IACL,iBAAiB,EAAE,KAAK,EACxB,UAAU,EAAE,CAAC,cAAc,IAAI,QAAQ,IAAI,CAAC,KAAK,EACjD,aAAa,EAAE,GAAG,EAClB,aAAa,EAAC,MAAM,EACpB,OAAO,QACP,KAAK,EAAE,KAAK,aAAL,KAAK,uBAAL,KAAK,CAAE,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,GAAE,OAAO,EAAC,KAAK,EAAE,CAAC,EAAE,KAAK,EAAE,CAAC,EAAC,CAAC,CAAA,CAAC,GACvD,OAAO,QAAE,MAAM,aAAN,MAAM,uBAAN,MAAM,CAAE,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAA,CAAC,aAAD,CAAC,uBAAD,CAAC,CAAE,KAAK,MAAK,KAAK,2CAAG,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,GAAE,OAAO,EAAC,KAAK,EAAE,CAAC,EAAE,KAAK,EAAE,CAAC,EAAC,CAAC,CAAA,CAAC,GACnG,QAAQ,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,cAAc,aAAd,cAAc,uBAAd,cAAc,CAAG,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,GAC7D,CACE,CACF,CACL,CAAC;AACN,CAAC"} \ No newline at end of file diff --git a/dist/cdc/filter/index.d.ts b/dist/cdc/filter/index.d.ts new file mode 100644 index 000000000..31a7ed909 --- /dev/null +++ b/dist/cdc/filter/index.d.ts @@ -0,0 +1,4 @@ +export * from './CDCCheckboxFilter'; +export * from './CDCGroupingFilter'; +export * from './CDCRangeFilter'; +export * from './CDCTextFilter'; diff --git a/dist/cdc/filter/index.js b/dist/cdc/filter/index.js new file mode 100644 index 000000000..b3b3f74d8 --- /dev/null +++ b/dist/cdc/filter/index.js @@ -0,0 +1,5 @@ +export * from './CDCCheckboxFilter'; +export * from './CDCGroupingFilter'; +export * from './CDCRangeFilter'; +export * from './CDCTextFilter'; +//# sourceMappingURL=index.js.map \ No newline at end of file diff --git a/dist/cdc/filter/index.js.map b/dist/cdc/filter/index.js.map new file mode 100644 index 000000000..74a6d7fb2 --- /dev/null +++ b/dist/cdc/filter/index.js.map @@ -0,0 +1 @@ +{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/cdc/filter/index.ts"],"names":[],"mappings":"AAAA,cAAc,qBAAqB,CAAC;AACpC,cAAc,qBAAqB,CAAC;AACpC,cAAc,kBAAkB,CAAC;AACjC,cAAc,iBAAiB,CAAC"} \ No newline at end of file diff --git a/dist/cdc/index.d.ts b/dist/cdc/index.d.ts new file mode 100644 index 000000000..770afb82d --- /dev/null +++ b/dist/cdc/index.d.ts @@ -0,0 +1,7 @@ +export * from './alert'; +export * from './common'; +export * from './creator'; +export * from './filter'; +export * from './api'; +export * from './CDCFilterDialog'; +export * from './interfaces'; diff --git a/dist/cdc/index.js b/dist/cdc/index.js new file mode 100644 index 000000000..124b11c7f --- /dev/null +++ b/dist/cdc/index.js @@ -0,0 +1,8 @@ +export * from './alert'; +export * from './common'; +export * from './creator'; +export * from './filter'; +export * from './api'; +export * from './CDCFilterDialog'; +export * from './interfaces'; +//# sourceMappingURL=index.js.map \ No newline at end of file diff --git a/dist/cdc/index.js.map b/dist/cdc/index.js.map new file mode 100644 index 000000000..90c396bea --- /dev/null +++ b/dist/cdc/index.js.map @@ -0,0 +1 @@ +{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/cdc/index.ts"],"names":[],"mappings":"AAAA,cAAc,SAAS,CAAC;AACxB,cAAc,UAAU,CAAC;AACzB,cAAc,WAAW,CAAC;AAC1B,cAAc,UAAU,CAAC;AACzB,cAAc,OAAO,CAAC;AACtB,cAAc,mBAAmB,CAAC;AAClC,cAAc,cAAc,CAAC"} \ No newline at end of file diff --git a/dist/cdc/interfaces.d.ts b/dist/cdc/interfaces.d.ts new file mode 100644 index 000000000..76d78b776 --- /dev/null +++ b/dist/cdc/interfaces.d.ts @@ -0,0 +1,77 @@ +/// +import { ISecureItem } from '../security'; +export interface IFilterComponentProps { + value: V; + onValueChanged?: (value: V, field?: string) => void; + disabled: boolean; + config: any; + field?: any; +} +export interface IFilterComponent { + clazz: (props: IFilterComponentProps) => JSX.Element; + disableDropping?: boolean; +} +export interface IFilter { + id: string; + operator?: 'AND' | 'OR'; + type: string; + value?: any; + field?: string; + children?: IFilter[]; +} +export interface ICDCConfiguration { + filters: IFilter[]; + components: { + [key: string]: { + component: IFilterComponent; + config?: any; + }; + }; + compareColumns: string[]; +} +export declare const ITEM_TYPES: { + FILTERCARD: string; +}; +export declare const getFilterFromTree: (filter: IFilter, id: string) => { + parent: IFilter | null; + current: IFilter | null; +}; +export interface IAlert extends ISecureItem { + id: number; + name: string; + cdc_id: string; + filter: IFilter; + enable_mail_notification: boolean; + latest_diff?: { + dictionary_item_added?: string[]; + dictionary_item_removed?: string[]; + values_changed?: { + id: string; + field: []; + old_value: string; + new_value: string; + }[]; + }; + latest_fetched_data?: { + _cdc_compare_id: string; + [key: string]: any; + }[]; + latest_compare_date?: Date; + modification_date?: string; + confirmed_data?: { + _cdc_compare_id: string; + [key: string]: any; + }[]; + confirmation_date: Date; + compare_columns: string[]; + latest_error?: string; + latest_error_date?: Date; +} +export interface IUploadAlert extends Pick { + compare?: string[]; +} +export declare function isAlert(obj: IAlert | IUploadAlert): obj is IAlert; +export interface IReactSelectOption { + value: string; + label: string; +} diff --git a/dist/cdc/interfaces.js b/dist/cdc/interfaces.js new file mode 100644 index 000000000..530591b18 --- /dev/null +++ b/dist/cdc/interfaces.js @@ -0,0 +1,28 @@ +export const ITEM_TYPES = { + FILTERCARD: 'filtercard' +}; +export const getFilterFromTree = (filter, id) => { + if ((filter === null || filter === void 0 ? void 0 : filter.id) === id) { + return { current: filter, parent: null }; + } + else if (filter && filter.children) { + // Is the id part of my children? + const current = filter.children.find((f) => f.id === id); + if (current) { + return { parent: filter, current }; + } + // Otherwise, continue with all children + for (const f of filter.children) { + const current = getFilterFromTree(f, id); + if (current.current) { + return current; + } + } + } + return { parent: null, current: null }; +}; +export function isAlert(obj) { + var _a; + return typeof ((_a = obj) === null || _a === void 0 ? void 0 : _a.id) === 'number'; +} +//# sourceMappingURL=interfaces.js.map \ No newline at end of file diff --git a/dist/cdc/interfaces.js.map b/dist/cdc/interfaces.js.map new file mode 100644 index 000000000..f3f1caa24 --- /dev/null +++ b/dist/cdc/interfaces.js.map @@ -0,0 +1 @@ +{"version":3,"file":"interfaces.js","sourceRoot":"","sources":["../../src/cdc/interfaces.ts"],"names":[],"mappings":"AA8BA,MAAM,CAAC,MAAM,UAAU,GAAG;IACxB,UAAU,EAAE,YAAY;CACzB,CAAC;AAEF,MAAM,CAAC,MAAM,iBAAiB,GAAG,CAC/B,MAAe,EACf,EAAU,EACyC,EAAE;IACrD,IAAI,CAAA,MAAM,aAAN,MAAM,uBAAN,MAAM,CAAE,EAAE,MAAK,EAAE,EAAE;QACrB,OAAO,EAAC,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,IAAI,EAAC,CAAC;KACxC;SAAM,IAAI,MAAM,IAAI,MAAM,CAAC,QAAQ,EAAE;QACpC,iCAAiC;QACjC,MAAM,OAAO,GAAG,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC;QACzD,IAAI,OAAO,EAAE;YACX,OAAO,EAAC,MAAM,EAAE,MAAM,EAAE,OAAO,EAAC,CAAC;SAClC;QACD,wCAAwC;QACxC,KAAK,MAAM,CAAC,IAAI,MAAM,CAAC,QAAQ,EAAE;YAC/B,MAAM,OAAO,GAAG,iBAAiB,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;YACzC,IAAI,OAAO,CAAC,OAAO,EAAE;gBACnB,OAAO,OAAO,CAAC;aAChB;SACF;KACF;IACD,OAAO,EAAC,MAAM,EAAE,IAAI,EAAE,OAAO,EAAE,IAAI,EAAC,CAAC;AACvC,CAAC,CAAC;AA6BF,MAAM,UAAU,OAAO,CAAC,GAA0B;;IAChD,OAAO,cAAQ,GAAW,0CAAE,EAAE,CAAA,KAAK,QAAQ,CAAC;AAC9C,CAAC"} \ No newline at end of file diff --git a/dist/components/dialogs.d.ts b/dist/components/dialogs.d.ts index 706b80bde..e17cc6bc1 100644 --- a/dist/components/dialogs.d.ts +++ b/dist/components/dialogs.d.ts @@ -1,5 +1,6 @@ /// /// +/// /// import '../webpack/_bootstrap'; export interface IDialogOptions { diff --git a/dist/dummy/DummyReactView.d.ts b/dist/dummy/DummyReactView.d.ts index 93050f4dd..b2363a9e6 100644 --- a/dist/dummy/DummyReactView.d.ts +++ b/dist/dummy/DummyReactView.d.ts @@ -1,3 +1,4 @@ +/// import { ISelector } from '../views/AReactView'; import { AReactChooserView } from '../views/AReactChooserView'; export declare class DummyReactView extends AReactChooserView { diff --git a/dist/index.d.ts b/dist/index.d.ts index 13fae3aef..da703fba2 100644 --- a/dist/index.d.ts +++ b/dist/index.d.ts @@ -1,4 +1,5 @@ export * from './auth'; +export * from './cdc'; export * from './public'; export * from './base'; export * from './tour'; diff --git a/dist/index.js b/dist/index.js index e51a7e5f7..633ae243e 100644 --- a/dist/index.js +++ b/dist/index.js @@ -1,4 +1,5 @@ export * from './auth'; +export * from './cdc'; export * from './public'; export * from './base'; export * from './tour'; diff --git a/dist/index.js.map b/dist/index.js.map index d2dec14dc..d30354f32 100644 --- a/dist/index.js.map +++ b/dist/index.js.map @@ -1 +1 @@ -{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,QAAQ,CAAC;AACvB,cAAc,UAAU,CAAC;AACzB,cAAc,QAAQ,CAAC;AACvB,cAAc,QAAQ,CAAC;AACvB,cAAc,QAAQ,CAAC;AACvB,cAAc,SAAS,CAAC;AACxB,cAAc,SAAS,CAAC;AACxB,cAAc,WAAW,CAAC;AAC1B,cAAc,UAAU,CAAC;AACzB,cAAc,SAAS,CAAC;AACxB,cAAc,WAAW,CAAC;AAC1B,cAAc,mBAAmB,CAAC;AAClC,cAAc,OAAO,CAAC;AACtB,cAAc,QAAQ,CAAC;AACvB,cAAc,UAAU,CAAC;AACzB,cAAc,cAAc,CAAC;AAC7B,cAAc,MAAM,CAAC;AACrB,cAAc,YAAY,CAAC;AAC3B,cAAc,OAAO,CAAC;AACtB,cAAc,QAAQ,CAAC;AACvB,cAAc,QAAQ,CAAC;AACvB,cAAc,SAAS,CAAC;AACxB,cAAc,UAAU,CAAC;AACzB,cAAc,QAAQ,CAAC;AACvB,cAAc,QAAQ,CAAC;AACvB,cAAc,UAAU,CAAC;AACzB,cAAc,kBAAkB,CAAC;AACjC,cAAc,SAAS,CAAC;AACxB,cAAc,UAAU,CAAC;AACzB,cAAc,SAAS,CAAC;AACxB,cAAc,cAAc,CAAC;AAC7B,cAAc,aAAa,CAAC;AAC5B,cAAc,QAAQ,CAAC;AACvB,cAAc,SAAS,CAAC;AACxB,cAAc,gBAAgB,CAAC;AAC/B,cAAc,aAAa,CAAC;AAC5B,cAAc,UAAU,CAAC"} \ No newline at end of file +{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,QAAQ,CAAC;AACvB,cAAc,OAAO,CAAC;AACtB,cAAc,UAAU,CAAC;AACzB,cAAc,QAAQ,CAAC;AACvB,cAAc,QAAQ,CAAC;AACvB,cAAc,QAAQ,CAAC;AACvB,cAAc,SAAS,CAAC;AACxB,cAAc,SAAS,CAAC;AACxB,cAAc,WAAW,CAAC;AAC1B,cAAc,UAAU,CAAC;AACzB,cAAc,SAAS,CAAC;AACxB,cAAc,WAAW,CAAC;AAC1B,cAAc,mBAAmB,CAAC;AAClC,cAAc,OAAO,CAAC;AACtB,cAAc,QAAQ,CAAC;AACvB,cAAc,UAAU,CAAC;AACzB,cAAc,cAAc,CAAC;AAC7B,cAAc,MAAM,CAAC;AACrB,cAAc,YAAY,CAAC;AAC3B,cAAc,OAAO,CAAC;AACtB,cAAc,QAAQ,CAAC;AACvB,cAAc,QAAQ,CAAC;AACvB,cAAc,SAAS,CAAC;AACxB,cAAc,UAAU,CAAC;AACzB,cAAc,QAAQ,CAAC;AACvB,cAAc,QAAQ,CAAC;AACvB,cAAc,UAAU,CAAC;AACzB,cAAc,kBAAkB,CAAC;AACjC,cAAc,SAAS,CAAC;AACxB,cAAc,UAAU,CAAC;AACzB,cAAc,SAAS,CAAC;AACxB,cAAc,cAAc,CAAC;AAC7B,cAAc,aAAa,CAAC;AAC5B,cAAc,QAAQ,CAAC;AACvB,cAAc,SAAS,CAAC;AACxB,cAAc,gBAAgB,CAAC;AAC/B,cAAc,aAAa,CAAC;AAC5B,cAAc,UAAU,CAAC"} \ No newline at end of file diff --git a/dist/lineup/internal/ScoreUtils.d.ts b/dist/lineup/internal/ScoreUtils.d.ts index 9725e7526..347d03091 100644 --- a/dist/lineup/internal/ScoreUtils.d.ts +++ b/dist/lineup/internal/ScoreUtils.d.ts @@ -4,12 +4,18 @@ export declare class ScoreUtils { static readonly CMD_ADD_SCORE = "tdpAddScore"; static readonly CMD_REMOVE_SCORE = "tdpRemoveScore"; private static addScoreLogic; - static addScoreImpl(inputs: IObjectRef[], parameter: any): any; - static addScoreAsync(inputs: IObjectRef[], parameter: any): Promise; - static removeScoreImpl(inputs: IObjectRef[], parameter: any): any; - static addScore(provider: IObjectRef, scoreName: string, scoreId: string, params: any): any; + static addScoreImpl(inputs: IObjectRef[], parameter: any): Promise<{ + inverse: import("../../provenance").IAction; + }>; + static addScoreAsync(inputs: IObjectRef[], parameter: any): Promise<{ + inverse: import("../../provenance").IAction; + }>; + static removeScoreImpl(inputs: IObjectRef[], parameter: any): Promise<{ + inverse: import("../../provenance").IAction; + }>; + static addScore(provider: IObjectRef, scoreName: string, scoreId: string, params: any): import("../../provenance").IAction; static pushScoreAsync(graph: ProvenanceGraph, provider: IObjectRef, scoreName: string, scoreId: string, params: any): Promise; - static removeScore(provider: IObjectRef, scoreName: string, scoreId: string, params: any, columnId: string | string[]): any; + static removeScore(provider: IObjectRef, scoreName: string, scoreId: string, params: any, columnId: string | string[]): import("../../provenance").IAction; private static shallowEqualObjects; /** * compresses score creation and removal diff --git a/dist/stratification/Stratification.d.ts b/dist/stratification/Stratification.d.ts index a83193e77..62428ed58 100644 --- a/dist/stratification/Stratification.d.ts +++ b/dist/stratification/Stratification.d.ts @@ -29,7 +29,7 @@ export declare class Stratification extends ADataType import { Column } from 'react-table'; export interface ITableProps { columns: Column[]; diff --git a/dist/utils/TDPApplicationUtils.d.ts b/dist/utils/TDPApplicationUtils.d.ts index 5127207ce..a3d4d0a5e 100644 --- a/dist/utils/TDPApplicationUtils.d.ts +++ b/dist/utils/TDPApplicationUtils.d.ts @@ -41,10 +41,8 @@ export declare class TDPApplicationUtils { inverse: import("../provenance").IAction; }; static initSession(map: object): import("../provenance").IAction; - static setParameterImpl(inputs: IObjectRef[], parameter: any, graph: ProvenanceGraph): Promise<{ - inverse: import("../provenance").IAction; - }>; - static setParameter(view: IObjectRef, name: string, value: any, previousValue: any): import("../provenance").IAction; + static setParameterImpl(inputs: IObjectRef[], parameter: any, graph: ProvenanceGraph): any; + static setParameter(view: IObjectRef, name: string, value: any, previousValue: any): any; static compressSetParameter(path: ActionNode[]): ActionNode[]; /** * @deprecated diff --git a/package.json b/package.json index 434372632..8b0e4f5e9 100644 --- a/package.json +++ b/package.json @@ -93,6 +93,10 @@ "react-table": "7.0.0-rc.16", "popper.js": "^1.15.0", "plotly.js-dist": "^1.57.1", + "immer": "^9.0.6", + "react-dnd": "^14.0.4", + "react-dnd-html5-backend": "^14.0.2", + "react-select": "^4.1.0", "marked": "~3.0.2", "bootstrap": "~5.1.0", "@popperjs/core": "^2.9.3", @@ -114,6 +118,7 @@ "@types/react-dom": "^16.9.5", "@types/papaparse": "~5.2.4", "identity-obj-proxy": "~3.0.0", + "@types/react-select": "^4.0.14", "jest": "~26.6.3", "jest-raw-loader": "~1.0.1", "rimraf": "~3.0.2", diff --git a/requirements.txt b/requirements.txt index e14dfb90e..06666cebe 100644 --- a/requirements.txt +++ b/requirements.txt @@ -10,7 +10,9 @@ flask-swagger-ui==3.36.0 yamlreader==3.0.4 alembic==1.4.3 openpyxl~=3.0.5 +deepdiff==5.5.0 pyjwt==2.1.0 +flask_apscheduler==1.12.2 -e git+https://github.com/phovea/phovea_server.git@develop#egg=phovea_server # phovea_clue python-memcached==1.59 diff --git a/src/cdc/CDCFilterDialog.tsx b/src/cdc/CDCFilterDialog.tsx new file mode 100644 index 000000000..b4088bf8a --- /dev/null +++ b/src/cdc/CDCFilterDialog.tsx @@ -0,0 +1,193 @@ +import * as React from 'react'; +import ReactDOM from 'react-dom'; +import {BSModal, useAsync} from '../hooks'; +import {IAlert, IUploadAlert, ICDCConfiguration} from './interfaces'; +import {getAlerts, runAllAlerts} from './api'; +import {CDCGroupingFilterId, CDCGroupingFilter, createCDCGroupingFilter, CDCCheckboxFilter, CDCCheckboxFilterId, createCDCCheckboxFilter, CDCRangeFilter, CDCRangeFilterId, createCDCRangeFilter} from './filter'; +import {v4 as uuidv4} from 'uuid'; +import {CDCTextFilter, CDCTextFilterId, createCDCTextFilter} from './filter/CDCTextFilter'; +import {CDCAlertView} from './alert/CDCAlertView'; +import {ErrorMessage} from './common'; + +interface ICDCFilterDialogProps { + cdcConfig: {[cdcId: string]: ICDCConfiguration}; +} + +export const CDC_DEFAULT_ALERT_DATA: () => IUploadAlert = () => ({name: '', enable_mail_notification: false, cdc_id: 'JSONPlaceholderUserCDC', filter: createCDCGroupingFilter(uuidv4()), compare_columns: null}); + +export function CDCFilterDialog({cdcConfig}: ICDCFilterDialogProps) { + const [selectedAlert, setSelectedAlert] = React.useState(null); + const [creationMode, setCreationMode] = React.useState(false); + const [alertData, setAlertData] = React.useState(null); + const [dialogNode, setDialogNode] = React.useState(null); + + const {status: alertStatus, error: alertError, execute: fetchAlerts, value: alerts} = useAsync(getAlerts, []); + + const onAlertChanged = async (id?: number) => { + //refetches alerts and makes new selection + fetchAlerts().then((alerts) => { + //if no id there is no need to iterate through alerts + if (!id) { + setSelectedAlert(null); + } else { + setSelectedAlert(alerts.find((alert) => alert.id === id)); + } + }); + }; + + const {status: syncStatus, error: syncError, execute: doSync} = useAsync(async () => { + const result = await runAllAlerts(); + if (result.error.length > 0) { + throw new Error(`Alert(s) could not be synchronized!`); + } + onAlertChanged(selectedAlert?.id); + }); + + const onCreateButtonClick = () => { + setCreationMode(true); + setSelectedAlert(null); + setAlertData(CDC_DEFAULT_ALERT_DATA()); + }; + + const onAlertClick = async (alert: IAlert) => { + setAlertData(alert); + setCreationMode(false); + setSelectedAlert(alert); + }; + + const reviewStatus = (alert: IAlert) => { + switch (true) { + case (alert.latest_error != null): + return `Error from Sync: ${new Date(alert.latest_error_date)?.toLocaleDateString()}`; + case (alert.latest_diff != null): + return 'Pending data revision'; + case (alert.confirmation_date != null): + return `Last confirmed: ${new Date(alert.confirmation_date)?.toLocaleDateString()}`; + default: + return 'No data revision yet'; + } + }; + + const openDialog = () => { + const dialog = document.createElement('div'); + document.body.appendChild(dialog); + setDialogNode(dialog); + }; + + return <> + openDialog()}> + {dialogNode ? ReactDOM.createPortal( { + if(!show) { + setDialogNode(null); + dialogNode.remove(); + } else if(!dialogNode) { + openDialog(); + } + }}> +
+
+
+
+
Alerts
+ +
+
+
+
+ {syncError ? : null} +
+ + +
+ {alertError ? fetchAlerts()} /> : null} + {alertStatus === 'success' ? : null} +
+
+ {selectedAlert || creationMode ? + + : null + } +
+
+
+
+
+
+
, dialogNode) : null} + ; +} + +export class CDCFilterDialogClass { + private node: HTMLElement; + + constructor(parent: HTMLElement) { + this.node = document.createElement('div'); + parent.appendChild(this.node); + this.init(); + } + + private init() { + ReactDOM.render( + , + this.node + ); + } +} + diff --git a/src/cdc/alert/CDCAlertView.tsx b/src/cdc/alert/CDCAlertView.tsx new file mode 100644 index 000000000..bc23cf3b1 --- /dev/null +++ b/src/cdc/alert/CDCAlertView.tsx @@ -0,0 +1,177 @@ +import React from 'react'; +import Select from 'react-select'; +import {ErrorMessage} from '../common'; +import {useAsync} from '../../hooks'; +import {deleteAlert, editAlert, saveAlert} from '../api'; +import {CDCFilterCreator} from '../creator'; +import {IAlert, IUploadAlert, IReactSelectOption, ICDCConfiguration} from '../interfaces'; +import {CDCDataChangeTable} from './CDCDataChangeTable'; + +interface ICDCEditAlert { + alertData: IUploadAlert; + setAlertData: (formData: IUploadAlert) => void; + onAlertChanged: (id?: number) => void; + selectedAlert?: IAlert; + creationMode: boolean; + setCreationMode: (mode: boolean) => void; + cdcConfig: {[cdcId: string]: ICDCConfiguration}; +} + +export function CDCAlertView({alertData, setAlertData, onAlertChanged, selectedAlert, setCreationMode, creationMode, cdcConfig}: ICDCEditAlert) { + const [editMode, setEditMode] = React.useState(false); + const [deleteMode, setDeleteMode] = React.useState(false); + const [validFilter, setValidFilter] = React.useState(true); + const [validName, setValidName] = React.useState(true); + const [validCompareColumns, setValidCompareColumns] = React.useState(true); + const [activePage, setActivePage] = React.useState<'data' | 'info'>('info'); + + const {status: deleteStatus, error: deleteError, execute: doDelete} = useAsync(async () => { + setEditMode(false); + await deleteAlert(selectedAlert.id); + onAlertChanged(); + }); + + const {status: saveStatus, error: saveError, execute: doSave} = useAsync(async () => { + const valFilter = !!alertData?.filter; + const valName = alertData?.name?.trim().length > 0; + const valCompareColumns = alertData?.compare_columns?.length > 0; + if (valFilter && valName && valCompareColumns) { + let newAlert; + if (selectedAlert) { + newAlert = await editAlert(selectedAlert.id, {...alertData}); + setEditMode(false); + } else { + newAlert = await saveAlert({...alertData}); + setCreationMode(false); + } + onAlertChanged(newAlert.id); + } + setValidFilter(valFilter); + setValidName(valName); + setValidCompareColumns(valCompareColumns); + }); + + // TODO: CDCs are more complex than just filters, i.e. they also have fields. + const cdcs = Object.keys(cdcConfig); + + const filterSelection = cdcConfig[alertData?.cdc_id]?.filters; + const compareColumns = cdcConfig[alertData?.cdc_id]?.compareColumns; + const filterComponents = cdcConfig[alertData?.cdc_id]?.components; + + React.useEffect(() => { + if (selectedAlert) { + setEditMode(false); + setDeleteMode(false); + setActivePage('info'); + } + }, [selectedAlert]); + + + const onDiscard = () => { + setEditMode(false); + setAlertData(selectedAlert); + }; + + return (
+ {selectedAlert?.latest_error ? + + : deleteError ? + + : saveError ? + + : null + } + +
+ {activePage === 'data' && !editMode && !creationMode ? : null} + {activePage === 'info' ? <> +
+
+ + {!creationMode && !editMode ? +
{alertData.name}
+ : + <> setAlertData({...alertData, name: e.target.value})} required /> + {validName ? null : +
+ Name must not be empty! +
} + } +
+
+ + + isDisabled={!creationMode && !editMode} + options={cdcs.map((c) => ({label: c, value: c}))} + value={{label: alertData.cdc_id, value: alertData.cdc_id}} + onChange={(e) => setAlertData({...alertData, cdc_id: e.value, compare_columns: null})} + /> +
+
+ + + isMulti + className={`${validCompareColumns ? '' : 'form-control is-invalid'}`} + isDisabled={!creationMode && !editMode} + closeMenuOnSelect={false} + options={compareColumns?.map((c) => ({label: c, value: c}))} + //check for compare_columns because otherwise it would not reset the selection after the cdc_id was changed + value={alertData.compare_columns ? alertData.compare_columns.map((c) => ({label: c, value: c})) : null} + onChange={(e) => setAlertData({...alertData, compare_columns: e.map((col) => col.value)})} + /> + {validCompareColumns ? null : +
+ Change fields must not be empty! +
+ } +
+
+ +
+ setAlertData({...alertData, enable_mail_notification: e.target.checked})} /> + +
+
+
+
+ {filterSelection && alertData?.filter ? + setAlertData({...alertData, filter})} + isInvalid={!validFilter} /> + : +

No filters available for this cdc

} +
+ : null} +
+
); +} diff --git a/src/cdc/alert/CDCDataChangeTable.tsx b/src/cdc/alert/CDCDataChangeTable.tsx new file mode 100644 index 000000000..2c1df188c --- /dev/null +++ b/src/cdc/alert/CDCDataChangeTable.tsx @@ -0,0 +1,110 @@ +import {get} from 'lodash'; +import React, {useEffect} from 'react'; +import {useAsync} from '../../hooks'; +import {confirmAlertById} from '../api'; +import {ErrorMessage} from '../common/ErrorMessage'; +import {IAlert} from '../interfaces'; + +interface ICDCDataChangeTable { + selectedAlert: IAlert; + onAlertChanged: (id?: number) => void; +} + +export function CDCDataChangeTable({selectedAlert, onAlertChanged}: ICDCDataChangeTable) { + const [dataChange, setDataChange] = React.useState>>(new Map()); + + useEffect(() => { + if (selectedAlert?.latest_diff?.values_changed) { + const change: Map> = new Map(); + selectedAlert.latest_diff?.values_changed?.map((d) => { + const nestedField = d.field.map((f) => f).join('.'); + if (change.has(d.id)) { + change.set(d.id, change.get(d.id).set(nestedField, {old: d.old_value, new: d.new_value})); + } else { + change.set(d.id, new Map().set(nestedField, {old: d.old_value, new: d.new_value})); + } + }); + setDataChange(change); + } else { + setDataChange(new Map()); + } + }, [selectedAlert]); + + const {status: confirmStatus, error: confirmError, execute: doConfirm} = useAsync(async () => { + const alert = await confirmAlertById(selectedAlert.id); + onAlertChanged(alert.id); + }); + + return (<> + {confirmStatus === 'pending' ? + + : <> + {selectedAlert.latest_diff || selectedAlert.confirmed_data ? (<> + + + + + {selectedAlert.compare_columns.map((field, i) => )} + + + + + {selectedAlert.latest_diff ? <> + {selectedAlert.latest_diff?.dictionary_item_added?.map((d) => { + const data = selectedAlert.latest_fetched_data.find((a) => a._cdc_compare_id === d); + return ( + + {selectedAlert.compare_columns.map((field, i) => )} + + ); + })} + {selectedAlert.latest_diff?.dictionary_item_removed?.map((d) => { + const data = selectedAlert.confirmed_data.find((a) => a._cdc_compare_id === d); + return ( + + {selectedAlert.compare_columns.map((field, i) => )} + + ); + })} + : null} + {selectedAlert.confirmed_data ? <> + {selectedAlert.confirmed_data + // Only show entries which are not already shown above + .filter((item) => !selectedAlert.latest_diff?.dictionary_item_added?.includes(item._cdc_compare_id) && !selectedAlert.latest_diff?.dictionary_item_removed?.includes(item._cdc_compare_id)) + // Sort such that rows with changes are on top + .sort((a, b) => (dataChange.has(b._cdc_compare_id) ? 1 : 0) - (dataChange.has(a._cdc_compare_id) ? 1 : 0)).map((d) => { + const id = d._cdc_compare_id; + const hasChanged = dataChange.has(id); + // TODO: All these .find() and .includes() should be refactored as they are O(n). + const isAlreadyHandled = selectedAlert.latest_diff?.dictionary_item_added?.includes(id) || selectedAlert.latest_diff?.dictionary_item_removed?.includes(id); + return (isAlreadyHandled ? null : + + + {selectedAlert.compare_columns.map((field) => ( + + {hasChanged ? ( + dataChange.get(id).has(field) ? () : () + ) : ( + + )} + + ))} + + + ); + })} + : null} + +
ID{field}Status
{data._cdc_compare_id}{get(data, field)}Added
{data._cdc_compare_id}{get(data, field)}Removed
{d._cdc_compare_id}{dataChange.get(id).get(field).old} {dataChange.get(id).get(field).new}{get(d, field)}{get(d, field)}{hasChanged ? <>Changed : null}
+ {selectedAlert.latest_diff ?
+ {confirmError ? : null} +
+ +
+
: null} + ) :

No new data available

} + } + ); +} + + diff --git a/src/cdc/alert/index.ts b/src/cdc/alert/index.ts new file mode 100644 index 000000000..13dae734d --- /dev/null +++ b/src/cdc/alert/index.ts @@ -0,0 +1 @@ +export * from './CDCAlertView'; diff --git a/src/cdc/api.ts b/src/cdc/api.ts new file mode 100644 index 000000000..d584f21cc --- /dev/null +++ b/src/cdc/api.ts @@ -0,0 +1,46 @@ +/********************************************************* + * Copyright (c) 2021 datavisyn GmbH, http://datavisyn.io + * + * This file is property of datavisyn. + * Code and any other files associated with this community + * may not be copied and/or distributed without permission. + * + * Proprietary and confidential. No warranty. + * + *********************************************************/ + +import {IAlert, IUploadAlert} from './interfaces'; +import {AppContext} from '../app'; +import {Ajax} from '../base'; + +export async function getAlerts(): Promise { + return AppContext.getInstance().getAPIJSON(`/tdp/cdc/alert`); +} + +export async function getAlertsById(id: number): Promise { + return AppContext.getInstance().getAPIJSON(`/tdp/cdc/alert/${id}`); +} + +export async function editAlert(id: number, alert: Partial): Promise { + return Ajax.send(AppContext.getInstance().api2absURL(`/tdp/cdc/alert/${id}`), alert, 'PUT', 'JSON', 'application/json'); +} + +export async function deleteAlert(id: number): Promise { + return Ajax.send(AppContext.getInstance().api2absURL(`/tdp/cdc/alert/${id}`), null, 'DELETE'); +} + +export async function saveAlert(alert: IUploadAlert): Promise { + return Ajax.send(AppContext.getInstance().api2absURL(`/tdp/cdc/alert`), alert, 'POST', 'JSON', 'application/json'); +} + +export async function runAlertById(id: number): Promise { + return AppContext.getInstance().getAPIJSON(`/tdp/cdc/alert/${id}/run`); +} + +export async function confirmAlertById(id: number): Promise { + return AppContext.getInstance().getAPIJSON(`/tdp/cdc/alert/${id}/confirm`); +} + +export async function runAllAlerts(): Promise<{success: string[], error: string[]} | null> { + return AppContext.getInstance().getAPIJSON(`/tdp/cdc/alert/run`); +} diff --git a/src/cdc/common/ErrorMessage.tsx b/src/cdc/common/ErrorMessage.tsx new file mode 100644 index 000000000..f7a577e4d --- /dev/null +++ b/src/cdc/common/ErrorMessage.tsx @@ -0,0 +1,15 @@ +import * as React from 'react'; + +export function ErrorMessage({error, onRetry}: {error: Error | null; onRetry?: () => void}) { + return error ? ( +
+ +
{error.message}
+ {onRetry ? ( + + ) : null} +
+ ) : null; +} diff --git a/src/cdc/common/index.ts b/src/cdc/common/index.ts new file mode 100644 index 000000000..3dee9621e --- /dev/null +++ b/src/cdc/common/index.ts @@ -0,0 +1 @@ +export * from './ErrorMessage'; diff --git a/src/cdc/creator/CDCFilterCard.tsx b/src/cdc/creator/CDCFilterCard.tsx new file mode 100644 index 000000000..4e0b36fce --- /dev/null +++ b/src/cdc/creator/CDCFilterCard.tsx @@ -0,0 +1,148 @@ +import * as React from 'react'; +import {IFilter, IFilterComponent, ITEM_TYPES} from '../interfaces'; +import {useDrag} from 'react-dnd'; +import {DropZone} from './DropZone'; + +interface IFilterCardProps { + filter: IFilter; + onDrop?: (item: IFilter, {target, index}: {target: IFilter; index: number}) => void; + onDelete?: (filter: IFilter) => void; + onChange?: (filter: IFilter, changeFunc: (filter: IFilter) => void) => void; + onValueChanged?: (filter: IFilter, value: any, field: string) => void; + filterComponents: {[key: string]: {component: IFilterComponent, config?: any}}; + disableFilter: boolean; + isInvalid?: boolean; + disableRemoving?: boolean; + disableDragging?: boolean; +} + +export function FilterCard({filter, onDrop, onDelete, onChange, onValueChanged, filterComponents, disableFilter, isInvalid, disableDragging, disableRemoving}: IFilterCardProps) { + const [{isDragging, draggedItem}, drag, preview] = useDrag(() => ({ + type: ITEM_TYPES.FILTERCARD, + item: filter, + collect: (monitor) => ({ + isDragging: !!monitor.isDragging(), + draggedItem: monitor.getItem() + }) + }), [filter]); + + const hasChildren = filter.children && filter.children.length >= 0; + const filterComponent = filterComponents[filter.type]; + + if (!filterComponent) { + return <>ERROR!!; + } + + return (<> +
+
+
+
+ {disableDragging || disableFilter ? null : ( + + )} +
+
+ {filterComponent?.component ? ( +
+ onValueChanged(filter, value, field || filter.field) + : undefined + } + /> + {filter.type === 'group' ? +
+
Group Filter
+ {onChange && hasChildren && filter?.children?.length > 1 ? ( + + + + ) : null} +
: null} +
+ ) : null} +
+
+ {!disableRemoving && onDelete && !disableFilter ? ( + + ) : null} +
+
+ {onDrop && (hasChildren || !filterComponent.component.disableDropping) && !disableFilter ? ( + + ) : null} + {filter.children?.map((child, i, allChildren) => ( + + + {onDrop && hasChildren && !disableFilter ? ( + + ) : null} + + ))} +
+
+ {isInvalid ? +
+ Filter must not be empty! +
: + null} + ); +} diff --git a/src/cdc/creator/CDCFilterCreator.tsx b/src/cdc/creator/CDCFilterCreator.tsx new file mode 100644 index 000000000..842601130 --- /dev/null +++ b/src/cdc/creator/CDCFilterCreator.tsx @@ -0,0 +1,127 @@ +import produce from 'immer'; +import React from 'react'; +import {DndProvider} from 'react-dnd'; +import {HTML5Backend} from 'react-dnd-html5-backend'; +import {FilterCard} from './CDCFilterCard'; +import {getFilterFromTree, IFilter, IFilterComponent} from '../interfaces'; +import {v4 as uuidv4} from 'uuid'; + +export function CDCFilterCreator({filterSelection, filter, setFilter, disableFilter, isInvalid, filterComponents}: { + filterComponents?: {[key: string]: {component: IFilterComponent, config?: any}}; + filterSelection?: IFilter[]; + filter: IFilter; + setFilter: (value: IFilter) => void; + disableFilter?: boolean; + isInvalid?: boolean; +}) { + const onDelete = (newFilter: IFilter) => { + setFilter(produce(filter, (nextFilter) => { + const {current, parent} = getFilterFromTree(nextFilter, newFilter.id); + if (current && parent && parent.children) { + // Find the index of the current element in the parents children + const deleteIndex = parent.children.indexOf(current); + // Remove it from the current parent + if (deleteIndex !== -1) { + parent.children.splice(deleteIndex, 1); + } + } + }) + ); + }; + + const onDrop = ( + item: IFilter, + {target, index}: {target: IFilter; index: number} + ) => { + // Add item to target children array + setFilter(produce(filter, (nextFilter) => { + // DANGER: BE SURE TO ONLY REFERENCE SOMETHING FROM nextFilter, + // AND NOTHING FROM 'OUTSIDE' LIKE item, or target. THESE REFERENCES + // ARE NOT UP-TO-DATE! + + // Find target in nextFilter + const dropTarget = getFilterFromTree(nextFilter, target.id); + const dropItem = getFilterFromTree(nextFilter, item.id); + + // Check if the dropped item is part of the tree already + if (dropItem.current) { + // If we have a parent to remove us from... + if (dropItem.parent?.children) { + // Find the index of the current element in the parents children + const deleteIndex = dropItem.parent.children.indexOf( + dropItem.current + ); + // Remove it from the current parent + if (deleteIndex !== -1) { + dropItem.parent?.children?.splice(deleteIndex, 1); + } + } + } else { + // Otherwise, it is a new item to be added in the next step + dropItem.current = {...item, id: uuidv4()}; + } + + if (dropTarget.current) { + // Next, add it as target child + if (!dropTarget.current.children) { + dropTarget.current.children = []; + } + dropTarget.current.children.splice(index, 0, dropItem.current); + } else { + console.error('Something is wrong'); + } + })); + }; + + const onChange = (newFilter: IFilter, changeFunc: (filter: IFilter) => void) => { + setFilter(produce(filter, (nextFilter) => { + const {current, parent} = getFilterFromTree(nextFilter, newFilter.id); + if (current) { + changeFunc(current); + } + })); + }; + + if(filter.type !== 'group') { + throw Error('First filter always has to be a group filter!'); + } + + return ( + +
+
+
Your filters
+ { + onChange(filter, (f) => { + if(value !== undefined) { + f.value = value; + } + if(field !== undefined) { + f.field = field; + } + }); + }} + filterComponents={filterComponents} + disableFilter={disableFilter} + isInvalid={isInvalid} + disableDragging={true} + disableRemoving={true} + /> +
+ {filterSelection ? +
+
New filters
+ {filterSelection.map((f) => ( + + ))} +
+ : null} +
+
+ ); +} diff --git a/src/cdc/creator/DropZone.tsx b/src/cdc/creator/DropZone.tsx new file mode 100644 index 000000000..852aa34ca --- /dev/null +++ b/src/cdc/creator/DropZone.tsx @@ -0,0 +1,51 @@ +import * as React from 'react'; +import { getFilterFromTree, IFilter, ITEM_TYPES } from '../interfaces'; +import { useDrop } from 'react-dnd'; + +interface IDropZoneProps { + onDrop: (item: IFilter, {target, index}: {target: IFilter; index: number}) => void; + canDrop: boolean; + filter: IFilter; + index: number; +} + +export function DropZone({canDrop, onDrop, filter, index}: IDropZoneProps) { + const [{ isOver, draggedItem }, drop] = useDrop( + () => ({ + accept: ITEM_TYPES.FILTERCARD, + drop: (item) => { + onDrop(item, { target: filter, index }); + }, + canDrop: () => canDrop, + collect: (monitor) => ({ + isOver: !!monitor.isOver(), + draggedItem: monitor.getItem() + }) + }), + [canDrop, onDrop, filter, index] + ); + + // Check if the dragged item is actually our parent, because then we are no target + const isDraggedItemParent = React.useMemo(() => { + // If we can find the current filter given our dragged item as parent, we are a child + return draggedItem && !!getFilterFromTree(draggedItem, filter.id).current; + }, [draggedItem, filter]); + + const isVisible = + !isDraggedItemParent && draggedItem !== filter && draggedItem && canDrop; + + return ( +
+ <>  +
+ ); +} diff --git a/src/cdc/creator/index.ts b/src/cdc/creator/index.ts new file mode 100644 index 000000000..624ed41da --- /dev/null +++ b/src/cdc/creator/index.ts @@ -0,0 +1,3 @@ +export * from './CDCFilterCard'; +export * from './CDCFilterCreator'; +// export * from './DropZone'; diff --git a/src/cdc/filter/CDCCheckboxFilter.tsx b/src/cdc/filter/CDCCheckboxFilter.tsx new file mode 100644 index 000000000..3442e80b6 --- /dev/null +++ b/src/cdc/filter/CDCCheckboxFilter.tsx @@ -0,0 +1,60 @@ +import {IFilter, IFilterComponent, IFilterComponentProps} from '../interfaces'; +import * as React from 'react'; +import {uniqueId} from 'lodash'; + +interface ICDCCheckboxFilterValue { + [field: string]: boolean; +} + +/* tslint:disable-next-line:variable-name */ +export const CDCCheckboxFilterId = 'checkbox'; +/* tslint:disable-next-line:variable-name */ +export const CDCCheckboxFilter: IFilterComponent = { + clazz: CDCCheckboxFilterComponent, + disableDropping: true +}; + +export function createCDCCheckboxFilter(id: string, value: ICDCCheckboxFilterValue): IFilter { + return { + id, + type: CDCCheckboxFilterId, + value + }; +} + +export function CDCCheckboxFilterComponent({value, onValueChanged, disabled, config}: IFilterComponentProps) { + const id = React.useMemo(() => uniqueId('CDCCheckboxFilterComponent'), []); + + return <> + {config.fields.map((field, i) => { + return ( +
+
+ { + if (value[field] === false) { + const newVal = {}; + Object.keys(value).forEach((key) => { + if (key !== field) { + newVal[key] = value[key]; + } + }); + onValueChanged?.(newVal); + } else { + onValueChanged?.({...value, [field]: e.target.checked}); + } + }} /> + +
+
+ ); + })} + ; +} diff --git a/src/cdc/filter/CDCGroupingFilter.tsx b/src/cdc/filter/CDCGroupingFilter.tsx new file mode 100644 index 000000000..8f827d09f --- /dev/null +++ b/src/cdc/filter/CDCGroupingFilter.tsx @@ -0,0 +1,23 @@ +import {IFilter, IFilterComponent} from '../interfaces'; +import * as React from 'react'; + +/* tslint:disable-next-line:variable-name */ +export const CDCGroupingFilterId = 'group'; +/* tslint:disable-next-line:variable-name */ +export const CDCGroupingFilter: IFilterComponent = { + clazz: CDCGroupingFilterComponent, + disableDropping: true +}; + +export function createCDCGroupingFilter(id: string): IFilter { + return { + id, + operator: 'AND', + children: [], + type: CDCGroupingFilterId, + }; +} + +function CDCGroupingFilterComponent() { + return

; +} diff --git a/src/cdc/filter/CDCRangeFilter.tsx b/src/cdc/filter/CDCRangeFilter.tsx new file mode 100644 index 000000000..17091ff70 --- /dev/null +++ b/src/cdc/filter/CDCRangeFilter.tsx @@ -0,0 +1,42 @@ +import {IFilter, IFilterComponent, IFilterComponentProps} from '../interfaces'; +import * as React from 'react'; +import InputRange from 'react-input-range'; + +export interface ICDCRangeFilterValue { + min: number; + max: number; +} + +/* tslint:disable-next-line:variable-name */ +export const CDCRangeFilterId = 'range'; +/* tslint:disable-next-line:variable-name */ +export const CDCRangeFilter: IFilterComponent = { + clazz: CDCRangeFilterComponent, + disableDropping: true +}; + +export function createCDCRangeFilter(id: string, field: string, value: ICDCRangeFilterValue): IFilter { + return { + id, + type: CDCRangeFilterId, + field, + value, + }; +} + +function CDCRangeFilterComponent({value, onValueChanged, disabled, config, field}: IFilterComponentProps) { + return
+
+
{field}
+
+
+ typeof e === 'number' ? undefined : onValueChanged?.(e)} + /> +
+
; +} diff --git a/src/cdc/filter/CDCTextFilter.tsx b/src/cdc/filter/CDCTextFilter.tsx new file mode 100644 index 000000000..31f84b369 --- /dev/null +++ b/src/cdc/filter/CDCTextFilter.tsx @@ -0,0 +1,51 @@ +import {IFilter, IFilterComponent, IFilterComponentProps} from '../interfaces'; +import * as React from 'react'; +import Select from 'react-select'; + +/* tslint:disable-next-line:variable-name */ +export const CDCTextFilterId = 'text'; +/* tslint:disable-next-line:variable-name */ +export const CDCTextFilter: IFilterComponent = { + clazz: CDCTextFilterComponent, + disableDropping: true +}; + +export function createCDCTextFilter(id: string, field: string, value: string[]): IFilter { + return { + id, + type: CDCTextFilterId, + field, + value, + }; +} + +export function CDCTextFilterComponent({value, onValueChanged, disabled, field, config}: IFilterComponentProps) { + return <> +
+
+ {return {label: v, value: v};})} + options={config?.find((f) => f?.field === field)?.options.map((o) => {return {label: o, value: o};})} + onChange={(e) => onValueChanged?.([...e.map((v) => v.value)])} + /> +
+
+ ; +} diff --git a/src/cdc/filter/index.ts b/src/cdc/filter/index.ts new file mode 100644 index 000000000..31a7ed909 --- /dev/null +++ b/src/cdc/filter/index.ts @@ -0,0 +1,4 @@ +export * from './CDCCheckboxFilter'; +export * from './CDCGroupingFilter'; +export * from './CDCRangeFilter'; +export * from './CDCTextFilter'; diff --git a/src/cdc/index.ts b/src/cdc/index.ts new file mode 100644 index 000000000..770afb82d --- /dev/null +++ b/src/cdc/index.ts @@ -0,0 +1,7 @@ +export * from './alert'; +export * from './common'; +export * from './creator'; +export * from './filter'; +export * from './api'; +export * from './CDCFilterDialog'; +export * from './interfaces'; diff --git a/src/cdc/interfaces.ts b/src/cdc/interfaces.ts new file mode 100644 index 000000000..4c1358c01 --- /dev/null +++ b/src/cdc/interfaces.ts @@ -0,0 +1,93 @@ +import {ISecureItem} from '../security'; + +export interface IFilterComponentProps { + value: V; + onValueChanged?: (value: V, field?: string) => void; + disabled: boolean; + config: any; + field?: any; +} + +export interface IFilterComponent { + clazz: (props: IFilterComponentProps) => JSX.Element; + disableDropping?: boolean; +} + +export interface IFilter { + id: string; + operator?: 'AND' | 'OR'; + type: string; + value?: any; + field?: string; + children?: IFilter[]; +} + +export interface ICDCConfiguration { + filters: IFilter[]; + components: {[key: string]: {component: IFilterComponent, config?: any}}; + compareColumns: string[]; +} + +export const ITEM_TYPES = { + FILTERCARD: 'filtercard' +}; + +export const getFilterFromTree = ( + filter: IFilter, + id: string +): {parent: IFilter | null; current: IFilter | null} => { + if (filter?.id === id) { + return {current: filter, parent: null}; + } else if (filter && filter.children) { + // Is the id part of my children? + const current = filter.children.find((f) => f.id === id); + if (current) { + return {parent: filter, current}; + } + // Otherwise, continue with all children + for (const f of filter.children) { + const current = getFilterFromTree(f, id); + if (current.current) { + return current; + } + } + } + return {parent: null, current: null}; +}; + +export interface IAlert extends ISecureItem { + id: number; + name: string; + cdc_id: string; + filter: IFilter; + enable_mail_notification: boolean; + latest_diff?: {dictionary_item_added?: string[], dictionary_item_removed?: string[], values_changed?: {id: string, field: [], old_value: string, new_value: string}[]}; + latest_fetched_data?: { + _cdc_compare_id: string; + [key: string]: any; + }[]; + latest_compare_date?: Date; + modification_date?: string; + confirmed_data?: { + _cdc_compare_id: string; + [key: string]: any; + }[]; + confirmation_date: Date; + compare_columns: string[]; + latest_error?: string; + latest_error_date?: Date; +} + +export interface IUploadAlert extends Pick { + compare?: string[]; +} + +export function isAlert(obj: IAlert | IUploadAlert): obj is IAlert { + return typeof (obj as any)?.id === 'number'; +} + + +export interface IReactSelectOption { + value: string; + label: string; +} diff --git a/src/index.ts b/src/index.ts index 13fae3aef..da703fba2 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,4 +1,5 @@ export * from './auth'; +export * from './cdc'; export * from './public'; export * from './base'; export * from './tour'; diff --git a/tdp_core/__init__.py b/tdp_core/__init__.py index db16af613..51fc3c41e 100644 --- a/tdp_core/__init__.py +++ b/tdp_core/__init__.py @@ -51,6 +51,12 @@ def phovea(registry): 'namespace': '/api/tdp/db-migration' }) + # CDC + registry.append('namespace', 'tdp_cdc', 'tdp_core.cdc.api', + { + 'namespace': '/api/tdp/cdc' + }) + # phovea_clue registry.append('namespace', 'caleydo-clue-screenshot', 'tdp_core.remoteplayer', { diff --git a/tdp_core/cdc/BaseCDC.py b/tdp_core/cdc/BaseCDC.py new file mode 100644 index 000000000..4589d892a --- /dev/null +++ b/tdp_core/cdc/BaseCDC.py @@ -0,0 +1,22 @@ +from abc import abstractmethod +from typing import TypeVar, Generic, List +from deepdiff import DeepDiff + +T = TypeVar('T') + + +class BaseCDC(Generic[T]): + @abstractmethod + def load_data(self) -> List[T]: + pass + + @abstractmethod + def get_id(self, item: T) -> str: + pass + + def compare(self, old: List[T], new: List[T]): + old = old or [] + new = new or [] + old_lookup = {item['_cdc_compare_id']: item for item in old} + new_lookup = {item['_cdc_compare_id']: item for item in new} + return DeepDiff(old_lookup, new_lookup, view='tree') diff --git a/tdp_core/cdc/CDCAlert.py b/tdp_core/cdc/CDCAlert.py new file mode 100644 index 000000000..c8059b7a1 --- /dev/null +++ b/tdp_core/cdc/CDCAlert.py @@ -0,0 +1,85 @@ +from marshmallow import EXCLUDE, Schema +from marshmallow import fields +from marshmallow_sqlalchemy import SQLAlchemyAutoSchema +from sqlalchemy import Column, Integer, DateTime, TEXT, Boolean, JSON +# TODO: Remove and use postgres +from sqlalchemy import create_engine +from sqlalchemy.ext.declarative import declarative_base +from sqlalchemy.orm import sessionmaker +from typing import List, Dict + +from tdp_core.cdc.filter import Filter +from . import api + +Base = declarative_base() + + +class CDCAlert(Base): + __tablename__ = 'CDCAlert' + __table_args__ = { + # TODO: Enable in postgres + # 'schema': 'tdp_core', + 'extend_existing': True + } + + id = Column(Integer, primary_key=True, autoincrement=True) + name = Column(TEXT, nullable=False) + cdc_id = Column(TEXT, nullable=False) + filter = Column(JSON, nullable=False) + + enable_mail_notification = Column(Boolean, nullable=False) + latest_email_notification = Column(DateTime, nullable=True) + + latest_compare_date = Column(DateTime, nullable=True) + latest_diff = Column(JSON, nullable=True) + latest_fetched_data = Column(JSON, nullable=True) + compare_columns = Column(JSON, nullable=True) + + latest_error_date = Column(DateTime, nullable=True) + latest_error = Column(JSON, nullable=True) + + confirmation_date = Column(DateTime, nullable=True) # date of confirmation + confirmed_data = Column(JSON, nullable=True) # your confirmed data + # security + creator = Column(TEXT, nullable=False) # NOQA: N815 + creation_date = Column(DateTime, nullable=False) # NOQA: N815 + group = Column(TEXT, nullable=False) # NOQA: N815 + permissions = Column(Integer, nullable=False, default=7700) # NOQA: N815 + # buddies + modifier = Column(TEXT) # NOQA: N815 + modification_date = Column(DateTime) # NOQA: N815 + + def apply_filter(self, data: List[Dict]): + """ Re-computes the filter on every call, by recursive marshmallow loading """ + filt = Filter().load(self.filter) + return list(filter(filt, data)) + + +class CDCAlertSchema(SQLAlchemyAutoSchema): + class Meta: + model = CDCAlert + load_instance = True + unknown = EXCLUDE + + +class CDCAlertArgsSchema(Schema): + class Meta: + unknown = EXCLUDE + + id = fields.Integer() + name = fields.String() + enable_mail_notification = fields.Boolean() + cdc_id = fields.String(validate=lambda name: name in api.cdcs.keys()) + filter = fields.Dict(required=True, validate=Filter().load) + compare_columns = fields.List(fields.String()) + + +class RunAllAlertsSchema(Schema): + success = fields.List(fields.Integer(), required=True) + error = fields.List(fields.Integer(), required=True) + + +engine = create_engine('sqlite:////:memory:') +# Base.metadata.drop_all(engine) +Base.metadata.create_all(engine) +create_session = sessionmaker(engine) diff --git a/tdp_core/cdc/CortellisTrialsCDC.py b/tdp_core/cdc/CortellisTrialsCDC.py new file mode 100644 index 000000000..86a886aca --- /dev/null +++ b/tdp_core/cdc/CortellisTrialsCDC.py @@ -0,0 +1,13 @@ +from typing import Dict +from .BaseCDC import BaseCDC +import requests + + +class CortellisTrialsCDC(BaseCDC): + def load_data(self, options: Dict = {}): + data = requests.get('https://api.the-brain.int.bayer.com/datasource/cortellis/trialsForTarget?entrez_gene_id=1234&returnkey=phase&returnkey=indications&returnkey=datestart&returnkey=dateend&returnkey=numberofsites&returnkey=enrollmentcount&returnkey=trialid').json() + # data[0]['phase'] = 'Phase 3 Clinical' + return data + + def get_id(self, item): + return item['trialid'] diff --git a/tdp_core/cdc/JSONPlaceholderPostsCDC.py b/tdp_core/cdc/JSONPlaceholderPostsCDC.py new file mode 100644 index 000000000..7d1c338a0 --- /dev/null +++ b/tdp_core/cdc/JSONPlaceholderPostsCDC.py @@ -0,0 +1,11 @@ +from typing import Dict +from .BaseCDC import BaseCDC +import requests + + +class JSONPlaceholderPostsCDC(BaseCDC): + def load_data(self, options: Dict = {}): + return requests.get('https://jsonplaceholder.typicode.com/posts').json() + + def get_id(self, item): + return item['id'] diff --git a/tdp_core/cdc/JSONPlaceholderUserCDC.py b/tdp_core/cdc/JSONPlaceholderUserCDC.py new file mode 100644 index 000000000..bb76ae460 --- /dev/null +++ b/tdp_core/cdc/JSONPlaceholderUserCDC.py @@ -0,0 +1,13 @@ +from typing import Dict +from .BaseCDC import BaseCDC +import requests + + +class JSONPlaceholderUserCDC(BaseCDC): + def load_data(self, options: Dict = {}): + users = requests.get('https://jsonplaceholder.typicode.com/users').json() + # users[4]['name'] = 'Max Mustermann' + return users + + def get_id(self, item): + return item['id'] diff --git a/tdp_core/cdc/__init__.py b/tdp_core/cdc/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/tdp_core/cdc/api.py b/tdp_core/cdc/api.py new file mode 100644 index 000000000..0b4c69b05 --- /dev/null +++ b/tdp_core/cdc/api.py @@ -0,0 +1,274 @@ + +from datetime import datetime +from flask_apscheduler import APScheduler +from flask_smorest import Api, Blueprint + +from phovea_server.config import view +from phovea_server.ns import Namespace, abort, no_cache +from phovea_server.security import can_write, login_required, can_read, current_username +from phovea_server.util import jsonify +from .CDCAlert import CDCAlert, CDCAlertSchema, create_session, CDCAlertArgsSchema, RunAllAlertsSchema +from .CortellisTrialsCDC import CortellisTrialsCDC +from .JSONPlaceholderPostsCDC import JSONPlaceholderPostsCDC +from .JSONPlaceholderUserCDC import JSONPlaceholderUserCDC +from .filter import FieldFilterMixin +from .mail import cdc_mail + +app = Namespace(__name__) +app.config['OPENAPI_VERSION'] = '3.0.2' +app.config['OPENAPI_URL_PREFIX'] = '/spec' +app.config['OPENAPI_JSON_PATH'] = 'api.json' +app.config['OPENAPI_SWAGGER_UI_PATH'] = "/" +app.config['OPENAPI_SWAGGER_UI_URL'] = "https://cdn.jsdelivr.net/npm/swagger-ui-dist/" +api = Api(app, spec_kwargs={ + 'title': 'CDC', + 'version': 1, + 'servers': [{ + 'url': '/api/tdp/cdc/' + }] +}) +blp = Blueprint('cdc', __name__, url_prefix='/') +scheduler = APScheduler() +_conf = view("tdp_core") + +cdcs = {cls.__name__: cls() for cls in [JSONPlaceholderUserCDC, JSONPlaceholderPostsCDC, CortellisTrialsCDC]} + + +def compute_new_data_diff(alert: CDCAlert): + app.logger.info(f'Refreshing alert {alert.id}') + cdc = cdcs[alert.cdc_id] + + if not cdc: + raise Exception(f'Missing cdc {alert.cdc_id}') + + data = cdc.load_data({}) # TODO: Define options like username? + data = alert.apply_filter(data) + # on py 3.9, use {a: b} | {c: d} to merge dicts, instead of {a: b, **{c: d}} + data = [{'_cdc_compare_id': str(cdc.get_id(item)), + **{field: FieldFilterMixin.access(item, field) for field in alert.compare_columns}} + for item in data] + + # Compare confirmed with new entry + diff = cdc.compare(alert.confirmed_data, data) + + if "dictionary_item_removed" in diff: + diff["dictionary_item_removed"] = [rm.path(output_format='list')[0] for rm in diff["dictionary_item_removed"]] + + if "dictionary_item_added" in diff: + diff["dictionary_item_added"] = [add.path(output_format='list')[0] for add in diff["dictionary_item_added"]] + + if "values_changed" in diff: + vc = diff["values_changed"] + changed_path = [changed.path(output_format='list') for changed in vc] + diff["values_changed"] = [{ + "id": p[0], + "field": p[1:len(p)], + "old_value": c.t1, + "new_value": c.t2 + } for c, p in zip(vc, changed_path)] + return data, diff + + +def run_alert(alert: CDCAlert) -> bool: + try: + new_data, diff = compute_new_data_diff(alert) + except Exception as e: + app.logger.exception(f'Error when running alert {alert.id}') + # TODO: Clear latest_diff and latest_fetched_data and latest_compare_data? + alert.latest_error = str(e) + alert.latest_error_date = datetime.utcnow() + return False + + if diff: + # We have a new diff! Send email? Store in db? ... + alert.latest_compare_date = datetime.utcnow() + alert.latest_fetched_data = new_data + alert.latest_diff = diff + # TODO else: also set latest diff to empty + + alert.latest_error = None + alert.latest_error_date = None + return True + + +@app.errorhandler(ValueError) # happens in filter validation +@app.errorhandler(KeyError) # happens in cdc lookups +def handle_error(e): + return { + "code": 400, + "status": "Bad Request", + "message": str(e) + }, 400 + + +@app.route('/cdc', methods=['GET']) +def list_cdc(): + return jsonify(list(cdcs.keys())) + + +@app.route('/cdc/', methods=['GET']) +def execute_cdc(id: str): + try: + return cdcs.get(id, None) + except KeyError: + abort(404, f'No cdc with id {id} available') + + +@no_cache +@login_required +@blp.route('/alert', methods=["GET"]) +@blp.response(200, CDCAlertSchema(many=True, )) +def get_alerts(): + session = create_session() + alerts = session.query(CDCAlert).all() + return sorted([p for p in alerts if can_read(p)], key=lambda item: item.id) + + +@no_cache +@login_required +@blp.route('/alert', methods=["POST"]) +@blp.arguments(CDCAlertArgsSchema, location='json', description='Create an alert') +@blp.response(200, CDCAlertSchema) +def create_alert(data): + session = create_session() + # session.query(CDCAlert).delete() + alert = CDCAlertSchema().load(data, partial=True, session=session) + # Security + alert.creator = current_username() + alert.creation_date = datetime.utcnow() + alert.group = "" + # Allow no one to see, and only the creator/buddy to edit + # Use permissions from endpoint + # alert.permissions = 7700 + alert.modifier = None + alert.modification_date = None + run_alert(alert) + session.add(alert) + session.commit() + return alert + + +@no_cache +@login_required +@blp.route('/alert/', methods=["PUT"]) +@blp.arguments(CDCAlertArgsSchema(partial=True), location='json', description='Update an alert') +@blp.response(200, CDCAlertSchema) +def edit_alert_by_id(data, id: str): + session = create_session() + item = session.query(CDCAlert).get(id) + if not item: + abort(404, f'No alert with id {id}') + if not can_write(item): + abort(401) + + alert = CDCAlertSchema().load(data, partial=True, instance=item, session=session) + alert.modification_date = datetime.utcnow() + alert.modifier = current_username() + run_alert(alert) + session.commit() + return alert + + +@no_cache +@login_required +@blp.route('/alert/', methods=["DELETE"]) +@blp.response(200) +def delete_alert_by_id(id: int): + session = create_session() + item = session.query(CDCAlert).get(id) + if not item: + abort(404, f'No alert with id {id}') + if not can_write(item): + abort(401) + + session.delete(item) + session.commit() + return "", 200 + + +def cdc_update(without_perms=True): + """ Schedule-able version that does not need a login / session-scope """ + session = create_session() + alerts = [alert for alert in session.query(CDCAlert).all() + if without_perms or can_read(alert)] # if without_perms is true, can_read isn't considered + status = [run_alert(alert) for alert in alerts] + session.commit() + return {'success': [a.id for a, s in zip(alerts, status) if s], + 'error': [a.id for a, s in zip(alerts, status) if not s]} + + +@no_cache +@login_required +@blp.route('/alert/run', methods=["GET"]) +@blp.response(200, RunAllAlertsSchema, description="Response includes ids of successful and failing alerts") +def run_all_alerts(): + return cdc_update(without_perms=False) + + +@no_cache +@login_required +@blp.route('/alert//run', methods=["GET"]) +@blp.response(200, CDCAlertSchema) +def run_alert_by_id(id: int): + session = create_session() + alert = session.query(CDCAlert).get(id) + if not alert: + abort(404, f'No alert with id {id}') + if not can_read(alert): + abort(401) + + successful = run_alert(alert) + session.commit() + if not successful: + abort(400) + + return alert, 200 + + +@no_cache +@login_required +@blp.route('/alert//confirm', methods=["GET"]) +@blp.response(200, CDCAlertSchema) +def confirm_alert_by_id(id: str): + session = create_session() + alert = session.query(CDCAlert).get(id) + if not alert: + abort(404, f'No alert with id {id}') + if not can_read(alert): + abort(401) + + if not alert.latest_fetched_data: + abort(400, f'No data to confirm for id {id}') + + alert.confirmation_date = datetime.utcnow() + alert.confirmed_data = alert.latest_fetched_data + alert.latest_compare_date = None + alert.latest_fetched_data = None + alert.latest_diff = None + session.commit() + return alert, 200 + + +def add_job_config(sched_func): + name = sched_func.__name__ + time_dict = _conf.get(name, default={}) + if time_dict: + app.logger.info(f"Added job {name} with cron-params {time_dict}") + scheduler.add_job(id=name, func=sched_func, trigger="cron", **time_dict) + + +@blp.route("/asd") +def a(): + cdc_mail() + return "", 200 + + +def create(): + api.register_blueprint(blp) + + scheduler.init_app(app) + add_job_config(cdc_update) + add_job_config(cdc_mail) + scheduler.start() + + return api._app diff --git a/tdp_core/cdc/filter.py b/tdp_core/cdc/filter.py new file mode 100644 index 000000000..a91676e6b --- /dev/null +++ b/tdp_core/cdc/filter.py @@ -0,0 +1,100 @@ +from functools import reduce +from operator import and_, or_, eq +from re import match +from typing import Dict, List, Any, Callable +from marshmallow import Schema, post_load, INCLUDE, fields, ValidationError + + +class Filter(Schema): + """ Filter baseclass to delegate to children """ + class Meta: + unknown = INCLUDE + + id = fields.String(required=True) + type = fields.String(required=False) # "type" of the filter: group, checkbox, text... + + @post_load() + def _postload(self, data: Dict, **_) -> Callable: + """ Call a postload function in every sub-class """ + return self.postload(data, **data) + + def postload(self, data: Dict, type: str, **_) -> Callable: + """ Delegate to a subclass based on type field """ + filters = {f.__name__.lower(): f for f in Filter.__subclasses__()} + if type not in filters: + raise ValidationError(f"Filter type {type} doesn't exist in {list(filters.keys())}", field_name="type") + sub_cls = filters[type] + return sub_cls().load(data) + + +class FieldFilterMixin: + """ Mixin for filters that access a field in the input-data. + Supports simple attributes, sub.attributes or indexed[0], as as well as .len """ + field = fields.String(required=True) + + @staticmethod + def access(item, field: str) -> Any: + for field_name in field.split("."): + if field_name.startswith("_"): + raise ValidationError("Private access detected.", field_name='field') + if item is None: + return None + + # get array-calls + m = match(r"(.*)\[(.*)\\]", field) + + if m: + field_name, field_idx = m.groups() + item = getattr(item, field_name).__getitem__(field_idx) + elif field_name == "len": + item = len(item) + else: + item = item.get(field_name) + return item + + +ALLOWED_OPERATORS = {"AND": and_, "OR": or_} + + +class Group(Filter): + operator = fields.String(required=True, validate=lambda o: o in ALLOWED_OPERATORS) + children = fields.List(fields.Dict) + + # noinspection PyMethodOverriding + def postload(self, data: Dict, children: List, operator: str, **_): + children = [Filter().load(child) for child in children] + op = ALLOWED_OPERATORS[operator] + return lambda d: reduce(op, [child(d) for child in children]) if children else True + + +class Range(Filter, FieldFilterMixin): + value = fields.Dict(fields.String(), fields.Number) + + # noinspection PyMethodOverriding + def postload(self, data, field: str, value: Dict, **_): + if "min" not in value or "max" not in value: + raise ValidationError("Range did not contain min or max.", field_name="value") + return lambda item: value["min"] <= FieldFilterMixin.access(item, field) <= value["max"] + + +class Text(Filter, FieldFilterMixin): + """ Filter for textfields to match """ + value = fields.List(fields.String(), required=True) + match_case = fields.Boolean(required=False, missing=False) + match_exact = fields.Boolean(required=False, missing=False) + + # noinspection PyMethodOverriding + def postload(self, data: Dict, field: str, value: List[str], match_case: bool, match_exact: bool, **_): + compare = eq if match_exact else str.__contains__ + if match_case: + compare = lambda a, b: compare(b.lower(), a.lower()) # NOQA E731 + return lambda item: any(compare(FieldFilterMixin.access(item, field), v) for v in value) + + +class Checkbox(Filter): + value = fields.Dict(fields.String(), fields.Boolean(), required=True) + + # noinspection PyMethodOverriding + def postload(self, data, value: Dict[str, bool], **_): + return lambda item: all([FieldFilterMixin.access(item, key) == v + for key, v in value.items()]) diff --git a/tdp_core/cdc/mail.py b/tdp_core/cdc/mail.py new file mode 100644 index 000000000..c89479e04 --- /dev/null +++ b/tdp_core/cdc/mail.py @@ -0,0 +1,59 @@ +from collections import defaultdict +from datetime import datetime +from email.mime.multipart import MIMEMultipart +from email.mime.text import MIMEText +from smtplib import SMTP +from sqlalchemy import and_, or_ + +from phovea_server.config import view +from .CDCAlert import create_session, CDCAlert + +conf = view("tdp_core").get("mail") + + +def smtp_server(): + assert type(conf) == dict, "No conf found" + assert all(k in conf and conf[k] for k in ["host", "port", "username", "password", "sender"]), f"Invalid conf {conf}" + server = SMTP(host=conf["host"], port=int(conf["port"])) + server.starttls() + server.login(user=conf["username"], password=conf["password"]) + return server + + +def send_mail(receiver: str, subject: str, message: str, server: SMTP): + if receiver == "admin": # todo: remove after testing + receiver = "markus.vogl@datavisyn.io" + + assert "@" in receiver and "." in receiver, "Invalid email" + msg = MIMEMultipart() # this allows files/images to be attached + msg['Subject'] = subject + msg['From'] = conf.get("sender") + msg['To'] = receiver + msg.attach(MIMEText(message, 'html')) + server.sendmail(from_addr=msg['From'], to_addrs=msg['To'], msg=msg.as_string()) + + +def cdc_mail(): + from tdp_core.cdc.api import app # resolve import-loop by late-importing + with app.app_context(): + session = create_session() + empty_old = or_(CDCAlert.latest_email_notification.is_(None), + CDCAlert.latest_email_notification < CDCAlert.latest_compare_date) + alerts = session.query(CDCAlert).filter(and_(CDCAlert.enable_mail_notification.is_(True), empty_old)).all() + if not alerts: + return + print(f"Sending mails for {len(alerts)}...") + + srv = smtp_server() # if we fail here, we just exit + # group alerts by sender, so every user only gets 1 mail + user_alert = defaultdict(list) + for alert in alerts: + user_alert[alert.creator].append(alert) + + for user, alerts in user_alert.items(): + msg = f"New alerts for {' '.join(a.name for a in alerts)}" # TOOD: better message + send_mail(receiver=user, subject=f"You have {len(alerts)} new CDC alerts", message=msg, server=srv) + for a in alerts: + a.latest_email_notification = datetime.utcnow() + srv.close() + session.commit() diff --git a/tdp_core/config.json b/tdp_core/config.json index 2f3224516..ea9eb7677 100644 --- a/tdp_core/config.json +++ b/tdp_core/config.json @@ -70,5 +70,18 @@ "port": 27017, "db_graph": "graph", "db_namedsets": "targid" + }, + "cdc_update": { + "second": 0 + }, + "cdc_mail": { + "second": 15 + }, + "mail": { + "sender": "noreply@datavisyn.io", + "host": "smtp.gmail.com", + "port": 587, + "username": "", + "password": "" } }