1
\$\begingroup\$

I have this code, but I need to return pure functions instead the mutating functions.

Is it possible? Perhaps using OOP? I don't know how to improve it.

I tried to return the values in each function, but I haven't had success.

import { AssetStatusType } from '@domain/interfaces/common'

import {
  BothComponentType,
  ComponentsType,
  GroupFiltersType,
  OperationType,
  OverviewModelType,
  PendenciesOverviewType,
  PendenciesType,
  StructurePendenciesCount,
  StructureStatusCount
} from '../../types'

const updateStatusCount = (
  statusCount: StructureStatusCount,
  status: AssetStatusType
) => {
  statusCount[status] = (statusCount[status] || 0) + 1
}

const updatePendenciesCount = (
  pendenciesCount: StructurePendenciesCount,
  key: AssetStatusType,
  subKey: OperationType | PendenciesOverviewType
) => {
  pendenciesCount[key] = pendenciesCount[key] ?? {}
  pendenciesCount[key][subKey] = (pendenciesCount[key]?.[subKey] || 0) + 1
}

const processOverviewPendencies = (
  pendencies: PendenciesType[] | null | undefined,
  pendenciesCount: StructurePendenciesCount,
  countedIds: Set<string>,
  id: string
) => {
  if (pendencies?.length) {
    for (const { state, pendencyType } of pendencies) {
      const uniqueId = `${state}-${pendencyType}-${id}`
      if (!countedIds.has(uniqueId)) {
        updatePendenciesCount(pendenciesCount, state, pendencyType)

        countedIds.add(uniqueId)
      }
    }
  }
}

const processOverviewComponents = (
  components: ComponentsType[],
  isGroupByTree: boolean,
  statusCount: StructureStatusCount,
  pendenciesCount: StructurePendenciesCount,
  countedIds: Set<string>,
  id: string,
  type: BothComponentType
) => {
  for (const { pendencies, status, operationType } of components) {
    if (isGroupByTree && type === 'location') {
      updateStatusCount(statusCount, status)

      if (operationType) {
        updatePendenciesCount(pendenciesCount, status, operationType)
      }
    }
    processOverviewPendencies(pendencies, pendenciesCount, countedIds, id)
  }
}

const processOverviewData = (
  data: OverviewModelType[],
  groupBy: GroupFiltersType,
  statusCount: StructureStatusCount,
  pendenciesCount: StructurePendenciesCount,
  countedIds: Set<string>
) => {
  const isGroupByTree = groupBy === 'tree'
  const isGroupByAsset = groupBy === 'asset'

  for (const { id, status, components, operationType, type } of data) {
    if (isGroupByAsset || type === 'asset') {
      updateStatusCount(statusCount, status)
      if (operationType) {
        updatePendenciesCount(pendenciesCount, status, operationType)
      }
    }

    processOverviewComponents(
      components,
      isGroupByTree,
      statusCount,
      pendenciesCount,
      countedIds,
      id,
      type
    )
  }
}

export const calculateOverviewCounts = (
  data: OverviewModelType[],
  groupBy: GroupFiltersType
) => {
  const statusCount: StructureStatusCount = {} as StructureStatusCount
  const pendenciesCount: StructurePendenciesCount =
    {} as StructurePendenciesCount

  const countedIds = new Set<string>()

  processOverviewData(data, groupBy, statusCount, pendenciesCount, countedIds)

  return { ...statusCount, pendencies: pendenciesCount }
}

Is there a cleaner and more elegant way to do this? I need to return an object like this:

// calculateOverviewCounts return this

{
    pendencies: StructurePendenciesCount;
    working: number;
    inAlert: number;
    warning: number;
    stopped: number;
    off: number;
}

StructurePendenciesCount is: StructurePendenciesCount

\$\endgroup\$
1

1 Answer 1

0
\$\begingroup\$

I don't really understand what the code is trying to do, but I can comment in general.

When you have a tree structure like this, you can usually flatten it to make the problem much easier to solve. Some tips:

  • Use Array.flatMap to un-nest the tree.
  • Use lodash uniqBy (or write your own) on the flattened tree to get unique pendency items before counting them
  • Use lodash countBy (or write your own) to count by status
  • Replace if (pendencies?.length) with pendencies ?? [] to reduce branching (and shouldn't it be "dependencies"?)
  • Try to answer each question with a different function (as opposed to answering all questions within one iteration of the tree structure). If performance is a concern (doubtful), you may ignore this
  • Fix the data structure, if that's possible. Name things the same (state vs status). groupBy asset is on a type of asset, but groupBy tree is on a location. Seems a bit odd. Status in general seems like it could be in a separate, attached object instead of inlined in each model.

For example, to get status counts (all code is untested. The filters seem wrong):

const components = overviews.flatMap(overview => overview.components)
const assetOverviews = overviews.filter(overview => groupBy === 'asset' && overview.type === 'asset')
const treeComponents = components.filter(component => groupBy === 'tree' && component.type === 'location')
const statusCounts = _.countBy([...assetOverviews,...treeComponents], x => x.status)

Similarly for pendencies:

const uniquePendencies = _(overviews)
    .flatMap(overview => overview.components.map(component => ({ component, overview })))
    .flatMap(({ overview, component }) => (component.pendencies ?? []).map(pendency => ({ overview, pendency })))
    .uniqBy(({ overview, pendency }) => `${pendency.state}-${pendency.pendencyType}-${overview.id}`)
    .map(x => x.pendency)
    .value()

You can then map to a common structure for all your arrays:

const pendencyCountItems = [
    ...overviews.filter(o => o.operationType).map(o => ({ key: o.status, subKey: o.operationType })),
    ...components.filter(o => o.operationType).map(o => ({ key: o.status, subKey: o.operationType })),
    ...uniquePendencies.map(o => ({ key: o.state, subKey: o.pendencyType })),
]

Which you can then group and count:

const pendencyCounts = _(pendencyCountItems)
    .groupBy('key')
    .mapValues(itemsThisStatus => _.countBy(itemsThisStatus, 'subType'))
    .value()
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.