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

feat: Add garbage collection cleanup admin and cron job #227

Merged
Merged
Show file tree
Hide file tree
Changes from 11 commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
c4d2efa
Add garbage collection cleanup admin and cron job
markkelnar Jul 11, 2023
ebe21a2
Delete in smaller batch events instead of all posts
markkelnar Jul 12, 2023
dc3db02
bump some version numbers
markkelnar Jul 14, 2023
63d3716
Add tests for admin settings garbage collection options
markkelnar Jul 14, 2023
81b4885
Add garbage collection unit tests
markkelnar Jul 14, 2023
e9166b8
Merge remote-tracking branch 'origin/main' into feature/garbage-colle…
markkelnar Jul 14, 2023
456d979
Get age from settings value in utils class
markkelnar Jul 14, 2023
9ff1e21
Add admin editor ability to skip garbage collection per query
markkelnar Jul 14, 2023
378ebed
Add tax_query to gargage collection to ignore where docs opt out
markkelnar Jul 18, 2023
1334dfc
Rename garbage collection class
markkelnar Jul 18, 2023
025b7c7
Add groups collection for documents. Refactor garbage collection to i…
markkelnar Jul 20, 2023
f8e6a7a
fix code sniff white space
markkelnar Jul 20, 2023
72dd923
Update gc text in graphql settings to talk about groups
markkelnar Jul 20, 2023
0f7baaa
Add the word "delete" to text
markkelnar Jul 20, 2023
c99ab11
Merge remote-tracking branch 'origin/main' into feature/garbage-colle…
markkelnar Jul 21, 2023
614a874
Use garbage_collect instead of gc
markkelnar Jul 24, 2023
a55d6e9
text description change.
markkelnar Jul 24, 2023
037df6e
snake case function name
markkelnar Jul 24, 2023
5af7178
Merge remote-tracking branch 'me/feature/garbage-collect-aged-queries…
markkelnar Jul 24, 2023
55fc784
fix for consistent names
markkelnar Jul 24, 2023
514894a
enable admin quick-edit for document groups
markkelnar Jul 24, 2023
b65efd9
Merge remote-tracking branch 'origin/main' into feature/garbage-colle…
markkelnar Jul 25, 2023
22cf8f5
Merge commit '9a023bfd9243b02f80861c56fab9c20d04c4a79a' into feature/…
jasonbahl Jul 25, 2023
35168d9
re-add allow-plugin for codesniffer-installer
markkelnar Jul 26, 2023
294bbee
Merge remote-tracking branch 'me/feature/garbage-collect-aged-queries…
markkelnar Jul 26, 2023
19923e0
add filter for garbage collect recurrence
markkelnar Jul 26, 2023
2495e72
merge fixes
markkelnar Jul 28, 2023
3a7f141
composer update
markkelnar Jul 28, 2023
92dbc42
Merge branch 'main' into feature/garbage-collect-aged-queries
jasonbahl Aug 9, 2023
7923d53
fix phpstan suggestions
markkelnar Aug 9, 2023
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
4 changes: 2 additions & 2 deletions .env.dist
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ MYSQL_USER=${DB_USER}
MYSQL_PASSWORD=${DB_PASSWORD}

# docker container env vars
WP_VERSION=5.9
PHP_VERSION=8.0
WP_VERSION=6.1
PHP_VERSION=8.1
jasonbahl marked this conversation as resolved.
Show resolved Hide resolved
WPGRAPHQL_VERSION=latest
DATA_DUMP_DIR=/var/www/html/wp-content/plugins/wp-graphql-smart-cache/tests/_data
4 changes: 2 additions & 2 deletions .env.testing
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ MYSQL_USER=${DB_USER}
MYSQL_PASSWORD=${DB_PASSWORD}

# docker container env vars
WP_VERSION=5.9
PHP_VERSION=8.0
WP_VERSION=6.1
PHP_VERSION=8.1
WPGRAPHQL_VERSION=latest
DATA_DUMP_DIR=/var/www/html/wp-content/plugins/wp-graphql-smart-cache/tests/_data
365 changes: 144 additions & 221 deletions composer.lock

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ services:
local:

app_db:
image: mariadb:10.2
image: mariadb:10.11
env_file:
- .env.dist
ports:
Expand All @@ -46,7 +46,7 @@ services:
local:

testing_db:
image: mariadb:10.2
image: mariadb:10.11
env_file:
- .env.testing
ports:
Expand Down
1 change: 1 addition & 0 deletions src/Admin/Editor.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
use WPGraphQL\SmartCache\Document;
use WPGraphQL\SmartCache\Document\Grant;
use WPGraphQL\SmartCache\Document\MaxAge;
use WPGraphQL\SmartCache\Document\GarbageCollection;
markkelnar marked this conversation as resolved.
Show resolved Hide resolved
use GraphQL\Error\SyntaxError;
use GraphQL\Server\RequestError;

Expand Down
43 changes: 43 additions & 0 deletions src/Admin/Settings.php
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,49 @@ function () {
]
);

