Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/faster block catalog reset #41

Merged
merged 6 commits into from
Feb 19, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
6 changes: 1 addition & 5 deletions .github/workflows/eslint.yml
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,6 @@ jobs:
- name: Install Node dependencies
run: npm ci --omit=optional

- name: Install eslint-plugin-prettier v5.0.0 # Do this to get around a bug in the version that @10up/eslint-config requires.
run: npm install eslint-plugin-prettier@^5.0.0

- name: Get updated JS files
id: changed-files
uses: tj-actions/changed-files@v41
Expand All @@ -42,5 +39,4 @@ jobs:

- name: Run JS linting
if: ${{ steps.changed-files.outputs.any_changed == 'true' }}
run: |
npx 10up-toolkit lint-js ${{ steps.changed-files.outputs.all_changed_files }}
run: ./node_modules/.bin/10up-toolkit lint-js ${{ steps.changed-files.outputs.all_changed_files }}
67 changes: 6 additions & 61 deletions assets/js/admin/indexer.js
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,10 @@ class Indexer extends EventTarget {

cancel() {
this.cancelPending();
this.triggerEvent('indexCancel', { progress: this.progress, total: this.total });
this.triggerEvent('indexCancel', {
progress: this.progress,
total: this.total,
});
}

loadTerms(opts) {
Expand Down Expand Up @@ -150,69 +153,11 @@ class Indexer extends EventTarget {
});
}

