Skip to content

Commit

Permalink
Added missing tags functionalities (#714)
Browse files Browse the repository at this point in the history
* changed tags function, added according job to display progress, cleanup old code

* satisfied linter

* readded tag assign/unassign on check, added revert option to optimistic upate

* disable button if there are no tags

* cleanup job

* added `hasChildren` prop to `IElementDraft`, disabled tag assign button if there are no  children

* cleanup

* added translations

* build files

* Automatic frontend build

---------

Co-authored-by: Corepex <[email protected]>
  • Loading branch information
Corepex and Corepex authored Nov 21, 2024
1 parent dd60071 commit 340efb6
Show file tree
Hide file tree
Showing 28 changed files with 2,366 additions and 80 deletions.
3 changes: 1 addition & 2 deletions assets/js/src/core/components/tree-element/tree-element.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@ export interface TreeDataItem extends TreeDataNode {
interface ITreeElementProps extends TreeProps {
treeData: TreeDataItem[]
className?: string
onCheck?: (checkedKeys: { checked: Key[], halfChecked: Key[] } | Key[]) => void
onActionsClick?: (key: any, action: string) => void
onDragAndDrop?: (params: { node: TreeDataItem, dragNode: TreeDataItem, dropPosition: number }) => void
onSelected?: (key: any) => void
Expand Down Expand Up @@ -81,7 +80,7 @@ const TreeElement = (props: ITreeElementProps): React.JSX.Element => {
expandAction='click'
expandedKeys={ expandedKeys }
loadData={ onLoadData !== null ? onLoadData : undefined }
onCheck={ (checkedKeys): void => onCheck?.(checkedKeys) }
onCheck={ (checkedKeys, event): void => onCheck?.(checkedKeys, event) }
onDrop={ (evt): void => {
onDragAndDrop?.({
node: evt.node as TreeDataItem,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,9 @@
import React, { type Key } from 'react'
import {
type Tag,
type TagAssignToElementApiArg
type TagAssignToElementApiArg,
useTagAssignToElementMutation,
useTagUnassignFromElementMutation
} from '@Pimcore/modules/element/editor/shared-tab-manager/tabs/tags/tags-api-slice.gen'
import {
useCreateTreeStructure
Expand All @@ -26,6 +28,7 @@ import { flattenArray } from '@Pimcore/modules/element/editor/shared-tab-manager
import { Flex } from '@Pimcore/components/flex/flex'
import { TreeElement } from '@Pimcore/components/tree-element/tree-element'
import { SearchInput } from '@Pimcore/components/search-input/search-input'
import { type TreeProps } from 'antd'

export interface TagsTreeProps {
elementId: number
Expand All @@ -42,6 +45,8 @@ export const TagsTree = ({ elementId, elementType, tags, setFilter, isLoading, d
const treeData = createTreeStructure({ tags })
const { updateTagsForElementByTypeAndId } = useOptimisticUpdate()
const flatTags = flattenArray(tags)
const [assignTag] = useTagAssignToElementMutation()
const [unassignTag] = useTagUnassignFromElementMutation()

const applyTagsToElement = async (checkedTags: Key[]): Promise<void> => {
updateTagsForElementByTypeAndId({
Expand All @@ -54,8 +59,54 @@ export const TagsTree = ({ elementId, elementType, tags, setFilter, isLoading, d
setDefaultCheckedTags(checkedTags)
}

const handleCheck = (checkedKeys: { checked: Key[], halfChecked: Key[] }): void => {
const assignTagToElement = async (tagId: number): Promise<void> => {
const assignTask = assignTag({
elementType,
id: elementId,
tagId
})

assignTask.catch(() => {
console.log('Failed to assign tag to element')
})

const response = (await assignTask) as any

if (response.error !== undefined) {
throw new Error(response.error.data.error as string)
}
}

const removeTagFromElement = async (tagId: number): Promise<void> => {
const unassignTask = unassignTag({
elementType,
id: elementId,
tagId
})

unassignTask.catch(() => {
console.log('Failed to remove tag from element')
})

const response = (await unassignTask) as any

if (response.error !== undefined) {
throw new Error(response.error.data.error as string)
}
}

const handleCheck: TreeProps['onCheck'] = async (checkedKeys: { checked: Key[], halfChecked: Key[] }, info): Promise<void> => {
const tagId = Number(info.node.key)

void applyTagsToElement(checkedKeys.checked)

try {
info.checked
? await assignTagToElement(tagId)
: await removeTagFromElement(tagId)
} catch (e) {
void applyTagsToElement(checkedKeys.checked.filter((key) => key !== tagId))
}
}

return (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,92 +11,64 @@
* @license https://github.com/pimcore/studio-ui-bundle/blob/1.x/LICENSE.md POCL and PCL
*/

import { api as tagsApi, type Tag } from '@Pimcore/modules/element/editor/shared-tab-manager/tabs/tags/tags-api-slice.gen'
import { useAppDispatch } from '@Pimcore/app/store'
import {
type TagBatchOperationToElementsByTypeAndIdApiArg,
useTagBatchOperationToElementsByTypeAndIdMutation
} from '@Pimcore/modules/element/editor/shared-tab-manager/tabs/tags/tags-api-slice.gen'
import { useElementContext } from '@Pimcore/modules/element/hooks/use-element-context'
import { useElementDraft } from '@Pimcore/modules/element/hooks/use-element-draft'
import { useJobs } from '@Pimcore/modules/execution-engine/hooks/useJobs'
import { createJob } from '@Pimcore/modules/execution-engine/jobs/tag-assign/factory'
import { defaultTopics, topics } from '@Pimcore/modules/execution-engine/topics'

interface UseShortcutActionsReturn {
applyFolderTags: () => Promise<void>
removeCurrentAndApplyFolderTags: () => Promise<void>
removeAndApplyTagsToChildren: () => Promise<void>
applyTagsToChildren: () => Promise<void>
}

export const useShortcutActions = (): UseShortcutActionsReturn => {
const { id, elementType } = useElementContext()
const dispatch = useAppDispatch()
const { element } = useElementDraft(id, elementType)
const parentId = element?.parentId
const [tagBatchMutation] = useTagBatchOperationToElementsByTypeAndIdMutation()
const { addJob } = useJobs()

const getCurrentAndParentTags = async (): Promise<Awaited<any>> => {
const parentTags = await dispatch(tagsApi.endpoints.tagGetCollectionForElementByTypeAndId.initiate({
const assignTags = async (operation: TagBatchOperationToElementsByTypeAndIdApiArg['operation']): Promise<number> => {
const assignTask = tagBatchMutation({
elementType,
id: parentId!
}))
id,
operation
})

const currentTags = await dispatch(tagsApi.endpoints.tagGetCollectionForElementByTypeAndId.initiate({
elementType,
id
}))
assignTask.catch(() => {
console.log('Failed to apply tags to children')
})

return { parentTags, currentTags }
}
const response = (await assignTask) as any

const applyFolderTags = async (): Promise<void> => {
Promise.resolve(getCurrentAndParentTags())
.then(async ({ parentTags, currentTags }) => {
const saveParentTags = parentTags.data?.items ?? []
const saveChildrenTags = currentTags.data?.items ?? []
const items: Tag[] = { ...saveParentTags, ...saveChildrenTags }
if (response.error !== undefined) {
throw new Error(response.error.data.error as string)
}

dispatch(
tagsApi.util.updateQueryData(
'tagGetCollectionForElementByTypeAndId',
{
elementType,
id
},
(draft): any => {
return {
totalItems: items.length,
items
}
}
)
)
})
.catch((error) => {
console.error(error)
})
const data = response.data
return data.jobRunId
}

const removeCurrentAndApplyFolderTags = async (): Promise<void> => {
Promise.resolve(getCurrentAndParentTags())
.then(async ({ parentTags }) => {
const items: Tag[] = parentTags.data?.items ?? []
const applyTagsToChildren = async (): Promise<void> => {
addJob(createJob({
title: 'Assign tags to children',
topics: [topics['tag-assignment-finished'], ...defaultTopics],
action: async () => await assignTags('assign')
}))
}

dispatch(
tagsApi.util.updateQueryData(
'tagGetCollectionForElementByTypeAndId',
{
elementType,
id
},
(draft): any => {
return {
totalItems: Object.keys(items).length,
items
}
}
)
)
})
.catch((error) => {
console.error(error)
})
const removeAndApplyTagsToChildren = async (): Promise<void> => {
addJob(createJob({
title: 'Replace and assign tags to children',
topics: [topics['tag-replacement-finished'], ...defaultTopics],
action: async () => await assignTags('replace')
}))
}

return {
applyFolderTags,
removeCurrentAndApplyFolderTags
removeAndApplyTagsToChildren,
applyTagsToChildren
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -30,11 +30,13 @@ import { SplitLayout } from '@Pimcore/components/split-layout/split-layout'
import { Content } from '@Pimcore/components/content/content'
import { Header } from '@Pimcore/components/header/header'
import { useElementContext } from '@Pimcore/modules/element/hooks/use-element-context'
import { useElementDraft } from '@Pimcore/modules/element/hooks/use-element-draft'

export const TagsTabContainer = (): React.JSX.Element => {
const { t } = useTranslation()
const { id, elementType } = useElementContext()
const { applyFolderTags, removeCurrentAndApplyFolderTags } = useShortcutActions()
const { element } = useElementDraft(id, elementType)
const { applyTagsToChildren, removeAndApplyTagsToChildren } = useShortcutActions()

const { data, isLoading } = useTagGetCollectionForElementByTypeAndIdQuery({
elementType,
Expand Down Expand Up @@ -67,16 +69,17 @@ export const TagsTabContainer = (): React.JSX.Element => {
<Content padded>
<Header title={ t('tags.assigned-tags-text') }>
<Dropdown.Button
disabled={ data?.totalItems === 0 || element?.hasChildren !== true }
menu={ {
items: [{
label: 'Remove current element tags & Apply folder tags',
label: t('tags.remove-and-apply-tags-to-children'),
key: '1',
onClick: removeCurrentAndApplyFolderTags
onClick: removeAndApplyTagsToChildren
}]
} }
onClick={ applyFolderTags }
onClick={ applyTagsToChildren }
>
{t('tags.apply-folder-tags')}
{t('tags.apply-tags-to-children')}
</Dropdown.Button>
</Header>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ interface IElementDraft extends PropertiesDraft, SchedulesDraft, TrackableChange
parentId: number
fullPath?: string
type?: string
hasChildren?: boolean
permissions?: ElementPermissions
}

Expand Down
2 changes: 2 additions & 0 deletions assets/js/src/core/modules/execution-engine/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import { NotificationJobContainer as DownloadJobContainer } from './jobs/downloa
import { NotificationJobContainer as ZipUploadJobContainer } from './jobs/zip-upload/notification-job-container'
import { NotificationJobContainer as DeleteJobContainer } from './jobs/delete/notification-job-container'
import { NotificationJobContainer as CloneJobContainer } from './jobs/clone/notification-job-container'
import { NotificationJobContainer as TagAssignJobContainer } from './jobs/tag-assign/notification-job-container'

export const executionEngineModule: AbstractModule = {
onInit () {
Expand All @@ -30,6 +31,7 @@ export const executionEngineModule: AbstractModule = {
jobComponentRegistry.registerComponent('zip-upload', ZipUploadJobContainer)
jobComponentRegistry.registerComponent('delete', DeleteJobContainer)
jobComponentRegistry.registerComponent('clone', CloneJobContainer)
jobComponentRegistry.registerComponent('tag-assign', TagAssignJobContainer)
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
/**
* Pimcore
*
* This source file is available under two different licenses:
* - Pimcore Open Core License (POCL)
* - Pimcore Commercial License (PCL)
* Full copyright and license information is available in
* LICENSE.md which is distributed with this source code.
*
* @copyright Copyright (c) Pimcore GmbH (http://www.pimcore.org)
* @license https://github.com/pimcore/studio-ui-bundle/blob/1.x/LICENSE.md POCL and PCL
*/

import { type AbstractJob, JobStatus } from '../abstact-job'
import { getUniqueId } from '../factory-helper'

export interface TagAssignJob extends AbstractJob {
type: 'tag-assign'
config: undefined
}

export interface TagAssignFactoryArgs {
action: AbstractJob['action']
title: AbstractJob['title']
topics: AbstractJob['topics']
}

export const createJob = (job: TagAssignFactoryArgs): TagAssignJob => {
return {
id: getUniqueId(),
action: job.action,
type: 'tag-assign',
title: job.title,
status: JobStatus.QUEUED,
topics: job.topics,
config: undefined
}
}
Loading

0 comments on commit 340efb6

Please sign in to comment.