register_graphql_settings_field(
'graphql_persisted_queries_section',
[
'name' => 'query_gc',
markkelnar marked this conversation as resolved.
Show resolved Hide resolved
'label' => __( 'Clean Up Queries', 'wp-graphql-smart-cache' ),
'desc' => __( 'Toggle on to enable garbage collection of saved queries older than number of days specified below', 'wp-graphql-smart-cache' ),
'type' => 'checkbox',
'default' => 'off',
'sanitize_callback' => function ( $value ) {
/**
* When enable garbage collection,
* schedule the garbage collection action/event to run once daily.
* Otherwise remove it.
*/
if ( 'on' === $value ) {
if ( ! wp_next_scheduled( 'wp_graphql_smart_cache_query_gc' ) ) {
// Add scheduled job to run in one minute
wp_schedule_event( time() + 60, 'daily', 'wp_graphql_smart_cache_query_gc' );
markkelnar marked this conversation as resolved.
Show resolved Hide resolved
}
} else {
wp_clear_scheduled_hook( 'wp_graphql_smart_cache_query_gc' );
}
return $value;
},
]
);

register_graphql_settings_field(
'graphql_persisted_queries_section',
[
'name' => 'query_gc_age',
'desc' => __( 'Age, in number of days, of saved query when it will be removed', 'wp-graphql-smart-cache' ),
'type' => 'number',
'default' => '30',
'sanitize_callback' => function ( $value ) {
if ( 1 > $value || ! is_numeric( $value ) ) {
return function_exists( 'get_graphql_setting' ) ? \get_graphql_setting( 'query_gc_age', false, 'graphql_persisted_queries_section' ) : null;
}
return (int) $value;
},
]
);

