Skip to content

Commit c819181

Browse files
committed
Add admin notice for WPGraphQL Logging plugin
Introduces an admin notice to inform users about potential performance impact of WPGraphQL Logging under heavy usage. Includes new AdminNotice class, template, AJAX dismissal handling, and related unit and e2e tests. Updates plugin setup to initialize the notice and ensures user meta is reset in test utilities.
1 parent 5247aa3 commit c819181

File tree

9 files changed

+318
-4
lines changed

9 files changed

+318
-4
lines changed

plugins/wpgraphql-logging/phpcs.xml

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,14 @@
5454
<rule ref="PSR12.Files.DeclareStatement"/>
5555
<rule ref="PEAR.NamingConventions.ValidClassName"/>
5656
<rule ref="PHPCompatibilityWP"/>
57-
<rule ref="WordPress-VIP-Go"/>
57+
<rule ref="WordPress-VIP-Go">
58+
<exclude name="WordPressVIPMinimum.JS.Window"/>
59+
<exclude name="WordPressVIPMinimum.JS.DangerouslySetInnerHTML"/>
60+
<exclude name="WordPressVIPMinimum.JS.InnerHTML"/>
61+
<exclude name="WordPressVIPMinimum.JS.StrippingTags"/>
62+
<exclude name="WordPressVIPMinimum.JS.StringConcat"/>
63+
<exclude name="WordPressVIPMinimum.JS.HTMLExecutingFunctions"/>
64+
</rule>
5865

5966
<rule ref="WordPress">
6067
<exclude name="Universal.Arrays.DisallowShortArraySyntax"/>
Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace WPGraphQL\Logging\Admin;
6+
7+
/**
8+
* The admin notice class for WPGraphQL Logging.
9+
*
10+
* @package WPGraphQL\Logging
11+
*
12+
* @since 0.0.1
13+
*/
14+
class AdminNotice {
15+
/**
16+
* The admin page slug.
17+
*
18+
* @var string
19+
*/
20+
public const ADMIN_NOTICE_KEY = 'wpgraphql-logging-admin-notice';
21+
22+
/**
23+
* The instance of the admin notice.
24+
*
25+
* @var self|null
26+
*/
27+
protected static ?self $instance = null;
28+
29+
/**
30+
* Initializes the view logs page.
31+
*/
32+
public static function init(): ?self {
33+
if ( ! current_user_can( 'manage_options' ) ) {
34+
return null;
35+
}
36+
37+
if ( ! isset( self::$instance ) || ! ( is_a( self::$instance, self::class ) ) ) {
38+
self::$instance = new self();
39+
self::$instance->setup();
40+
}
41+
42+
do_action( 'wpgraphql_logging_admin_notice_init', self::$instance );
43+
44+
return self::$instance;
45+
}
46+
47+
/**
48+
* Setup the admin notice.
49+
*/
50+
public function setup(): void {
51+
$key = self::ADMIN_NOTICE_KEY;
52+
$is_dismissed = $this->is_notice_dismissed();
53+
54+
// Exit if the notice has been dismissed.
55+
if ( $is_dismissed ) {
56+
return;
57+
}
58+
59+
add_action( 'admin_notices', [ $this, 'register_admin_notice' ], 10, 0 );
60+
add_action( 'wp_ajax_' . $key, [ $this, 'process_ajax_request' ], 10, 0 );
61+
}
62+
63+
/**
64+
* Register admin notice to inform users about WPGraphQL Logging.
65+
*/
66+
public function register_admin_notice(): void {
67+
$template = __DIR__ . '/View/Templates/WPGraphQLLoggerNotice.php';
68+
69+
if ( ! file_exists( $template ) ) {
70+
return;
71+
}
72+
73+
require $template; // phpcs:ignore WordPressVIPMinimum.Files.IncludingFile.UsingVariable
74+
}
75+
76+
/**
77+
* Process the AJAX request.
78+
*/
79+
public function process_ajax_request(): void {
80+
$key = self::ADMIN_NOTICE_KEY;
81+
if ( ! isset( $_POST['action'] ) || esc_attr( $key ) !== $_POST['action'] ) {
82+
return;
83+
}
84+
85+
check_ajax_referer( $key );
86+
87+
self::dismiss_admin_notice();
88+
}
89+
90+
/**
91+
* Check if the admin notice is dismissed.
92+
*/
93+
public function is_notice_dismissed(): bool {
94+
$key = self::ADMIN_NOTICE_KEY;
95+
return (bool) get_user_meta( get_current_user_id(), $key, true );
96+
}
97+
98+
/**
99+
* Dismiss the admin notice.
100+
*/
101+
public static function dismiss_admin_notice(): void {
102+
$key = self::ADMIN_NOTICE_KEY;
103+
update_user_meta( get_current_user_id(), $key, 1 );
104+
}
105+
}

