diff --git a/ui/package.json b/ui/package.json index cc7c1741b..f3aaca552 100644 --- a/ui/package.json +++ b/ui/package.json @@ -12,6 +12,7 @@ "bootstrap": "^4.6.0", "date-fns": "^2.21.1", "http-proxy-middleware": "^1.2.0", + "lodash": "^4.17.21", "prop-types": "^15.7.2", "react": "^17.0.2", "react-dom": "^17.0.2", diff --git a/ui/src/app/core/hooks/utils.js b/ui/src/app/core/hooks/utils.js new file mode 100644 index 000000000..e8ee1f74a --- /dev/null +++ b/ui/src/app/core/hooks/utils.js @@ -0,0 +1,10 @@ +function uuidv4() { + return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) { + var r = Math.random() * 16 | 0, v = c === 'x' ? r : (r & 0x3 | 0x8); + return v.toString(16); + }); +} + +export function useGuid() { + return uuidv4(); +} \ No newline at end of file diff --git a/ui/src/app/dashboard/container/ProvidersTab.js b/ui/src/app/dashboard/container/ProvidersTab.js index 88ba28c5a..fde9f260c 100644 --- a/ui/src/app/dashboard/container/ProvidersTab.js +++ b/ui/src/app/dashboard/container/ProvidersTab.js @@ -2,7 +2,7 @@ import React from 'react'; import { useMetadataEntities } from '../../metadata/hooks/api'; import Translate from '../../i18n/components/translate'; -import ProviderList from '../../metadata/provider/component/ProviderList'; +import ProviderList from '../../metadata/domain/provider/component/ProviderList'; export function ProvidersTab () { diff --git a/ui/src/app/dashboard/container/SourcesTab.js b/ui/src/app/dashboard/container/SourcesTab.js index 8b55a0da8..3a57861f7 100644 --- a/ui/src/app/dashboard/container/SourcesTab.js +++ b/ui/src/app/dashboard/container/SourcesTab.js @@ -3,7 +3,7 @@ import useFetch from 'use-http'; import Translate from '../../i18n/components/translate'; import API_BASE_PATH from '../../App.constant'; -import SourceList from '../../metadata/source/component/SourceList'; +import SourceList from '../../metadata/domain/source/component/SourceList'; import { useMetadataEntities } from '../../metadata/hooks/api'; export function SourcesTab () { diff --git a/ui/src/app/metadata/Metadata.js b/ui/src/app/metadata/Metadata.js index 99deee5fd..7564416c3 100644 --- a/ui/src/app/metadata/Metadata.js +++ b/ui/src/app/metadata/Metadata.js @@ -5,7 +5,7 @@ import { MetadataDetail } from './component/MetadataDetail'; import { MetadataHistory } from './component/MetadataHistory'; import { MetadataEditor } from './editor/MetadataEditor'; import { MetadataSelector } from './hoc/MetadataSelector'; -import MetadataSchema from './hoc/MetadataSchema'; +import { MetadataSchema } from './hoc/MetadataSchema'; export function Metadata () { diff --git a/ui/src/app/metadata/component/MetadataConfiguration.js b/ui/src/app/metadata/component/MetadataConfiguration.js index 64fde5e01..4a4584ce2 100644 --- a/ui/src/app/metadata/component/MetadataConfiguration.js +++ b/ui/src/app/metadata/component/MetadataConfiguration.js @@ -1,7 +1,70 @@ import React from 'react'; +import FormattedDate from '../../core/components/FormattedDate'; +import Translate from '../../i18n/components/translate'; +import { MetadataSection } from './MetadataSection'; +import { usePropertyWidth } from './properties/hooks'; + +import { ObjectProperty } from './properties/ObjectProperty'; + +export function MetadataConfiguration ({ configuration }) { + + const onEdit = (value) => console.log(value); + + const columns = configuration.dates?.length || 1; + const width = usePropertyWidth(columns); -export function MetadataConfiguration () { return ( - <> + <> + { configuration && configuration.sections.map((section, sidx) => + +
+ Option + {configuration.dates.map((d, didx) => + + {configuration.dates.length <= 1 ? + Value + : + + } + + )} +
+ +
+ ) } + ); -} \ No newline at end of file +} + +/* +
+
+
+

+ + 0{{ i + 1 }} + + {{ section.label | translate }} +

+
+ +
+
+
+ +
+ Option + + Value + {{ date | date:DATE_FORMAT }} + +
+ +
+
+
+
*/ \ No newline at end of file diff --git a/ui/src/app/metadata/component/MetadataOptions.js b/ui/src/app/metadata/component/MetadataOptions.js index 853e660cb..18b1f354b 100644 --- a/ui/src/app/metadata/component/MetadataOptions.js +++ b/ui/src/app/metadata/component/MetadataOptions.js @@ -7,6 +7,10 @@ import Translate from '../../i18n/components/translate'; import { MetadataObjectContext } from '../hoc/MetadataSelector'; import { MetadataHeader } from './MetadataHeader'; +import { MetadataConfiguration } from './MetadataConfiguration'; + + +import { useMetadataConfiguration } from '../hooks/configuration'; export function MetadataOptions () { @@ -14,6 +18,8 @@ export function MetadataOptions () { const { type, id } = useParams(); + const configuration = useMetadataConfiguration([metadata]); + return ( <>

@@ -50,6 +56,7 @@ export function MetadataOptions () { */} + ); diff --git a/ui/src/app/metadata/component/MetadataSection.js b/ui/src/app/metadata/component/MetadataSection.js new file mode 100644 index 000000000..5abf8f764 --- /dev/null +++ b/ui/src/app/metadata/component/MetadataSection.js @@ -0,0 +1,42 @@ +import { faEdit } from '@fortawesome/free-solid-svg-icons'; +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; +import React from 'react'; +import Translate from '../../i18n/components/translate'; + +export function MetadataSection ({ section, index = -1, onEdit, children }) { + return ( + <> +
+
+
+

+ {index > -1 && + + {index < 10 && 0} + { index + 1 } + + } + + + + +

+ {onEdit && +
+ +
+ } +
+ {section && section.properties && section.properties.length && +
+ { children } +
+ } +
+
+ + ); +} \ No newline at end of file diff --git a/ui/src/app/metadata/component/properties/ArrayProperty.js b/ui/src/app/metadata/component/properties/ArrayProperty.js new file mode 100644 index 000000000..b655d784d --- /dev/null +++ b/ui/src/app/metadata/component/properties/ArrayProperty.js @@ -0,0 +1,149 @@ +import { faEye } from '@fortawesome/free-solid-svg-icons'; +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; +import React from 'react'; +import Translate from '../../../i18n/components/translate'; +import { ArrayValue } from './ArrayValue'; +import { usePropertyWidth } from './hooks'; +import { PropertyValue } from './PropertyValue'; + + + +const isUri = (value) => { + try { + let url = new URL(value); + } catch (err) { + return false; + } + return true; +} + +const getItemType = (property) => { + const items = property.items; + const def = 'default'; + return items ? items.widget ? items.widget.id : def : def; +} + +const isUrl = (str) => { + return isUri(str); +} + +export function ArrayProperty ({ property, columns, index, onPreview }) { + + const keys = property.value.reduce((val, version) => version ? version.length > val ? version.length : val : val, 0); + const range = [...Array(keys).keys()]; + + const dataList = property.widget?.data; + + const width = usePropertyWidth(columns); + + const type = getItemType(property); + + return ( + <> + {property.items.type === 'object' && +
+
{ property.name }
+ {range.map((i) => +
+ {Object.keys(property.items.properties).map((prop, n) => +
+ {property.differences && Changed: } + {property.items.properties && +
+ {property.items.properties[prop].title} +
+ } + { property.value.map((version, vIdx) => + + )} +
+ )} + +
+ )} +
+ } + {property.items.type === 'string' && + <> + { (type === 'datalist' || type === 'select' || !property.width || !property.widget.id) ? +
+ {property.differences && Changed: } + { property.name } + {property.value.map((v, vidx) => + <> + {(!v || !v.length) &&

-

} + {(v && v.length > 0) && +
    + {v.map((item, idx) => +
  • 1 ? 'py-2' : ''} ${'border-0'}`}> + {onPreview && isUrl(item) && + <> +   + + } + { item } +
  • + )} +
+ } + + )} +
+ : property.widget && property.widget.data ? + <> + {dataList.map((item, itemIdx) => +
+ {item.differences && Changed: } + { item.label } + { property.value.map((v, vIdx) => +
+ {v && v.indexOf(item.key) > -1 && true } + {(!v || !(v.indexOf(item.key) > -1)) && false } +
+ )} +
+ )} + + : ''} + + } + + ); +} + +/* + + + + + + + + +
+ +
+
+ +
+
+
+ + [ngbPopover]="popContent" + triggers="mouseenter:mouseleave" + popoverClass="popover-lg popover-info" + + + +
    +
  • + {{ item }} +
  • +
+
+
+ +*/ \ No newline at end of file diff --git a/ui/src/app/metadata/component/properties/ArrayValue.js b/ui/src/app/metadata/component/properties/ArrayValue.js new file mode 100644 index 000000000..a70368da1 --- /dev/null +++ b/ui/src/app/metadata/component/properties/ArrayValue.js @@ -0,0 +1,38 @@ +import React from 'react'; + +export function ArrayValue () { + return (<>); +} + +/* +
+ Changed: + {{ property.name }} + +

-

+
    +
  • + +   + + {{ item }} +
  • +
+ +
    +
  • + {{ item }} +
  • +
+
+
+
*/ \ No newline at end of file diff --git a/ui/src/app/metadata/component/properties/ObjectProperty.js b/ui/src/app/metadata/component/properties/ObjectProperty.js new file mode 100644 index 000000000..f9a03aaac --- /dev/null +++ b/ui/src/app/metadata/component/properties/ObjectProperty.js @@ -0,0 +1,47 @@ +import React from 'react'; + +import { PrimitiveProperty } from './PrimitiveProperty'; +import { ArrayProperty } from './ArrayProperty'; +import Translate from '../../../i18n/components/translate'; + +export function ObjectProperty ({ property, columns, onPreview }) { + + + const getProperty = (prop, idx) => { + switch(prop.type) { + case 'array': + return + case 'object': + return + {prop.name &&
} + +
+ default: + return + } + } + + return ( +
+ {property && property.properties.map((prop, pIdx) => getProperty(prop, pIdx))} +
+ ); +} + +/* + + + +
{{ prop.name | translate }}
+ +
+ +
+ + + +*/ \ No newline at end of file diff --git a/ui/src/app/metadata/component/properties/PrimitiveProperty.js b/ui/src/app/metadata/component/properties/PrimitiveProperty.js new file mode 100644 index 000000000..f86a798b7 --- /dev/null +++ b/ui/src/app/metadata/component/properties/PrimitiveProperty.js @@ -0,0 +1,33 @@ +import React from 'react'; + +import Translate from '../../../i18n/components/translate'; + +import { usePropertyWidth } from './hooks'; +import { PropertyValue } from './PropertyValue'; + +export function PrimitiveProperty ({ property, columns }) { + + const width = usePropertyWidth(columns); + + return ( +
+ {property.differences && Changed: } + {property.name && + <> +
+ + { property.name } + + {property.value.map((v, valIdx) => + + ) } +
+ + + } +
+ ); +} +/**/ \ No newline at end of file diff --git a/ui/src/app/metadata/component/properties/PropertyValue.js b/ui/src/app/metadata/component/properties/PropertyValue.js new file mode 100644 index 000000000..14779b3e4 --- /dev/null +++ b/ui/src/app/metadata/component/properties/PropertyValue.js @@ -0,0 +1,33 @@ +import React from 'react'; +import { UncontrolledPopover, PopoverBody } from 'reactstrap'; + +import { usePropertyWidth } from './hooks'; +import { useGuid } from '../../../core/hooks/utils'; + +export function PropertyValue ({ name, value, columns }) { + + const width = usePropertyWidth(columns); + + const id = useGuid(); + + console.log(value) + + return ( + <> + { name && value && value !== false ? + <> + + {value !== undefined ? value.toString() : (value === false) ? value.toString() : '-'} + + + {value} + + + : '-'} + + ); +} \ No newline at end of file diff --git a/ui/src/app/metadata/component/properties/hooks.js b/ui/src/app/metadata/component/properties/hooks.js new file mode 100644 index 000000000..fe4f809e6 --- /dev/null +++ b/ui/src/app/metadata/component/properties/hooks.js @@ -0,0 +1,3 @@ +export function usePropertyWidth(columns) { + return `${Math.floor(100 / (columns + 1))}%` +} \ No newline at end of file diff --git a/ui/src/app/metadata/domain/index.js b/ui/src/app/metadata/domain/index.js new file mode 100644 index 000000000..01d2429aa --- /dev/null +++ b/ui/src/app/metadata/domain/index.js @@ -0,0 +1,14 @@ +import { SourceEditor } from "./source/SourceDefinition"; + +export const editors = { + source: SourceEditor +}; + +export const ProviderEditorTypes = []; +export const FilterEditorTypes = []; + + +export const getDefinition = (type) => + ProviderEditorTypes.find(def => def.type === type) || + FilterEditorTypes.find(def => def.type === type) || + SourceEditor; \ No newline at end of file diff --git a/ui/src/app/metadata/provider/component/ProviderList.js b/ui/src/app/metadata/domain/provider/component/ProviderList.js similarity index 98% rename from ui/src/app/metadata/provider/component/ProviderList.js rename to ui/src/app/metadata/domain/provider/component/ProviderList.js index 788162645..a771fac38 100644 --- a/ui/src/app/metadata/provider/component/ProviderList.js +++ b/ui/src/app/metadata/domain/provider/component/ProviderList.js @@ -5,8 +5,8 @@ import { Badge, UncontrolledPopover, PopoverBody } from 'reactstrap'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { faChevronCircleDown, faChevronCircleUp } from '@fortawesome/free-solid-svg-icons'; -import FormattedDate from '../../../core/components/FormattedDate'; -import Translate from '../../../i18n/components/translate'; +import FormattedDate from '../../../../core/components/FormattedDate'; +import Translate from '../../../../i18n/components/translate'; export default function ProviderList({ entities, onDelete }) { return ( diff --git a/ui/src/app/metadata/domain/source/SourceDefinition.js b/ui/src/app/metadata/domain/source/SourceDefinition.js new file mode 100644 index 000000000..bbf38b740 --- /dev/null +++ b/ui/src/app/metadata/domain/source/SourceDefinition.js @@ -0,0 +1,242 @@ +export const SourceBase = { + label: 'Metadata Source', + type: '@MetadataProvider', + steps: [], + schema: '', + validatorParams: [/*getAllOtherIds*/], + + bindings: { + '/securityInfo/x509CertificateAvailable': [ + { + 'input': (event, property) => { + let available = !property.value, + parent = property.parent, + certs = parent.getProperty('x509Certificates'); + if (available && !certs.value.length) { + certs.setValue([ + { + name: '', + type: 'both', + value: '' + } + ], true); + } + + if (!available && certs.value.length > 0) { + certs.setValue([], true); + } + } + } + ], + '/assertionConsumerServices/*/makeDefault': [ + { + 'input': (event, property) => { + let parent = property.parent.parent; + let props = parent.properties; + props.forEach(prop => { + if (prop !== property) { + prop.setValue({ + ...prop.value, + makeDefault: false + }, false); + } + }); + } + } + ] + }, + + parser: (changes, schema) => { + if (!schema || !schema.properties) { + return changes; + } + if (schema.properties.hasOwnProperty('organization') && !changes.organization) { + changes.organization = {}; + } + return changes; + }, + + formatter: (changes, schema) => { + return changes; + }, + + getValidators: (entityIdList) => { + const validators = { + '/': (value, property, form_current) => { + let errors; + // iterate all customer + Object.keys(value).forEach((key) => { + const item = value[key]; + const validatorKey = `/${key}`; + const validator = validators.hasOwnProperty(validatorKey) ? validators[validatorKey] : null; + const error = validator ? validator(item, form_current.getProperty(key), form_current) : null; + if (error && error.invalidate) { + errors = errors || []; + errors.push(error); + } + }); + return errors; + }, + '/entityId': (value, property, form) => { + const err = entityIdList.indexOf(value) > -1 ? { + code: 'INVALID_ID', + path: `#${property.path}`, + message: 'message.id-unique', + params: [value], + invalidate: true + } : null; + return err; + }, + '/relyingPartyOverrides': (value, property, form) => { + if (!value.signAssertion && value.dontSignResponse) { + return { + code: 'INVALID_SIGNING', + path: `#${property.path}`, + message: 'message.invalid-signing', + params: [value], + invalidate: false + }; + } + return null; + }, + '/serviceProviderSsoDescriptor': (value, property, form) => { + if (value.nameIdFormats && value.nameIdFormats.length && !value.protocolSupportEnum) { + return { + code: 'PROTOCOL_SUPPORT_ENUM_REQUIRED', + path: `#${property.path}`, + message: 'message.protocol-support-required', + params: [value], + invalidate: true + }; + } + return null; + } + }; + return validators; + } +} + + +export const SourceEditor = { + ...SourceBase, + schema: `/MetadataSources`, + steps: [ + { + index: 1, + id: 'common', + label: 'label.sp-org-info', + fields: [ + 'serviceProviderName', + 'entityId', + 'serviceEnabled', + 'organization', + 'contacts' + ], + fieldsets: [ + { + type: 'group', + fields: [ + 'serviceProviderName', + 'entityId', + 'serviceEnabled', + 'organization' + ] + }, + { + type: 'group', + fields: [ + 'contacts' + ] + } + ] + }, + { + index: 3, + id: 'metadata-ui', + label: 'label.metadata-ui', + fields: [ + 'mdui' + ] + }, + { + index: 4, + id: 'descriptor-info', + label: 'label.descriptor-info', + fields: [ + 'serviceProviderSsoDescriptor' + ] + }, + { + index: 5, + id: 'logout-endpoints', + label: 'label.logout-endpoints', + fields: [ + 'logoutEndpoints' + ], + fieldsets: [ + { + type: 'group', + fields: [ + 'logoutEndpoints' + ] + } + ] + }, + { + index: 6, + id: 'key-info', + label: 'label.key-info', + fields: [ + 'securityInfo' + ] + }, + { + index: 7, + id: 'assertion', + label: 'label.assertion', + fields: [ + 'assertionConsumerServices' + ], + fieldsets: [ + { + type: 'group', + fields: [ + 'assertionConsumerServices' + ] + } + ] + }, + { + index: 8, + id: 'relying-party', + label: 'label.relying-party', + fields: [ + 'relyingPartyOverrides' + ], + fieldsets: [ + { + type: 'group', + fields: [ + 'relyingPartyOverrides' + ] + } + ] + }, + { + index: 9, + id: 'attribute', + label: 'label.attribute-release', + fields: [ + 'attributeRelease' + ], + fieldsets: [ + { + type: 'group', + fields: [ + 'attributeRelease' + ] + } + ] + } + ] +}; \ No newline at end of file diff --git a/ui/src/app/metadata/source/component/SourceList.js b/ui/src/app/metadata/domain/source/component/SourceList.js similarity index 98% rename from ui/src/app/metadata/source/component/SourceList.js rename to ui/src/app/metadata/domain/source/component/SourceList.js index fc8751a39..482e21b0c 100644 --- a/ui/src/app/metadata/source/component/SourceList.js +++ b/ui/src/app/metadata/domain/source/component/SourceList.js @@ -5,8 +5,8 @@ import { Badge, UncontrolledPopover, PopoverBody, Button, Modal, ModalHeader, Mo import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { faTrash, faExclamationTriangle } from '@fortawesome/free-solid-svg-icons'; -import FormattedDate from '../../../core/components/FormattedDate'; -import Translate from '../../../i18n/components/translate'; +import FormattedDate from '../../../../core/components/FormattedDate'; +import Translate from '../../../../i18n/components/translate'; export default function SourceList({ entities, onDelete }) { @@ -17,6 +17,7 @@ export default function SourceList({ entities, onDelete }) { const [deleting, setDeleting] = React.useState(null); + const deleteSource = (id) => { onDelete(deleting); setDeleting(null); diff --git a/ui/src/app/metadata/hoc/MetadataSchema.js b/ui/src/app/metadata/hoc/MetadataSchema.js index dd0da1b85..2db96155f 100644 --- a/ui/src/app/metadata/hoc/MetadataSchema.js +++ b/ui/src/app/metadata/hoc/MetadataSchema.js @@ -1,36 +1,41 @@ import React from 'react'; import { useParams } from 'react-router'; -import { useMetadataSchema, getSchemaPath } from '../hooks/api'; +import { useMetadataSchema } from '../hooks/api'; +import { getDefinition } from '../domain/index'; export const MetadataSchemaContext = React.createContext(); - +export const MetadataDefinitionContext = React.createContext(); export function MetadataSchema({ children }) { let { type } = useParams(); + const definition = React.useMemo(() => getDefinition(type), [type]); + const { get, response } = useMetadataSchema(); - const [schema, setSchema] = React.useState([]); + const [schema, setSchema] = React.useState(); - async function loadSchema(t) { - const source = await get(`/${getSchemaPath(t)}`) + async function loadSchema(d) { + const source = await get(`/${definition.schema}`) if (response.ok) { setSchema(source); } } - React.useEffect(() => { loadSchema(type) }, [type]); + React.useEffect(() => { loadSchema(definition) }, [definition]); return ( - <> - {type && - + + {type && definition && schema && + {children} } - + ); } +//getConfigurationSections + export default MetadataSchema; \ No newline at end of file diff --git a/ui/src/app/metadata/hooks/configuration.js b/ui/src/app/metadata/hooks/configuration.js new file mode 100644 index 000000000..e1eb41d93 --- /dev/null +++ b/ui/src/app/metadata/hooks/configuration.js @@ -0,0 +1,13 @@ +import React from 'react'; + +import { MetadataDefinitionContext, MetadataSchemaContext } from '../hoc/MetadataSchema'; +import { getConfigurationSections } from './schema'; + +export function useMetadataConfiguration(models) { + const definition = React.useContext(MetadataDefinitionContext); + const schema = React.useContext(MetadataSchemaContext); + + console.log(definition, schema); + + return getConfigurationSections(models, definition, schema); +} \ No newline at end of file diff --git a/ui/src/app/metadata/hooks/schema.js b/ui/src/app/metadata/hooks/schema.js new file mode 100644 index 000000000..bc432fb80 --- /dev/null +++ b/ui/src/app/metadata/hooks/schema.js @@ -0,0 +1,216 @@ +export function getDefinition(path, definitions) { + let def = path.split('/').pop(); + return definitions[def]; +} + +export function getPropertyItemSchema(items, definitions) { + if (!items) { return null; } + return items.$ref ? getDefinition(items.$ref, definitions) : items; +} + +export function getStepProperty(property, model, definitions) { + if (!property) { return null; } + property = property.$ref ? { ...property, ...getDefinition(property.$ref, definitions) } : property; + return { + name: property.title, + value: model, + type: property.type, + items: getPropertyItemSchema(property.items, definitions), + properties: getStepProperties( + property, + model, + definitions + ), + widget: property.widget instanceof String ? { id: property.widget } : { ...property.widget } + }; +} + + +export function getStepProperties(schema, model, definitions = {}) { + if (!schema || !schema.properties) { return []; } + return Object + .keys(schema.properties) + .map(property => { + return { + ...getStepProperty( + schema.properties[property], + model && model.hasOwnProperty(property) ? model[property] : null, + definitions + ), + id: property + }; + }); +} + + +function omit(key, obj) { + if (!obj) { + return obj; + } + const { [key]: omitted, ...rest } = obj; + return rest; +} + +export const rollupDifferences = (prop) => { + let updates = { + ...prop + }; + + if (prop.properties) { + updates = { + ...updates, + properties: [ + ...prop.properties.map(p => rollupDifferences(p)) + ] + }; + } + + prop.differences = prop.properties.some(p => p.differences); + + return updates; +}; + +export const getConfigurationSections = (models, definition, schema) => { + return !definition || !schema || !models ? null : + ({ + dates: models.map(m => m ? m.modifiedDate : null), + sections: definition.steps + .filter(step => step.id !== 'summary') + .map( + (step, num) => { + return ({ + id: step.id, + pageNumber: num + 1, + index: step.index, + label: step.label, + properties: getStepProperties( + getSplitSchema(schema, step), + definition.formatter({}), + schema.definitions || {} + ) + }); + } + ) + .map((section) => { + return { + ...section, + properties: assignValueToProperties(models, section.properties, definition) + }; + }) + .map((section) => ({ + ...section, + differences: section.properties.some(prop => prop.differences) + })) + }); +}; + +const getDifferences = (models, prop) => { + return models.some((model, index, array) => { + if (!array) { + return false; + } + const prop1 = omit('modifiedDate', model[prop.id]); + const prop2 = omit('modifiedDate', array[0][prop.id]); + return JSON.stringify(prop1) !== JSON.stringify(prop2); + }); +}; + +export const assignValueToProperties = (models, properties, definition) => { + return properties.map(prop => { + const differences = getDifferences(models, prop); + + const widget = prop.type === 'array' && prop.widget && prop.widget.data ? ({ + ...prop.widget, + data: prop.widget.data.map(item => ({ + ...item, + differences: models + .map((model) => { + const value = model[prop.id]; + return value ? value.indexOf(item.key) > -1 : false; + }) + .reduce((current, val) => current !== val ? true : false, false) + })) + }) : null; + + switch (prop.type) { + case 'object': + return { + ...prop, + properties: assignValueToProperties( + models.map(model => definition.formatter(model)[prop.id] || {}), + prop.properties, + definition + ), + differences: getDifferences(models, prop) + }; + default: + return { + ...prop, + differences, + value: models.map(model => { + return model[prop.id]; + }), + widget + }; + } + }); +}; + +export const getLimitedPropertiesFn = (properties) => { + return ([ + ...properties + .filter(p => p.differences) + .map(p => { + const parsed = { ...p }; + if (p.widget && p.widget.data) { + parsed.widget = { + ...p.widget, + data: p.widget.data.filter(item => item.differences) + }; + } + if (p.properties) { + parsed.properties = getLimitedPropertiesFn(p.properties); + } + return parsed; + }) + ]); +}; + +export const getSplitSchema = (schema, step) => { + if (!schema || !step || !step.fields || !step.fields.length || !schema.properties) { + return schema; + } + const keys = Object.keys(schema.properties).filter(key => step.fields.indexOf(key) > -1); + const required = (schema.required || []).filter(val => keys.indexOf(val) > -1); + let s = { + type: schema.type, + properties: { + ...keys.reduce((properties, key) => ({ ...properties, [key]: schema.properties[key] }), {}) + } + }; + + if (step.override) { + Object.keys(step.override).forEach(key => { + let override = step.override[key]; + if (s.properties.hasOwnProperty(key)) { + s.properties[key] = { ...s.properties[key], ...override }; + } + }); + } + + if (step.order) { + s.order = step.order; + } + + if (schema.definitions) { + s.definitions = schema.definitions; + } + if (required && required.length) { + s.required = required; + } + if (step.fieldsets) { + s.fieldsets = step.fieldsets; + } + + return s; +}; \ No newline at end of file diff --git a/ui/yarn.lock b/ui/yarn.lock index 3e244f3df..ac9e26a66 100644 --- a/ui/yarn.lock +++ b/ui/yarn.lock @@ -7017,7 +7017,7 @@ lodash.uniq@^4.5.0: resolved "https://registry.yarnpkg.com/lodash.uniq/-/lodash.uniq-4.5.0.tgz#d0225373aeb652adc1bc82e4945339a842754773" integrity sha1-0CJTc662Uq3BvILklFM5qEJ1R3M= -"lodash@>=3.5 <5", lodash@^4.17.11, lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.19, lodash@^4.17.20, lodash@^4.17.5: +"lodash@>=3.5 <5", lodash@^4.17.11, lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.19, lodash@^4.17.20, lodash@^4.17.21, lodash@^4.17.5: version "4.17.21" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==