huridocs/uwazi

View on GitHub
app/react/Viewer/pageAssets.ts

Summary

Maintainability
A
0 mins
Test Coverage
A
100%
import { IStore } from 'app/istore';
import { formater as formatter } from 'app/Metadata';
import {
  pick,
  isArray,
  isObject,
  isEmpty,
  toPairs,
  take,
  get,
  groupBy,
  has,
  flatMap,
} from 'lodash';
import {
  MetadataObjectSchema,
  MetadataSchema,
  PropertyValueSchema,
} from 'shared/types/commonTypes';
import { EntitySchema } from 'shared/types/entityType';
import { IImmutable } from 'shared/types/Immutable';
import { TemplateSchema } from 'shared/types/templateType';

type Relation = { template: string; entityData: EntitySchema };
type FormattedEntity = EntitySchema & { metadata: any[]; relations: Relation[] };
type FormattedPropertyValueSchema = Partial<MetadataObjectSchema> & {
  type?: string;
  relatedEntity?: FormattedEntity;
  value?: PropertyValueSchema;
};

const pickEntityFields = (entity: FormattedEntity) =>
  pick(entity, [
    'title',
    'sharedId',
    'creationDate',
    'editDate',
    'language',
    'template',
    'inheritedProperty',
  ]);

const metadataFields = (property: FormattedPropertyValueSchema) => {
  switch (property.type) {
    case 'geolocation':
      return { displayValue: 'value[0]', value: 'value' };
    default:
      return { displayValue: 'value', value: ['timestamp', 'originalValue', 'value'] };
  }
};

const propertyDisplayType = (property: FormattedPropertyValueSchema) => {
  switch (property.type) {
    case 'relationship':
      return 'inherit';
    default:
      return property.type;
  }
};

const formatPropertyValue = (
  target: FormattedPropertyValueSchema,
  metadataField: { displayValue: string; value: string | string[] }
) => {
  const availableProperties = pick(target, metadataField.value);
  const firstValue =
    isObject(availableProperties) && !isEmpty(availableProperties)
      ? take(toPairs(availableProperties), 1)[0][1]
      : target;
  return firstValue;
};

const formatProperty = (item: FormattedPropertyValueSchema) => {
  const values: unknown[] = !isArray(item.value) || !item.value.length ? [item] : item.value;
  const formattedItem = values.map((target: any) => {
    const relatedEntity = pickEntityFields(target.relatedEntity);
    const metadataField = metadataFields(item);
    const value = formatPropertyValue(target, metadataField);
    return {
      displayValue: get(target, metadataField.displayValue, value),
      value,
      name: item.name,
      type: propertyDisplayType(item),
      ...(!isEmpty(relatedEntity) ? { reference: relatedEntity } : {}),
    };
  });
  return formattedItem;
};

const formatAggregationsMetadata = (metadata: MetadataSchema) =>
  Object.entries(metadata).reduce((memo, [propertyName, values]) => {
    const formmatedValues = (values as FormattedPropertyValueSchema[]).map(value =>
      value.label && value.label.length ? value.label : value.value
    );
    const result = { [propertyName]: formmatedValues };
    return { ...memo, ...result };
  }, {});

const aggregateByTemplate = (
  relations: Relation[],
  relationship: { _id: string; name: string },
  inheritingProperties: { [key: string]: string[] }
) =>
  relations.reduce(
    (groupedRelations, relation) => {
      const { template } = relation.entityData;
      const groupName = `${relationship.name}-${template}`;
      const relationMetadata = pick(
        relation.entityData.metadata,
        inheritingProperties[template as string]
      );
      const metadata = formatAggregationsMetadata(relationMetadata || {});
      const relatedEntity = {
        title: relation.entityData.title,
        sharedId: relation.entityData.sharedId,
        metadata,
      };
      return !has(groupedRelations, groupName)
        ? { ...groupedRelations, [groupName]: [relatedEntity] }
        : { ...groupedRelations, [groupName]: [...groupedRelations[groupName], relatedEntity] };
    },
    {} as { [key: string]: EntitySchema[] }
  );