plugins/wpgraphql-logging/src/Admin/SettingsPage.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ class SettingsPage {
4343
/**
4444
* Initializes the settings page.
4545
*/
46-
public static function init(): ?SettingsPage {
46+
public static function init(): ?self {
4747
if ( ! current_user_can( 'manage_options' ) ) {
4848
return null;
4949
}
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
<?php
2+
/**
3+
* Admin notice template.
4+
*
5+
* @package WPGraphQL\Logging
6+
*
7+
* @since 0.0.1
8+
*/
9+
10+
declare(strict_types=1);
11+
12+
use WPGraphQL\Logging\Admin\AdminNotice;
13+
14+
$wpgraphql_logging_key = AdminNotice::ADMIN_NOTICE_KEY;
15+
$wpgraphql_logging_ajax_nonce = wp_create_nonce( $wpgraphql_logging_key );
16+
$wpgraphql_logging_notice = __( "Heads up! While very useful for debugging, the WPGraphQL Logging Plugin can impact your site's performance under heavy usage, so please use it judiciously.", 'wpgraphql-logging' );
17+
?>
18+
19+
<div id="<?php echo esc_attr( $wpgraphql_logging_key ); ?>" class="notice wpgraphql-logging-admin-notice notice-warning is-dismissible">
20+
<p><?php echo esc_html( $wpgraphql_logging_notice ); ?></p>
21+
</div>
22+
23+
<script>
24+
window.addEventListener('load', function () {
25+
const dismissBtn = document.querySelector('#<?php echo esc_attr( $wpgraphql_logging_key ); ?>.wpgraphql-logging-admin-notice');
26+
dismissBtn?.addEventListener('click', function (event) {
27+
let postData = new FormData();
28+
postData.append('action', '<?php echo esc_attr( $wpgraphql_logging_key ); ?>');
29+
postData.append('_ajax_nonce', '<?php echo esc_html( $wpgraphql_logging_ajax_nonce ); ?>');
30+
31+
window.fetch('<?php echo esc_url( admin_url( 'admin-ajax.php' ) ); ?>', {
32+
method: 'POST',
33+
body: postData,
34+
})
35+
});
36+
});
37+
</script>
38+
39+
<?php

plugins/wpgraphql-logging/src/Admin/ViewLogsPage.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ class ViewLogsPage {
5555
/**
5656
* Initializes the view logs page.
5757
*/
58-
public static function init(): ?ViewLogsPage {
58+
public static function init(): ?self {
5959
if ( ! current_user_can( 'manage_options' ) ) {
6060
return null;
6161
}

plugins/wpgraphql-logging/src/Plugin.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
namespace WPGraphQL\Logging;
66

7+
use WPGraphQL\Logging\Admin\AdminNotice;
78
use WPGraphQL\Logging\Admin\Settings\ConfigurationHelper;
89
use WPGraphQL\Logging\Admin\SettingsPage;
910
use WPGraphQL\Logging\Admin\ViewLogsPage;
@@ -64,6 +65,7 @@ public function setup(): void {
6465
ConfigurationHelper::register_cache_hooks(); // Register cache hooks.
6566
SettingsPage::init(); // Settings page.
6667
ViewLogsPage::init(); // View logs page.
68+
AdminNotice::init(); // Admin notices.
6769
}
6870

6971
do_action( 'wpgraphql_logging_plugin_setup', self::$instance );

plugins/wpgraphql-logging/tests/e2e/plugins/reset-wpgraphql-logging-settings/reset-wpgraphql-logging-settings.php

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,5 +14,9 @@
1414
// Clear logs table
1515
$table_name = $wpdb->prefix . 'wpgraphql_logging';
1616
$wpdb->query("TRUNCATE TABLE {$table_name}");
17+
18+
19+
// Remove admin notice dismissed meta data
20+
delete_user_meta(get_current_user_id(), 'wpgraphql-logging-admin-notice');
1721
}
18-
});
22+
});
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
import { expect, test } from "@wordpress/e2e-test-utils-playwright";
2+
import {
3+
resetPluginSettings,
4+
goToLoggingSettingsPage,
5+
} from "../utils";
6+
7+
test.describe("WPGraphQL Logging Admin Notice", () => {
8+
test.beforeEach(async ({ admin, page }) => {
9+
await resetPluginSettings(admin); // Reset user meta data
10+
await goToLoggingSettingsPage(admin);
11+
await expect(page.locator("h1")).toHaveText("WPGraphQL Logging Settings");
12+
});
13+
14+
test("admin notice is displayed", async ({
15+
page,
16+
admin,
17+
}) => {
18+
await goToLoggingSettingsPage(admin);
19+
await expect(
20+
page.locator("#wpgraphql-logging-admin-notice")
21+
).toBeVisible();
22+
23+
await page.locator("#wpgraphql-logging-admin-notice.notice .notice-dismiss").click();
24+
await expect(
25+
page.locator("#wpgraphql-logging-admin-notice"),
26+
).not.toBeVisible();
27+
28+
await page.reload();
29+
await expect(
30+
page.locator("#wpgraphql-logging-admin-notice"),
31+
).not.toBeVisible();
32+
});
33+
});
Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace WPGraphQL\Logging\Tests\Admin;
6+
7+
use lucatume\WPBrowser\TestCase\WPTestCase;
8+
use WPGraphQL\Logging\Admin\AdminNotice;
9+
use ReflectionClass;
10+
use Mockery;
11+
12+
13+
/**
14+
* Test class for AdminNotice.
15+
*
16+
* @package WPGraphQL\Logging
17+
*
18+
* @since 0.0.1
19+
*/
20+
class AdminNoticeTest extends WPTestCase {
21+
22+
public function set_as_admin(): void {
23+
$admin_user = $this->factory->user->create(['role' => 'administrator']);
24+
wp_set_current_user($admin_user);
25+
set_current_screen('dashboard');
26+
}
27+
28+
public function set_dismissed_notice_meta_data(): void {
29+
update_user_meta( get_current_user_id(), AdminNotice::ADMIN_NOTICE_KEY, 1 );
30+
}
31+
32+
public function unset_dismissed_notice_meta_data(): void {
33+
delete_user_meta( get_current_user_id(), AdminNotice::ADMIN_NOTICE_KEY );
34+
}
35+
36+
public function test_admin_notice_instance() {
37+
$this->set_as_admin();
38+
39+
$reflection = new ReflectionClass( AdminNotice::class );
40+
$instanceProperty = $reflection->getProperty( 'instance' );
41+
$instanceProperty->setAccessible( true );
42+
$instanceProperty->setValue( null );
43+
44+
$this->assertNull( $instanceProperty->getValue() );
45+
$instance = AdminNotice::init();
46+
47+
$this->assertInstanceOf( AdminNotice::class, $instanceProperty->getValue() );
48+
$this->assertSame( $instance, $instanceProperty->getValue(), 'AdminNotice::init() should set the static instance property' );
49+
}
50+
51+
public function test_initialization_sets_instance_with_no_hooks_registered_as_not_admin(): void {
52+
wp_set_current_user(0);
53+
$instance = AdminNotice::init();
54+
$this->assertFalse(has_action('admin_notices', [$instance, 'register_admin_notice']));
55+
$this->assertFalse(has_action('wp_ajax_' . AdminNotice::ADMIN_NOTICE_KEY, [$instance, 'process_ajax_request']));
56+
}
57+
58+
public function test_initialization_sets_instance_with_no_hooks_registered_as_dismissed(): void {
59+
$this->set_as_admin();
60+
$this->set_dismissed_notice_meta_data();
61+
62+
$instance = AdminNotice::init();
63+
$this->assertFalse(has_action('admin_notices', [$instance, 'register_admin_notice']));
64+
$this->assertFalse(has_action('wp_ajax_' . AdminNotice::ADMIN_NOTICE_KEY, [$instance, 'process_ajax_request']));
65+
}
66+
67+
public function test_check_notice_is_displayed(): void {
68+
$this->set_as_admin();
69+
70+
$reflection = new ReflectionClass( AdminNotice::class );
71+
$instanceProperty = $reflection->getProperty( 'instance' );
72+
$instanceProperty->setAccessible( true );
73+
$instanceProperty->setValue( null );
74+
75+
$notice = AdminNotice::init(); // Notice should be now registered
76+
77+
$hook = has_action('admin_notices', [$notice, 'register_admin_notice']);
78+
$this->assertNotFalse($hook);
79+
$this->assertEquals(10, $hook);
80+
81+
$ajax_hook = has_action('wp_ajax_' . AdminNotice::ADMIN_NOTICE_KEY, [$notice, 'process_ajax_request']);
82+
$this->assertNotFalse($ajax_hook);
83+
$this->assertEquals(10, $ajax_hook);
84+
}
85+
86+
public function test_register_admin_notice_outputs_template(): void {
87+
$this->set_as_admin();
88+
89+
$notice = AdminNotice::init();
90+
91+
ob_start();
92+
$notice->register_admin_notice();
93+
$output = ob_get_clean();
94+
95+
$this->assertStringContainsString('wpgraphql-logging-admin-notice', $output);
96+
$this->assertStringContainsString('notice-warning', $output);
97+
$this->assertStringContainsString('is-dismissible', $output);
98+
$this->assertStringContainsString('Heads up! While very useful for debugging', $output);
99+
$this->assertStringContainsString('<script>', $output);
100+
}
101+
102+
public function test_dismiss_admin_notice_updates_user_meta(): void {
103+
$this->set_as_admin();
104+
105+
$notice = AdminNotice::init();
106+
$this->assertFalse($notice->is_notice_dismissed());
107+
108+
// Dismiss the notice.
109+
AdminNotice::dismiss_admin_notice();
110+
$this->assertTrue($notice->is_notice_dismissed());
111+
}
112+
113+
public function test_process_ajax_request_dismisses_notice(): void {
114+
$this->set_as_admin();
115+
$this->unset_dismissed_notice_meta_data();
116+
117+
$notice = AdminNotice::init();
118+
$this->assertFalse($notice->is_notice_dismissed());
119+
120+
$notice::dismiss_admin_notice();
121+
$this->assertTrue($notice->is_notice_dismissed());
122+
}
123+
124+
}

0 commit comments

Comments
 (0)