zammad/zammad

View on GitHub
app/frontend/shared/components/ObjectAttributes/useDisplayObjectAttributes.ts

Summary

Maintainability
B
5 hrs
Test Coverage
// Copyright (C) 2012-2024 Zammad Foundation, https://zammad-foundation.org/

import { keyBy, get } from 'lodash-es'
import { computed } from 'vue'

import type {
  ObjectManagerFrontendAttribute,
  ObjectAttributeValue,
} from '#shared/graphql/types.ts'
import { useSessionStore } from '#shared/stores/session.ts'
import type { ObjectLike } from '#shared/types/utils.ts'
import { camelize } from '#shared/utils/formatter.ts'

import type { AttributeDeclaration } from './types.ts'
import type { Dictionary } from 'ts-essentials'
import type { Component } from 'vue'

export interface ObjectAttributesDisplayOptions {
  object: ObjectLike
  attributes: ObjectManagerFrontendAttribute[]
  skipAttributes?: string[]
  accessors?: Record<string, string>
}

interface AttributeField {
  attribute: ObjectManagerFrontendAttribute
  component: Component
  value: unknown
  link: Maybe<string>
}

const attributesDeclarations = import.meta.glob<AttributeDeclaration>(
  './attributes/Attribute*/index.ts',
  { eager: true, import: 'default' },
)

const definitionsByType = Object.values(attributesDeclarations).reduce(
  (acc, declaration) => {
    declaration.dataTypes.forEach((type) => {
      acc[type] = declaration.component
    })
    return acc
  },
  {} as Record<string, Component>,
)

export const useDisplayObjectAttributes = (
  options: ObjectAttributesDisplayOptions,
) => {
  const attributesObject = computed<Dictionary<ObjectAttributeValue>>(() => {
    return keyBy(options.object.objectAttributeValues || {}, 'attribute.name')
  })

  const getValue = (key: string) => {
    const accessor = options.accessors?.[key]
    if (accessor) {
      return get(options.object, accessor)
    }
    if (key in attributesObject.value) {
      return attributesObject.value[key].value
    }
    if (key in options.object) {
      return options.object[key]
    }
    return options.object[camelize(key)]
  }

  const isEmpty = (value: unknown) => {
    if (Array.isArray(value)) {
      return value.length === 0
    }
    // null or undefined or ''
    return value == null || value === ''
  }

  const getLink = (name: string) => {
    const attribute = attributesObject.value[name]
    return attribute?.renderedLink || null
  }

  const session = useSessionStore()

  const fields = computed<AttributeField[]>(() => {
    return options.attributes
      .map((attribute) => {
        let value = getValue(attribute.name)

        if (typeof value !== 'boolean' && !value) {
          value = attribute.dataOption?.default
        }

        return {
          attribute,
          component: definitionsByType[attribute.dataType],
          value,
          link: getLink(attribute.name),
        }
      })
      .filter(({ attribute, value, component }) => {
        if (!component) return false

        const dataOption = attribute.dataOption || {}

        if (
          'permission' in dataOption &&
          !session.hasPermission(dataOption.permission)
        ) {
          return false
        }

        if (isEmpty(value)) {
          return false
        }

        return !options.skipAttributes?.includes(attribute.name)
      })
  })

  return {
    fields,
  }
}