Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
a743eea
Add Copilot autofill suggestions panel to mapping step
markus-moser May 21, 2026
c0d1c48
Polish autofill suggestions modal design
markus-moser May 21, 2026
8156a29
Fix lint and type errors in autofill suggestions
markus-moser May 21, 2026
a71f25e
Apply eslint-fixer changes
markus-moser May 21, 2026
c28645d
Automatic frontend build
markus-moser May 21, 2026
34b44d9
Fix ReDoS in tokenize: replace backtracking regex with lookahead split
markus-moser May 21, 2026
b83d9fb
Update package-lock.json after dependency install
markus-moser May 21, 2026
437dbe4
Automatic frontend build
markus-moser May 21, 2026
779cf2c
Automatic frontend build
markus-moser May 21, 2026
44742ca
Reduce cognitive complexity of computeAutofillSuggestions by extracti…
markus-moser May 21, 2026
af7b6a3
Automatic frontend build
markus-moser May 21, 2026
7ac2cb0
Replace CSS layout classes with Flex props, remove dead and redundant…
markus-moser May 21, 2026
1345503
Automatic frontend build
markus-moser May 21, 2026
a08d54e
Show spinner in preview result column while loading next row
markus-moser May 21, 2026
1c65098
Show spinner in preview result column while loading next row
markus-moser May 21, 2026
de68141
Automatic frontend build
markus-moser May 21, 2026
106c9bb
Replace hardcoded ISO language list with validLanguages from useSettings
markus-moser May 21, 2026
5228480
Automatic frontend build
markus-moser May 21, 2026
56f7bad
Add translations for all supported languages and clean up code
markus-moser May 21, 2026
4fc4c11
Update frontend build artifacts
markus-moser May 21, 2026
2eaa295
Automatic frontend build
markus-moser May 21, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
/**
* This source file is available under the terms of the
* Pimcore Open Core License (POCL)
* Full copyright and license information is available in
* LICENSE.md which is distributed with this source code.
*
* @copyright Copyright (c) Pimcore GmbH (https://www.pimcore.com)
* @license Pimcore Open Core License (POCL)
*/

import { createStyles } from 'antd-style'

export const useStyles = createStyles(({ css, token }) => {
return {
tableHeaderRow: css`
padding: ${token.paddingXXS}px ${token.paddingMD}px;
border-bottom: 1px solid ${token.colorBorderSecondary};
`,

tableHeaderCell: css`
color: ${token.colorTextSecondary};
white-space: nowrap;
`,

tableRow: css`
padding: ${token.paddingXXS}px ${token.paddingMD}px;
min-height: 36px;

&:hover {
background: ${token.colorFillAlter};
cursor: pointer;
}
`,

sourceCell: css`
flex: 1.5;
min-width: 0;
overflow: hidden;
`,

destinationCell: css`
flex: 2.5;
min-width: 0;
overflow: hidden;
`,

localeTag: css`
flex-shrink: 0;
font-size: ${token.fontSizeSM}px;
background-color: ${token.colorPrimaryBg} !important;
color: ${token.colorPrimary} !important;
border-color: ${token.colorPrimaryBorder} !important;
`,

resultCell: css`
flex: 2;
min-width: 0;
color: ${token.colorTextDescription};
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
`,

cellText: css`
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
`,

emptyState: css`
padding: ${token.paddingXL}px ${token.paddingMD}px;
`,

scoreText: css`
white-space: nowrap;
`,

dotGreen: css`
display: inline-block;
width: 10px;
height: 10px;
border-radius: 50%;
background-color: ${token.colorSuccess};
flex-shrink: 0;
`,

dotYellow: css`
display: inline-block;
width: 10px;
height: 10px;
border-radius: 50%;
background-color: ${token.colorWarning};
flex-shrink: 0;
`,

dotOrange: css`
display: inline-block;
width: 10px;
height: 10px;
border-radius: 50%;
background-color: ${token.colorWarningTextActive};
flex-shrink: 0;
`
}
})
Original file line number Diff line number Diff line change
@@ -0,0 +1,197 @@
/**
* This source file is available under the terms of the
* Pimcore Open Core License (POCL)
* Full copyright and license information is available in
* LICENSE.md which is distributed with this source code.
*
* @copyright Copyright (c) Pimcore GmbH (https://www.pimcore.com)
* @license Pimcore Open Core License (POCL)
*/

import React from 'react'
import { Checkbox, Flex, IconButton, NoContent, Spin, Tag } from '@pimcore/studio-ui-bundle/components'
import { useTranslation } from '@pimcore/studio-ui-bundle/app'
import { useTheme } from 'antd-style'
import { type MappingSuggestion } from '../utils/compute-autofill-suggestions'
import { createMappingItem } from '../utils/mapping-factory'
import { type MappingConfigItem } from '../../../../../types'
import { MappingArrowIcon } from '../mapping-item/arrow-column/mapping-arrow-icon.inline'
import { useStyles } from './autofill-suggestions-panel.styles'

export interface AutofillSuggestionsPanelProps {
suggestions: MappingSuggestion[]
selectedIds: Set<string>
onToggle: (id: string) => void
previewRow: Record<string, string | null>
hasPrevRow: boolean
isLoadingPreviewRow: boolean
onPrevRow: () => void
onNextRow: () => void
}

function scoreDotClass (score: number, styles: Record<string, string>): string {
if (score >= 90) return styles.dotGreen
if (score >= 70) return styles.dotYellow
return styles.dotOrange
}