async deleteIndex(ids, opts) {
async deleteIndex(opts) {
this.progress = 0;
this.completed = 0;
this.failures = 0;
this.total = ids.length;
this.triggerEvent('deleteIndexStart', { progress: 0, total: this.total });

const chunks = this.toChunks(ids, opts.batchSize || 50);
const n = chunks.length;

for (let i = 0; i < n; i++) {
const batch = chunks[i];
try {
await this.deleteIndexBatch(batch, opts); // eslint-disable-line no-await-in-loop
} catch (e) {
this.failures += batch.length;
this.triggerEvent('deleteIndexError', e);
}
}

this.triggerEvent('deleteIndexComplete', {
progress: this.progress,
total: this.total,
completed: this.completed,
failures: this.failures,
});
}

async deleteIndexBatch(batch, opts = {}) {
const fetchOpts = {
url: opts.endpoint,
method: 'POST',
data: {
term_ids: batch,
},
...opts,
};

const promise = this.apiFetch(fetchOpts);

promise.then((res) => {
if (res.errors) {
this.failures += batch.length;
this.triggerEvent('deleteIndexError', res);
} else if (!res.success && res.data) {
this.failures += batch.length;
this.triggerEvent('deleteIndexError', res);
} else {
this.completed += batch.length;
}

this.progress += batch.length;
this.triggerEvent('deleteIndexProgress', {
progress: this.progress,
total: this.total,
...res,
});
});

return promise;
}

deleteIndexBulk(opts) {
this.total = 0;
this.triggerEvent('deleteIndexStart');

const fetchOpts = {
Expand Down
63 changes: 4 additions & 59 deletions assets/js/admin/tools.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,6 @@ class ToolsApp {
this.onIndex('indexCancel', 'didIndexCancel');
this.onIndex('indexError', 'didIndexError');

this.onIndex('loadTermsStart', 'didLoadTermsStart');
this.onIndex('loadTermsComplete', 'didLoadTermsComplete');
this.onIndex('loadTermsError', 'didLoadTermsError');

this.onIndex('deleteIndexStart', 'didDeleteIndexStart');
this.onIndex('deleteIndexProgress', 'didDeleteIndexProgress');
this.onIndex('deleteIndexComplete', 'didDeleteIndexComplete');
Expand Down Expand Up @@ -247,57 +243,6 @@ class ToolsApp {
this.updateProgress();
}

didLoadTermsStart() {
const message = __('Loading block catalog terms to delete ...', 'block-catalog');

this.setState({ status: 'loading-terms', message });
this.hideErrors();
this.setNotice('');

window.scrollTo(0, 0);
}

didLoadTermsComplete(event) {
const message = __('Loaded terms to delete, starting ...', 'block-catalog');
this.setState({ status: 'loaded-terms', message, ...event.detail });

const opts = {
batchSize: this.settings?.delete_index_batch_size,
endpoint: this.settings?.delete_index_endpoint,
};

let { terms } = this.state;
terms = terms.map((term) => term.id);

this.indexer.deleteIndex(terms, opts);
}

didLoadTermsError(event) {
const err = event.detail || {};

let message = __('Failed to load terms to delete.', 'block-catalog');

if (err?.terms?.length === 0) {
message = __('Block catalog is empty, nothing to delete.', 'block-catalog');
this.setState({ status: 'load-terms-error', message: '', error: '' });
this.setNotice(message, 'error');
return;
}

if (err?.message) {
message += ` (${err?.code} - ${err.message})`;
}

if (err?.data?.message) {
message += ` (${err.data.message})`;
} else if (typeof err?.data === 'string') {
message += ` (${err.data})`;
}

this.setState({ status: 'load-terms-error', message: '', error: err });
this.setNotice(message, 'error');
}

didDeleteIndexStart(event) {
const message = __('Deleting Index ...', 'block-catalog');
this.setState({ status: 'deleting', message, ...event.detail });
Expand All @@ -324,10 +269,10 @@ class ToolsApp {
);

this.setNotice(message, 'error');
} else if (event.detail?.completed) {
} else if (event.detail?.removed) {
message = sprintf(
__('Deleted %d block catalog term(s) successfully.', 'block-catalog'),
event.detail?.completed,
event.detail?.removed,
);

this.setNotice(message, 'success');
Expand Down Expand Up @@ -390,10 +335,10 @@ class ToolsApp {
}

const opts = {
endpoint: this.settings.terms_endpoint,
endpoint: this.settings.delete_index_endpoint,
};

this.indexer.loadTerms(opts);
this.indexer.deleteIndex(opts);
return false;
}

Expand Down
93 changes: 59 additions & 34 deletions includes/classes/CatalogBuilder.php
Original file line number Diff line number Diff line change
Expand Up @@ -42,51 +42,77 @@ public function catalog( $post_id, $opts = [] ) {
}

/**
* Resets the Block Catalog by removing all catalog terms.
* Bulk deletes all block catalog terms and their relationships via Direct DB query.
* This is a faster alternative to wp_delete_term() which is slow for large catalogs.
*
* @param array $opts Optional args
* @return array
*/
public function delete_index( $opts = [] ) { // phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.Found
\BlockCatalog\Utility\start_bulk_operation();

$term_opts = [
'taxonomy' => BLOCK_CATALOG_TAXONOMY,
'fields' => 'ids',
'hide_empty' => false,
];
global $wpdb;

$terms = get_terms( $term_opts );
$total = count( $terms );
$removed = 0;
$errors = 0;
$is_cli = defined( 'WP_CLI' ) && WP_CLI;

// translators: %d is number of block catalog terms
$message = sprintf( __( 'Removing %d block catalog terms ...', 'block-catalog' ), $total );
$removed = 0;

if ( $is_cli ) {
$progress_bar = \WP_CLI\Utils\make_progress_bar( $message, $total );
// Delete term relationships
// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery
$term_relationships = $wpdb->query(
$wpdb->prepare(
"DELETE FROM {$wpdb->term_relationships}
WHERE term_taxonomy_id IN (
SELECT term_taxonomy_id FROM {$wpdb->term_taxonomy} WHERE taxonomy = %s
)",
BLOCK_CATALOG_TAXONOMY
)
);

if ( false === $term_relationships ) {
++$errors;
}

foreach ( $terms as $term_id ) {
if ( $is_cli ) {
$progress_bar->tick();
}
// Delete term taxonomy
// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery
$term_taxonomy = $wpdb->query(
$wpdb->prepare(
"DELETE FROM {$wpdb->term_taxonomy} WHERE taxonomy = %s",
BLOCK_CATALOG_TAXONOMY
)
);

if ( false === $term_taxonomy ) {
++$errors;
} else {
$removed = $term_taxonomy;
}

$result = $this->delete_term_index( $term_id );
// Delete terms
// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery
$terms = $wpdb->query(
$wpdb->prepare(
"DELETE FROM {$wpdb->terms}
WHERE term_id IN (
SELECT term_id FROM {$wpdb->term_taxonomy} WHERE taxonomy = %s
)",
BLOCK_CATALOG_TAXONOMY
)
);

if ( false === $terms ) {
++$errors;
}

\BlockCatalog\Utility\clear_caches();
// update block catalog term counts = 0
// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery
$wpdb->query(
$wpdb->prepare(
"UPDATE {$wpdb->term_taxonomy} SET count = 0 WHERE taxonomy = %s",
BLOCK_CATALOG_TAXONOMY
)
);

if ( ! is_wp_error( $result ) ) {
++$removed;
} else {
++$errors;
}
}
clean_term_cache( [], BLOCK_CATALOG_TAXONOMY, true );

if ( $is_cli ) {
$progress_bar->finish();
}
$is_cli = defined( 'WP_CLI' ) && WP_CLI;

if ( $is_cli ) {
if ( ! empty( $removed ) ) {
Expand All @@ -97,12 +123,11 @@ public function delete_index( $opts = [] ) { // phpcs:ignore Generic.CodeAnalysi
}

if ( ! empty( $errors ) ) {
// translators: %d is number of catalog terms removed
\WP_CLI::warning( sprintf( 'Failed to remove %d block catalog terms(s).', 'block-catalog' ), $errors );
}
}

\BlockCatalog\Utility\stop_bulk_operation();

return [
'removed' => $removed,
'errors' => $errors,
Expand Down
48 changes: 7 additions & 41 deletions includes/classes/RESTSupport.php
Original file line number Diff line number Diff line change
Expand Up @@ -91,13 +91,6 @@ public function register_endpoints() {
'permission_callback' => function () {
return current_user_can( \BlockCatalog\Utility\get_required_capability() );
},
'args' => [
'term_ids' => [
'required' => true,
'type' => 'array',
'validate_callback' => [ $this, 'validate_term_ids' ],
],
],
]
);
}
Expand Down Expand Up @@ -136,28 +129,18 @@ public function get_terms() {
/**
* Deletes the Block catalog index.
*
* @param \WP_REST_Request $request The request object
* @return array
*/
public function delete_index( $request ) {
public function delete_index() {
\BlockCatalog\Utility\start_bulk_operation();

$term_ids = $request->get_param( 'term_ids' );
$updated = 0;
$errors = 0;
$builder = new CatalogBuilder();

foreach ( $term_ids as $term_id ) {
$result = $builder->delete_term_index( $term_id );
$updated = 0;
$errors = 0;
$builder = new CatalogBuilder();

\BlockCatalog\Utility\clear_caches();

if ( is_wp_error( $result ) ) {
++$errors;
} else {
++$updated;
}
}
$result = $builder->delete_index( [ 'bulk' => true ] );
$updated = $result['removed'];
$errors = $result['errors'];

\BlockCatalog\Utility\stop_bulk_operation();

Expand Down Expand Up @@ -318,21 +301,4 @@ public function validate_post_ids( $post_ids ) {

return ! empty( $post_ids );
}

/**
* Validates the specified term ids.
*
* @param array $term_ids The term ids to validate
* @return bool
*/
public function validate_term_ids( $term_ids ) {
if ( empty( $term_ids ) ) {
return true;
}

$term_ids = array_map( 'intval', $term_ids );
$term_ids = array_filter( $term_ids );

return ! empty( $term_ids );
}
}
Loading
Loading