const getInheritingProperties = (templates: TemplateSchema[], entityTemplate: TemplateSchema) =>
  templates.reduce((inheriting, template) => {
    const inheritedProperties = entityTemplate.properties
      ?.map(entityProperty => {
        if (!entityProperty?.inherit) return undefined;
        const inheritingPropertyName = template.properties?.find(
          property => property?._id === entityProperty?.inherit?.property
        );
        return inheritingPropertyName?.name;
      })
      .filter(value => !!value);
    return inheritedProperties?.length
      ? { ...inheriting, [template._id as string]: inheritedProperties }
      : inheriting;
  }, {});

const filterInheritedRelations = (
  entity: FormattedEntity,
  inheritingProperties: { [key: string]: string[] }
) => {
  const targetEntities = flatMap(entity.metadata, property =>
    property.value && property.value.length
      ? flatMap(property.value, value => value.relatedEntity)
      : []
  )
    .filter(v => v)
    .map(relatedEntity => relatedEntity.sharedId);
  return entity.relations.filter(
    relation =>
      relation.entityData &&
      has(inheritingProperties, relation.entityData.template as string) &&
      targetEntities.includes(relation.entityData.sharedId)
  );
};

const aggregateRelationships = (
  entity: FormattedEntity,
  relationTypes: { _id: string; name: string }[],
  entityTemplate: IImmutable<TemplateSchema>,
  _templates: IImmutable<TemplateSchema[]>
) => {
  if (!entity.relations || !entity.relations.length || !entityTemplate) {
    return {};
  }
  const templates = _templates
    .filter(template => template?.get('_id') !== entityTemplate.get('_id'))
    .toJS();

  const inheritingProperties = getInheritingProperties(templates, entityTemplate.toJS());
  const filteredRelations = filterInheritedRelations(entity, inheritingProperties);
  const relationshipGroups = groupBy(filteredRelations, 'template');

  const namedRelationshipGroups = Object.entries(relationshipGroups).reduce(
    (aggregated, [relationshipId, relations]) => {
      const relationship = relationTypes.find(({ _id }) => _id === relationshipId);
      if (relationship) {
        const aggregatedByTemplate = aggregateByTemplate(
          relations,
          relationship,
          inheritingProperties
        );
        return { ...aggregated, ...aggregatedByTemplate };
      }
      return aggregated;
    },
    {}
  );

  return namedRelationshipGroups;
};

const formatEntityData = (
  formattedEntity: FormattedEntity,
  relationTypes: { _id: string; name: string }[],
  entityTemplate: IImmutable<TemplateSchema>,
  templates: IImmutable<TemplateSchema[]>
) => {
  const entity = pickEntityFields(formattedEntity);

  const formattedMetadata = formattedEntity.metadata.reduce((memo, property) => {
    const formattedProperty = formatProperty(property);
    return { ...memo, [property.name]: formattedProperty };
  }, {});

  const relationshipAggregations = aggregateRelationships(
    formattedEntity,
    relationTypes,
    entityTemplate,
    templates
  );

  return {
    ...entity,
    metadata: formattedMetadata,
    inherited_relationships: relationshipAggregations,
  };
};

const formatEntity = (formattedEntity: FormattedEntity) => {
  const originalMetadata = formattedEntity.metadata;
  const metadata = originalMetadata.reduce(
    (memo, property) => ({ ...memo, [property.name]: property }),
    {}
  );
  return { ...formattedEntity, metadata };
};

const prepareAssets = (
  entityRaw: EntitySchema,
  template: IImmutable<TemplateSchema>,
  state: Pick<IStore, 'templates' | 'thesauris'>,
  relationTypes: { _id: string; name: string }[]
) => {
  const formattedEntity = formatter.prepareMetadata(entityRaw, state.templates, state.thesauris);
  const entity = formatEntity(formattedEntity);
  const entityData = formatEntityData(formattedEntity, relationTypes, template, state.templates);
  return { entity, entityRaw, entityData, template: template.toJS() };
};

export { prepareAssets };