export const AutofillSuggestionsPanel = ({
suggestions,
selectedIds,
onToggle,
previewRow,
hasPrevRow,
isLoadingPreviewRow,
onPrevRow,
onNextRow
}: AutofillSuggestionsPanelProps): React.JSX.Element => {
const { t } = useTranslation()
const { styles } = useStyles()
const theme = useTheme()

if (suggestions.length === 0) {
return (
<div className={ styles.emptyState }>
<NoContent text={ t('data-importer.mapping.autofill-suggestions.empty') } />
</div>
)
}

return (
<Flex vertical>
<Flex
align="center"
className={ styles.tableHeaderRow }
gap="small"
>
<div style={ { width: 24, flexShrink: 0 } } />
<div style={ { width: 56, flexShrink: 0 } }>
<span className={ styles.tableHeaderCell }>
{ t('data-importer.mapping.autofill-suggestions.score') }
</span>
</div>
<div className={ styles.sourceCell }>
<span className={ styles.tableHeaderCell }>
{ t('data-importer.mapping.autofill-suggestions.source') }
</span>
</div>
<div style={ { width: 36, flexShrink: 0 } } />
<Flex
align="center"
className={ styles.destinationCell }
gap={ theme.paddingXS }
>
<span className={ styles.tableHeaderCell }>
{ t('data-importer.mapping.autofill-suggestions.destination') }
</span>
</Flex>
<div className={ styles.resultCell }>
<Flex
align="center"
justify="space-between"
>
<span className={ styles.tableHeaderCell }>
{ t('data-importer.mapping.autofill-suggestions.result') }
</span>
<Flex
align="center"
gap={ 4 }
>
<IconButton
disabled={ !hasPrevRow || isLoadingPreviewRow }
icon={ { value: 'chevron-left' } }
onClick={ onPrevRow }
size="small"
type="text"
/>
<IconButton
disabled={ isLoadingPreviewRow }
icon={ { value: 'chevron-right' } }
onClick={ onNextRow }
size="small"
type="text"
/>
</Flex>
</Flex>
</div>
</Flex>

{ suggestions.map((suggestion) => (
<Flex
align="center"
className={ styles.tableRow }
gap="small"
key={ suggestion.id }
onClick={ () => { onToggle(suggestion.id) } }
>
<div style={ { width: 24, flexShrink: 0 } }>
<Checkbox
checked={ selectedIds.has(suggestion.id) }
onChange={ () => { onToggle(suggestion.id) } }
onClick={ (e) => { e.stopPropagation() } }
/>
</div>
<div style={ { width: 56, flexShrink: 0 } }>
<Flex
align="center"
gap={ 6 }
>
<span className={ styles.scoreText }>{ `${suggestion.score}%` }</span>
<span className={ scoreDotClass(suggestion.score, styles) } />
</Flex>
</div>
<div className={ styles.sourceCell }>
<span className={ styles.cellText }>{ `${suggestion.sourceLabel} [${suggestion.sourceIndex}]` }</span>
</div>
<Flex
align="center"
justify="center"
style={ { width: 36, flexShrink: 0 } }
>
<MappingArrowIcon fill={ theme.colorTextTertiary } />
</Flex>
<Flex
align="center"
className={ styles.destinationCell }
gap={ theme.paddingXS }
>
<span className={ styles.cellText }>
{ `Direct, ${suggestion.targetFieldLabel}` }
</span>
{ suggestion.language !== null && (
<Tag className={ styles.localeTag }>
{ suggestion.language }
</Tag>
) }
</Flex>
<div className={ styles.resultCell }>
{ isLoadingPreviewRow
? (
<Spin
size="small"
type="classic"
/>
)
: (previewRow[suggestion.sourceIndex] ?? '')
}
</div>
</Flex>
)) }
</Flex>
)
}

export function applySelectedSuggestions (
suggestions: MappingSuggestion[],
selectedIds: Set<string>
): MappingConfigItem[] {
return suggestions
.filter((s) => selectedIds.has(s.id))
.map((s) => createMappingItem(
s.sourceIndex,
s.sourceLabel,
'autofill',
s.targetFieldName,
s.language ?? undefined
))
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
/**
* This source file is available under the terms of the
* Pimcore Open Core License (POCL)
* Full copyright and license information is available in
* LICENSE.md which is distributed with this source code.
*
* @copyright Copyright (c) Pimcore GmbH (https://www.pimcore.com)
* @license Pimcore Open Core License (POCL)
*/

export * from './autofill-suggestions-panel'
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ export function useMappingStepLoader (configName: string, isActive: boolean): Us

const getMappingConfig = useCallback(
(): MappingConfigItem[] =>
((form.getFieldsValue() as DataImporterFormValues).mappingConfig ?? []),
((form.getFieldValue('mappingConfig') as MappingConfigItem[] | undefined) ?? []),
[form]
)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,19 +10,22 @@

import { createContext, useContext } from 'react'
import { type ClassAttribute } from '../../../../types'
import { type SourceRow } from './sources-panel/sources-panel'

export interface MappingItemContextValue {
configName: string
classId: string | undefined
columnHeaderOptions: Array<{ value: string, label: string }>
attributesMap: Record<string, ClassAttribute[]>
sourceRows: SourceRow[]
}

const MappingItemContext = createContext<MappingItemContextValue>({
configName: '',
classId: undefined,
columnHeaderOptions: [],
attributesMap: {}
attributesMap: {},
sourceRows: []
})

export const MappingItemContextProvider = MappingItemContext.Provider
Expand Down
Loading
Loading