// Add a tab section to the graphql admin settings page
register_graphql_settings_section(
'graphql_cache_section',
Expand Down
56 changes: 56 additions & 0 deletions src/Document/GarbageCollection.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
<?php
/**
* Content
*
* @package Wp_Graphql_Smart_Cache
*/

namespace WPGraphQL\SmartCache\Document;

use WPGraphQL\SmartCache\Admin\Settings;
use WPGraphQL\SmartCache\Document;
use WPGraphQL\SmartCache\Document\Group;
use GraphQL\Server\RequestError;

class GarbageCollection {

/**
* @param integer $number_of_posts Number of post ids matching criteria.
*
* @return [int] Array of post ids
*/
public static function getDocumentsByAge( $number_of_posts = 100 ) {
markkelnar marked this conversation as resolved.
Show resolved Hide resolved
// $days_ago Posts older than this many days ago
$days_ago = get_graphql_setting( 'query_gc_age', null, 'graphql_persisted_queries_section' );
if ( 1 > $days_ago || ! is_numeric( $days_ago ) ) {
return [];
}

// Query for saved query documents that are older than age and not skipping garbage collection.
// Get documents where no group taxonmy term is set.
$wp_query = new \WP_Query(
[
'post_type' => Document::TYPE_NAME,
'post_status' => 'publish',
jasonbahl marked this conversation as resolved.
Show resolved Hide resolved
'posts_per_page' => $number_of_posts,
'fields' => 'ids',
'date_query' => [
[
'column' => 'post_modified_gmt',
'before' => $days_ago . ' days ago',
],
],
// phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_tax_query
'tax_query' => [
[
'taxonomy' => Group::TAXONOMY_NAME,
markkelnar marked this conversation as resolved.
Show resolved Hide resolved
'field' => 'name',
'operator' => 'NOT EXISTS',
],
],
]
);

return $wp_query->get_posts();
}
}
50 changes: 50 additions & 0 deletions src/Document/Group.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
<?php
/**
* Content
*
* @package Wp_Graphql_Smart_Cache
*/

namespace WPGraphQL\SmartCache\Document;

use WPGraphQL\SmartCache\Admin\Settings;
use WPGraphQL\SmartCache\Document;

class Group {

const TAXONOMY_NAME = 'graphql_document_group';

public function init() {
register_taxonomy(
self::TAXONOMY_NAME,
Document::TYPE_NAME,
[
'description' => __( 'Tag the saved query document with other queries as a "group".', 'wp-graphql-smart-cache' ),
'labels' => [
'name' => __( 'Groups', 'wp-graphql-smart-cache' ),
'singular_name' => __( 'Group', 'wp-graphql-smart-cache' ),
],
'hierarchical' => false,
'public' => false,
'publicly_queryable' => false,
'show_admin_column' => true,
'show_in_menu' => Settings::show_in_admin(),
'show_ui' => Settings::show_in_admin(),
'show_in_quick_edit' => false,
markkelnar marked this conversation as resolved.
Show resolved Hide resolved
markkelnar marked this conversation as resolved.
Show resolved Hide resolved
'show_in_graphql' => true,
'graphql_single_name' => 'graphql_document_group',
'graphql_plural_name' => 'graphql_document_groups',
]
);
}

/**
* Look up the first group for a post
*
* @param int The post id
*/
public static function get( $post_id ) {
$item = get_the_terms( $post_id, self::TAXONOMY_NAME );
return ! is_wp_error( $item ) && isset( $item[0]->name ) ? $item[0]->name : '';
}
}
49 changes: 0 additions & 49 deletions tests/functional/AdminSettingsGrantCest.php

This file was deleted.

105 changes: 105 additions & 0 deletions tests/functional/AdminSettingsQueriesCest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
<?php

/**
* Test the wp-graphql settings page for saved queries.
*/

class AdminSettingsQueriesCest
{
public function _after( FunctionalTester $I ) {
$I->dontHaveOptionInDatabase( 'graphql_persisted_queries_section' );
$I->dontHaveOptionInDatabase( 'graphql_cache_section' );
}

public function saveAllowOnlySettingsTest( FunctionalTester $I ) {
$I->loginAsAdmin();

$I->amOnPage('/wp-admin/admin.php?page=graphql-settings#graphql_persisted_queries_section');
$I->selectOption("form input[type=radio]", 'only_allowed');

// Save and see the selection after form submit
$I->click('Save Changes');
$I->seeOptionIsSelected('form input[type=radio]', 'only_allowed');
}

public function testChangeAllowTriggersPurge( FunctionalTester $I ) {
$I->wantTo( 'Change the allow/deny grant global setting and verify cache is purged' );

// Enable caching for this test
$I->haveOptionInDatabase( 'graphql_cache_section', [ 'cache_toggle' => 'on' ] );

// put something in transient cache
$transient_name = '_transient_gql_cache_foo:bar';
$I->haveOptionInDatabase( $transient_name, [ 'bizz' => 'bang' ] );

// verify it's there
$transients = unserialize( $I->grabFromDatabase( 'wp_options', 'option_value', [ 'option_name' => $transient_name ] ) );
$I->assertEquals( 'bang', $transients['bizz'] );

// change the allow/deny setting in admin
$I->loginAsAdmin();
$I->amOnPage('/wp-admin/admin.php?page=graphql-settings#graphql_persisted_queries_section');
$I->selectOption("form input[type=radio]", 'only_allowed');
$I->click('Save Changes');

// verify the transient is gone
$transients = unserialize( $I->grabFromDatabase( 'wp_options', 'option_value', [ 'option_name like' => '_transient_gql_cache_%' ] ) );
$I->assertEmpty( $transients );
}

public function saveSettingCleanUpEnableTest( FunctionalTester $I ) {
$I->loginAsAdmin();

$I->amOnPage('/wp-admin/admin.php?page=graphql-settings#graphql_persisted_queries_section');
$I->checkOption("//input[@type='checkbox' and @name='graphql_persisted_queries_section[query_gc]']");
$I->click('Save Changes');
$I->seeCheckboxIsChecked("//input[@type='checkbox' and @name='graphql_persisted_queries_section[query_gc]']");

// Verify the cron event has been scheduled
$cron_names = [];
// The next few lines extracts the weird WP event schedule shape from the database.
$cron = unserialize( $I->grabFromDatabase( 'wp_options', 'option_value', [ 'option_name' => 'cron' ] ) );
foreach ( $cron as $events ) {
if ( is_array( $events ) ) {
$cron_names = array_merge( $cron_names, array_keys( $events ) );
}
}
codecept_debug( $cron_names );
$I->assertContains( 'wp_graphql_smart_cache_query_gc', $cron_names );

$I->uncheckOption("//input[@type='checkbox' and @name='graphql_persisted_queries_section[query_gc]']");
$I->click('Save Changes');
$I->dontSeeCheckboxIsChecked("//input[@type='checkbox' and @name='graphql_persisted_queries_section[query_gc]']");

// Verify the cron event has been removed
$cron_names = [];
// The next few lines extracts the weird WP event schedule shape from the database.
$cron = unserialize( $I->grabFromDatabase( 'wp_options', 'option_value', [ 'option_name' => 'cron' ] ) );
foreach ( $cron as $events ) {
if ( is_array( $events ) ) {
$cron_names = array_merge( $cron_names, array_keys( $events ) );
}
}
codecept_debug( $cron_names );
$I->assertNotContains( 'wp_graphql_smart_cache_query_gc', $cron_names );
}

// Test the garbage collection number of days validate and saves
public function saveSettingCleanUpDaysTest( FunctionalTester $I ) {
$I->loginAsAdmin();

$I->amOnPage('/wp-admin/admin.php?page=graphql-settings#graphql_persisted_queries_section');

$I->seeInField(['name' => 'graphql_persisted_queries_section[query_gc_age]'], '30');
$I->fillField(['name' => 'graphql_persisted_queries_section[query_gc_age]'], '50');
$I->click('Save Changes');
$I->seeInField(['name' => 'graphql_persisted_queries_section[query_gc_age]'], '50');

// If invalid value, should return previous saved value
$I->fillField(['name' => 'graphql_persisted_queries_section[query_gc_age]'], '-1');
$I->click('Save Changes');
$I->seeInField(['name' => 'graphql_persisted_queries_section[query_gc_age]'], '50');

}

}
Loading