Browse Source

Maintenance: Mobile - Move object attributes to shared context

Vladimir Sheremet 2 years ago
parent
commit
1f19dc23a7

+ 0 - 134
app/frontend/apps/mobile/components/CommonObjectAttributes/CommonObjectAttributes.vue

@@ -1,134 +0,0 @@
-<!-- Copyright (C) 2012-2022 Zammad Foundation, https://zammad-foundation.org/ -->
-
-<script setup lang="ts">
-import type { Component } from 'vue'
-import { computed } from 'vue'
-import { keyBy } from 'lodash-es'
-import type { Dictionary } from 'lodash'
-import { camelize } from '@shared/utils/formatter'
-import type {
-  ObjectAttributeValue,
-  ObjectManagerFrontendAttribute,
-} from '@shared/graphql/types'
-import { useSessionStore } from '@shared/stores/session'
-import type { ObjectLike } from '@shared/types/utils'
-import type { AttributeDeclaration } from './types'
-import CommonSectionMenu from '../CommonSectionMenu/CommonSectionMenu.vue'
-import CommonSectionMenuItem from '../CommonSectionMenu/CommonSectionMenuItem.vue'
-
-export interface Props {
-  object: ObjectLike
-  attributes: ObjectManagerFrontendAttribute[]
-  skipAttributes?: string[]
-}
-
-const props = defineProps<Props>()
-
-const attributesDeclarations = import.meta.glob<AttributeDeclaration>(
-  './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>,
-)
-
-const attributesObject = computed<Dictionary<ObjectAttributeValue>>(() => {
-  return keyBy(props.object.objectAttributeValues || {}, 'attribute.name')
-})
-
-const getValue = (key: string) => {
-  if (key in attributesObject.value) {
-    return attributesObject.value[key].value
-  }
-  if (key in props.object) {
-    return props.object[key]
-  }
-  return props.object[camelize(key)]
-}
-
-const isEmpty = (value: unknown) => {
-  if (Array.isArray(value)) {
-    return value.length === 0
-  }
-  // null or undefined or ''
-  return value == null || value === ''
-}
-
-const session = useSessionStore()
-
-interface AttributeField {
-  attribute: ObjectManagerFrontendAttribute
-  component: Component
-  value: unknown
-}
-
-const fields = computed<AttributeField[]>(() => {
-  return props.attributes
-    .map((attribute) => {
-      let value = getValue(attribute.name)
-
-      if (typeof value !== 'boolean' && !value) {
-        value = attribute.dataOption?.default
-      }
-
-      return {
-        attribute,
-        component: definitionsByType[attribute.dataType],
-        value,
-      }
-    })
-    .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 !props.skipAttributes?.includes(attribute.name)
-    })
-})
-</script>
-
-<template>
-  <CommonSectionMenu v-if="fields.length">
-    <template v-for="field of fields" :key="field.attribute.name">
-      <CommonSectionMenuItem :label="field.attribute.display">
-        <!-- TODO link template might have #{}, but we don't have access to those, it should come from backend -->
-        <CommonLink
-          v-if="field.attribute.dataOption?.linktemplate"
-          :link="field.attribute.dataOption?.linktemplate"
-          class="cursor-pointer text-blue"
-        >
-          <Component
-            :is="field.component"
-            :attribute="field.attribute"
-            :value="field.value"
-          />
-        </CommonLink>
-        <Component
-          :is="field.component"
-          v-else
-          :attribute="field.attribute"
-          :value="field.value"
-        />
-      </CommonSectionMenuItem>
-    </template>
-    <slot name="after-fields" />
-  </CommonSectionMenu>
-</template>

+ 2 - 0
app/frontend/apps/mobile/initialize.ts

@@ -10,6 +10,7 @@ import initializeStore from '@shared/stores'
 import initializeGlobalComponents from '@shared/initializer/globalComponents'
 import initializeForm from '@mobile/form'
 import initializeGlobalProperties from '@shared/initializer/globalProperties'
