Skip to content

Commit

Permalink
Patterns: Fix pattern categories on import (#58926)
Browse files Browse the repository at this point in the history
Co-authored-by: ntsekouras <ntsekouras@git.wordpress.org>
Co-authored-by: aaronrobertshaw <aaronrobertshaw@git.wordpress.org>
Co-authored-by: glendaviesnz <glendaviesnz@git.wordpress.org>
Co-authored-by: bph <bph@git.wordpress.org>
Co-authored-by: hanneslsm <hanneslsm@git.wordpress.org>
Co-authored-by: colorful-tones <colorful-tones@git.wordpress.org>
Co-authored-by: annezazu <annezazu@git.wordpress.org>
  • Loading branch information
8 people committed Feb 13, 2024
1 parent f790549 commit 8057df3
Show file tree
Hide file tree
Showing 4 changed files with 125 additions and 90 deletions.
38 changes: 27 additions & 11 deletions packages/edit-site/src/components/add-new-pattern/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,11 @@ import {
PATTERN_DEFAULT_CATEGORY,
TEMPLATE_PART_POST_TYPE,
} from '../../utils/constants';
import usePatternCategories from '../sidebar-navigation-screen-patterns/use-pattern-categories';

const { useHistory, useLocation } = unlock( routerPrivateApis );
const { CreatePatternModal } = unlock( editPatternsPrivateApis );
const { CreatePatternModal, useAddPatternCategory } = unlock(
editPatternsPrivateApis
);

export default function AddNewPattern() {
const history = useHistory();
Expand All @@ -43,7 +44,6 @@ export default function AddNewPattern() {
const { createSuccessNotice, createErrorNotice } =
useDispatch( noticesStore );
const patternUploadInputRef = useRef();
const { patternCategories } = usePatternCategories();

function handleCreatePattern( { pattern, categoryId } ) {
setShowPatternModal( false );
Expand Down Expand Up @@ -97,6 +97,7 @@ export default function AddNewPattern() {
title: __( 'Import pattern from JSON' ),
} );

const { categoryMap, findOrCreateTerm } = useAddPatternCategory();
return (
<>
<DropdownMenu
Expand Down Expand Up @@ -132,12 +133,23 @@ export default function AddNewPattern() {
const file = event.target.files?.[ 0 ];
if ( ! file ) return;
try {
const currentCategoryId =
params.categoryType !== TEMPLATE_PART_POST_TYPE &&
patternCategories.find(
( category ) =>
category.name === params.categoryId
)?.id;
let currentCategoryId;
// When we're not handling template parts, we should
// add or create the proper pattern category.
if ( params.categoryType !== TEMPLATE_PART_POST_TYPE ) {
const currentCategory = categoryMap
.values()
.find(
( term ) => term.name === params.categoryId
);
if ( !! currentCategory ) {
currentCategoryId =
currentCategory.id ||
( await findOrCreateTerm(
currentCategory.label
) );
}
}
const pattern = await createPatternFromFile(
file,
currentCategoryId
Expand All @@ -146,8 +158,12 @@ export default function AddNewPattern() {
);

// Navigate to the All patterns category for the newly created pattern
// if we're not on that page already.
if ( ! currentCategoryId ) {
// if we're not on that page already and if we're not in the `my-patterns`
// category.
if (
! currentCategoryId &&
params.categoryId !== 'my-patterns'
) {
history.push( {
path: `/patterns`,
categoryType: PATTERN_TYPES.theme,
Expand Down
84 changes: 5 additions & 79 deletions packages/patterns/src/components/create-pattern-modal.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,21 +10,17 @@ import {
ToggleControl,
} from '@wordpress/components';
import { __, _x } from '@wordpress/i18n';
import { useState, useMemo } from '@wordpress/element';
import { useDispatch, useSelect } from '@wordpress/data';
import { useState } from '@wordpress/element';
import { useDispatch } from '@wordpress/data';
import { store as noticesStore } from '@wordpress/notices';
import { store as coreStore } from '@wordpress/core-data';

/**
* Internal dependencies
*/
import { PATTERN_DEFAULT_CATEGORY, PATTERN_SYNC_TYPES } from '../constants';

/**
* Internal dependencies
*/
import { store as patternsStore } from '../store';
import CategorySelector, { CATEGORY_SLUG } from './category-selector';
import CategorySelector from './category-selector';
import { useAddPatternCategory } from '../private-hooks';
import { unlock } from '../lock-unlock';

export default function CreatePatternModal( {
Expand Down Expand Up @@ -59,47 +55,9 @@ export function CreatePatternModalContents( {

const [ isSaving, setIsSaving ] = useState( false );
const { createPattern } = unlock( useDispatch( patternsStore ) );
const { saveEntityRecord, invalidateResolution } = useDispatch( coreStore );
const { createErrorNotice } = useDispatch( noticesStore );

const { corePatternCategories, userPatternCategories } = useSelect(
( select ) => {
const { getUserPatternCategories, getBlockPatternCategories } =
select( coreStore );

return {
corePatternCategories: getBlockPatternCategories(),
userPatternCategories: getUserPatternCategories(),
};
}
);

const categoryMap = useMemo( () => {
// Merge the user and core pattern categories and remove any duplicates.
const uniqueCategories = new Map();
userPatternCategories.forEach( ( category ) => {
uniqueCategories.set( category.label.toLowerCase(), {
label: category.label,
name: category.name,
id: category.id,
} );
} );

corePatternCategories.forEach( ( category ) => {
if (
! uniqueCategories.has( category.label.toLowerCase() ) &&
// There are two core categories with `Post` label so explicitly remove the one with
// the `query` slug to avoid any confusion.
category.name !== 'query'
) {
uniqueCategories.set( category.label.toLowerCase(), {
label: category.label,
name: category.name,
} );
}
} );
return uniqueCategories;
}, [ userPatternCategories, corePatternCategories ] );
const { categoryMap, findOrCreateTerm } = useAddPatternCategory();

async function onCreate( patternTitle, sync ) {
if ( ! title || isSaving ) {
Expand Down Expand Up @@ -137,38 +95,6 @@ export function CreatePatternModalContents( {
}
}

/**
* @param {string} term
* @return {Promise<number>} The pattern category id.
*/
async function findOrCreateTerm( term ) {
try {
const existingTerm = categoryMap.get( term.toLowerCase() );
if ( existingTerm && existingTerm.id ) {
return existingTerm.id;
}
// If we have an existing core category we need to match the new user category to the
// correct slug rather than autogenerating it to prevent duplicates, eg. the core `Headers`
// category uses the singular `header` as the slug.
const termData = existingTerm
? { name: existingTerm.label, slug: existingTerm.name }
: { name: term };
const newTerm = await saveEntityRecord(
'taxonomy',
CATEGORY_SLUG,
termData,
{ throwOnError: true }
);
invalidateResolution( 'getUserPatternCategories' );
return newTerm.id;
} catch ( error ) {
if ( error.code !== 'term_exists' ) {
throw error;
}

return error.data.term_id;
}
}
return (
<form
onSubmit={ ( event ) => {
Expand Down
2 changes: 2 additions & 0 deletions packages/patterns/src/private-apis.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import PatternsMenuItems from './components';
import RenamePatternCategoryModal from './components/rename-pattern-category-modal';
import PartialSyncingControls from './components/partial-syncing-controls';
import ResetOverridesControl from './components/reset-overrides-control';
import { useAddPatternCategory } from './private-hooks';
import {
PATTERN_TYPES,
PATTERN_DEFAULT_CATEGORY,
Expand All @@ -35,6 +36,7 @@ lock( privateApis, {
RenamePatternCategoryModal,
PartialSyncingControls,
ResetOverridesControl,
useAddPatternCategory,
PATTERN_TYPES,
PATTERN_DEFAULT_CATEGORY,
PATTERN_USER_CATEGORY,
Expand Down
91 changes: 91 additions & 0 deletions packages/patterns/src/private-hooks.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
/**
* WordPress dependencies
*/
import { useSelect, useDispatch } from '@wordpress/data';
import { store as coreStore } from '@wordpress/core-data';
import { useMemo } from '@wordpress/element';

/**
* Internal dependencies
*/
import { CATEGORY_SLUG } from './components/category-selector';

/**
* Helper hook that creates a Map with the core and user patterns categories
* and removes any duplicates. It's used when we need to create new user
* categories when creating or importing patterns.
* This hook also provides a function to find or create a pattern category.
*
* @return {Object} The merged categories map and the callback function to find or create a category.
*/
export function useAddPatternCategory() {
const { saveEntityRecord, invalidateResolution } = useDispatch( coreStore );
const { corePatternCategories, userPatternCategories } = useSelect(
( select ) => {
const { getUserPatternCategories, getBlockPatternCategories } =
select( coreStore );

return {
corePatternCategories: getBlockPatternCategories(),
userPatternCategories: getUserPatternCategories(),
};
},
[]
);
const categoryMap = useMemo( () => {
// Merge the user and core pattern categories and remove any duplicates.
const uniqueCategories = new Map();
userPatternCategories.forEach( ( category ) => {
uniqueCategories.set( category.label.toLowerCase(), {
label: category.label,
name: category.name,
id: category.id,
} );
} );

corePatternCategories.forEach( ( category ) => {
if (
! uniqueCategories.has( category.label.toLowerCase() ) &&
// There are two core categories with `Post` label so explicitly remove the one with
// the `query` slug to avoid any confusion.
category.name !== 'query'
) {
uniqueCategories.set( category.label.toLowerCase(), {
label: category.label,
name: category.name,
} );
}
} );
return uniqueCategories;
}, [ userPatternCategories, corePatternCategories ] );

async function findOrCreateTerm( term ) {
try {
const existingTerm = categoryMap.get( term.toLowerCase() );
if ( existingTerm?.id ) {
return existingTerm.id;
}
// If we have an existing core category we need to match the new user category to the
// correct slug rather than autogenerating it to prevent duplicates, eg. the core `Headers`
// category uses the singular `header` as the slug.
const termData = existingTerm
? { name: existingTerm.label, slug: existingTerm.name }
: { name: term };
const newTerm = await saveEntityRecord(
'taxonomy',
CATEGORY_SLUG,
termData,
{ throwOnError: true }
);
invalidateResolution( 'getUserPatternCategories' );
return newTerm.id;
} catch ( error ) {
if ( error.code !== 'term_exists' ) {
throw error;
}
return error.data.term_id;
}
}

return { categoryMap, findOrCreateTerm };
}

1 comment on commit 8057df3

@github-actions
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Flaky tests detected in 8057df3.
Some tests passed with failed attempts. The failures may not be related to this commit but are still reported for visibility. See the documentation for more information.

🔍 Workflow run URL: https://github.com/WordPress/gutenberg/actions/runs/7893233566
📝 Reported issues:

Please sign in to comment.