+import { initializeObjectAttributes } from './object-attributes/initializeObjectAttributes'
 
 export default function initializeApp(app: App) {
   // TODO remove when Vue 3.3 released
@@ -19,6 +20,7 @@ export default function initializeApp(app: App) {
   initializeGlobalComponents(app)
   initializeGlobalProperties(app)
   initializeForm(app)
+  initializeObjectAttributes()
 
   return app
 }

+ 15 - 0
app/frontend/apps/mobile/object-attributes/initializeObjectAttributes.ts

@@ -0,0 +1,15 @@
+// Copyright (C) 2012-2022 Zammad Foundation, https://zammad-foundation.org/
+
+import { setupObjectAttributes } from '@shared/components/ObjectAttributes/config'
+import CommonSectionMenu from '@mobile/components/CommonSectionMenu/CommonSectionMenu.vue'
+import CommonSectionMenuItem from '@mobile/components/CommonSectionMenu/CommonSectionMenuItem.vue'
+
+export const initializeObjectAttributes = () => {
+  setupObjectAttributes({
+    outer: CommonSectionMenu,
+    wrapper: CommonSectionMenuItem,
+    classes: {
+      link: 'cursor-pointer text-blue',
+    },
+  })
+}

+ 2 - 2
app/frontend/apps/mobile/pages/organization/views/OrganizationDetailView.vue

@@ -13,7 +13,7 @@ import { ErrorStatusCodes } from '@shared/types/error'
 import { useOrganizationEdit } from '@mobile/entities/organization/composables/useOrganizationEdit'
 import OrganizationMembersList from '@mobile/components/Organization/OrganizationMembersList.vue'
 import { AvatarOrganization } from '@shared/components/CommonOrganizationAvatar'
-import CommonObjectAttributes from '@mobile/components/CommonObjectAttributes/CommonObjectAttributes.vue'
+import ObjectAttributes from '@shared/components/ObjectAttributes/ObjectAttributes.vue'
 import { useOrganizationTicketsCount } from '@mobile/entities/organization/composables/useOrganizationTicketsCount'
 import { useOrganizationDetail } from '@mobile/entities/organization/composables/useOrganizationDetail'
 
@@ -74,7 +74,7 @@ const ticketData = computed(() => getTicketData(organization.value))
       </div>
     </div>
 
-    <CommonObjectAttributes
+    <ObjectAttributes
       :object="organization"
       :attributes="objectAttributes"
       :skip-attributes="['name']"

+ 3 - 3
app/frontend/apps/mobile/pages/ticket/views/TicketInformation/TicketInformationCustomer.vue

@@ -6,7 +6,7 @@ import { useUserEdit } from '@mobile/entities/user/composables/useUserEdit'
 import { useUsersTicketsCount } from '@mobile/entities/user/composables/useUserTicketsCount'
 import { watchEffect, computed } from 'vue'
 import CommonTicketStateList from '@mobile/components/CommonTicketStateList/CommonTicketStateList.vue'
-import CommonObjectAttributes from '@mobile/components/CommonObjectAttributes/CommonObjectAttributes.vue'
+import ObjectAttributes from '@shared/components/ObjectAttributes/ObjectAttributes.vue'
 import { useSessionStore } from '@shared/stores/session'
 import CommonLoader from '@mobile/components/CommonLoader/CommonLoader.vue'
 import CommonUserAvatar from '@shared/components/CommonUserAvatar/CommonUserAvatar.vue'
@@ -67,7 +67,7 @@ const secondaryOrganizations = computed(() =>
     </div>
   </CommonLoader>
   <div v-if="user">
-    <CommonObjectAttributes
+    <ObjectAttributes
       :attributes="objectAttributes"
       :object="user"
       :skip-attributes="['firstname', 'lastname']"
@@ -81,7 +81,7 @@ const secondaryOrganizations = computed(() =>
           {{ $t('Edit Customer') }}
         </button>
       </template>
-    </CommonObjectAttributes>
+    </ObjectAttributes>
     <CommonOrganizationsList
       :organizations="secondaryOrganizations.array"
       :total-count="secondaryOrganizations.totalCount"

+ 3 - 3
app/frontend/apps/mobile/pages/ticket/views/TicketInformation/TicketInformationOrganization.vue

@@ -5,7 +5,7 @@ import { computed, ref, watchEffect } from 'vue'
 import CommonLoader from '@mobile/components/CommonLoader/CommonLoader.vue'
 import CommonOrganizationAvatar from '@shared/components/CommonOrganizationAvatar/CommonOrganizationAvatar.vue'
 import CommonTicketStateList from '@mobile/components/CommonTicketStateList/CommonTicketStateList.vue'
-import CommonObjectAttributes from '@mobile/components/CommonObjectAttributes/CommonObjectAttributes.vue'
+import ObjectAttributes from '@shared/components/ObjectAttributes/ObjectAttributes.vue'
 import { useOrganizationEdit } from '@mobile/entities/organization/composables/useOrganizationEdit'
 import OrganizationMembersList from '@mobile/components/Organization/OrganizationMembersList.vue'
 import { useSessionStore } from '@shared/stores/session'
@@ -60,7 +60,7 @@ const ticketsData = computed(() => getTicketData(organization.value))
     </div>
   </CommonLoader>
   <div v-if="organization">
-    <CommonObjectAttributes
+    <ObjectAttributes
       :object="organization"
       :attributes="objectAttributes"
       :skip-attributes="['name']"
@@ -73,7 +73,7 @@ const ticketsData = computed(() => getTicketData(organization.value))
           {{ $t('Edit Organization') }}
         </button>
       </template>
-    </CommonObjectAttributes>
+    </ObjectAttributes>
 
     <OrganizationMembersList
       :organization="organization"

+ 2 - 2
app/frontend/apps/mobile/pages/user/views/UserDetailView.vue

@@ -6,7 +6,7 @@ import { useUserEdit } from '@mobile/entities/user/composables/useUserEdit'
 import { computed, ref } from 'vue'
 import CommonLoader from '@mobile/components/CommonLoader/CommonLoader.vue'
 import CommonUserAvatar from '@shared/components/CommonUserAvatar/CommonUserAvatar.vue'
-import CommonObjectAttributes from '@mobile/components/CommonObjectAttributes/CommonObjectAttributes.vue'
+import ObjectAttributes from '@shared/components/ObjectAttributes/ObjectAttributes.vue'
 import CommonTicketStateList from '@mobile/components/CommonTicketStateList/CommonTicketStateList.vue'
 import type { CommonButtonOption } from '@mobile/components/CommonButtonGroup/types'
 import CommonButtonGroup from '@mobile/components/CommonButtonGroup/CommonButtonGroup.vue'
@@ -110,7 +110,7 @@ const secondaryOrganizations = computed(() =>
       </CommonLink>
     </div>
 
-    <CommonObjectAttributes
+    <ObjectAttributes
       :attributes="objectAttributes"
       :object="user"
       :skip-attributes="['firstname', 'lastname']"

+ 2 - 2
app/frontend/apps/mobile/components/CommonObjectAttributes/CommonObjectAttributes.story.vue → app/frontend/shared/components/ObjectAttributes/ObjectAttributes.story.vue

@@ -3,7 +3,7 @@
 <script setup lang="ts">
 import { keyBy } from 'lodash-es'
 import type { ObjectManagerFrontendAttribute } from '@shared/graphql/types'
-import CommonObjectAttributes from './CommonObjectAttributes.vue'
+import ObjectAttributes from './ObjectAttributes.vue'
 import attributesJson from './__tests__/attributes.json'
 
 const attributes = attributesJson as ObjectManagerFrontendAttribute[]
@@ -69,6 +69,6 @@ const object = {
 
 <template>
   <Story>
-    <CommonObjectAttributes :attributes="attributes" :object="object" />
+    <ObjectAttributes :attributes="attributes" :object="object" />
   </Story>
 </template>

+ 49 - 0
app/frontend/shared/components/ObjectAttributes/ObjectAttributes.vue

@@ -0,0 +1,49 @@
+<!-- Copyright (C) 2012-2022 Zammad Foundation, https://zammad-foundation.org/ -->
+
+<script setup lang="ts">
+import type { ObjectManagerFrontendAttribute } from '@shared/graphql/types'
+import type { ObjectLike } from '@shared/types/utils'
+import { objectAttributesConfig } from './config'
+import { useDisplayObjectAttributes } from './useDisplayObjectAttributes'
+
+export interface Props {
+  object: ObjectLike
+  attributes: ObjectManagerFrontendAttribute[]
+  skipAttributes?: string[]
+}
+
+const props = defineProps<Props>()
+
+const { fields } = useDisplayObjectAttributes(props)
+</script>
+
+<template>
+  <Component :is="objectAttributesConfig.outer" v-if="fields.length">
+    <template v-for="field of fields" :key="field.attribute.name">
+      <Component
+        :is="objectAttributesConfig.wrapper"
+        :label="field.attribute.display"
+      >
+        <!-- TODO link template might have #{}, but we don't have access to those, it should come from backend -->
+        <CommonLink
+          v-if="field.attribute.dataOption?.linktemplate"
+          :link="field.attribute.dataOption?.linktemplate"
+          :class="objectAttributesConfig.classes.link"
+        >
+          <Component
+            :is="field.component"
+            :attribute="field.attribute"
+            :value="field.value"
+          />
+        </CommonLink>
+        <Component
+          :is="field.component"
+          v-else
+          :attribute="field.attribute"
+          :value="field.value"
+        />
+      </Component>
+    </template>
+    <slot name="after-fields" />
+  </Component>
+</template>

+ 9 - 9
app/frontend/apps/mobile/components/CommonObjectAttributes/__tests__/CommonObjectAttributes.spec.ts → app/frontend/shared/components/ObjectAttributes/__tests__/ObjectAttributes.spec.ts

@@ -9,7 +9,7 @@ import { mockApplicationConfig } from '@tests/support/mock-applicationConfig'
 import { mockPermissions } from '@tests/support/mock-permissions'
 import { flushPromises } from '@vue/test-utils'
 import { keyBy } from 'lodash-es'
-import CommonObjectAttributes from '../CommonObjectAttributes.vue'
+import ObjectAttributes from '../ObjectAttributes.vue'
 import attributes from './attributes.json'
 
 const attributesByKey = keyBy(attributes, 'name')
@@ -81,7 +81,7 @@ describe('common object attributes interface', () => {
       ]),
     )
 
-    const view = renderComponent(CommonObjectAttributes, {
+    const view = renderComponent(ObjectAttributes, {
       props: {
         object,
         attributes,
@@ -137,7 +137,7 @@ describe('common object attributes interface', () => {
     const object = {
       active: true,
     }
-    const view = renderComponent(CommonObjectAttributes, {
+    const view = renderComponent(ObjectAttributes, {
       props: {
         object,
         attributes: [attributesByKey.active],
@@ -161,7 +161,7 @@ describe('common object attributes interface', () => {
         },
       ],
     }
-    const view = renderComponent(CommonObjectAttributes, {
+    const view = renderComponent(ObjectAttributes, {
       props: {
         object,
         attributes: [attributesByKey.login],
@@ -180,7 +180,7 @@ describe('common object attributes interface', () => {
       name: 'login',
       display: 'Login',
     }
-    const view = renderComponent(CommonObjectAttributes, {
+    const view = renderComponent(ObjectAttributes, {
       props: {
         object,
         attributes: [
@@ -202,7 +202,7 @@ describe('common object attributes interface', () => {
       phone: '+49 123456789',
     }
 
-    const view = renderComponent(CommonObjectAttributes, {
+    const view = renderComponent(ObjectAttributes, {
       props: {
         object,
         attributes: [
@@ -263,7 +263,7 @@ describe('common object attributes interface', () => {
       ]),
     )
 
-    const view = renderComponent(CommonObjectAttributes, {
+    const view = renderComponent(ObjectAttributes, {
       props: {
         object,
         attributes,
@@ -299,7 +299,7 @@ describe('common object attributes interface', () => {
       { ...attributesByKey.date_time_field, name: 'future', display: 'future' },
     ]
 
-    const view = renderComponent(CommonObjectAttributes, {
+    const view = renderComponent(ObjectAttributes, {
       props: {
         object,
         attributes,
@@ -335,7 +335,7 @@ describe('common object attributes interface', () => {
       { ...attributesByKey.address, name: 'show', display: 'show' },
     ]
 
-    const view = renderComponent(CommonObjectAttributes, {
+    const view = renderComponent(ObjectAttributes, {
       props: {
         object,
         attributes,

Some files were not shown because too many files changed in this diff