diff --git a/plugins/wpgraphql-logging/phpcs.xml b/plugins/wpgraphql-logging/phpcs.xml
index 82c557f3..77b82827 100644
--- a/plugins/wpgraphql-logging/phpcs.xml
+++ b/plugins/wpgraphql-logging/phpcs.xml
@@ -54,7 +54,14 @@
-
+
+
+
+
+
+
+
+
diff --git a/plugins/wpgraphql-logging/src/Admin/AdminNotice.php b/plugins/wpgraphql-logging/src/Admin/AdminNotice.php
new file mode 100644
index 00000000..ecda3e74
--- /dev/null
+++ b/plugins/wpgraphql-logging/src/Admin/AdminNotice.php
@@ -0,0 +1,105 @@
+setup();
+ }
+
+ do_action( 'wpgraphql_logging_admin_notice_init', self::$instance );
+
+ return self::$instance;
+ }
+
+ /**
+ * Setup the admin notice.
+ */
+ public function setup(): void {
+ $key = self::ADMIN_NOTICE_KEY;
+ $is_dismissed = $this->is_notice_dismissed();
+
+ // Exit if the notice has been dismissed.
+ if ( $is_dismissed ) {
+ return;
+ }
+
+ add_action( 'admin_notices', [ $this, 'register_admin_notice' ], 10, 0 );
+ add_action( 'wp_ajax_' . $key, [ $this, 'process_ajax_request' ], 10, 0 );
+ }
+
+ /**
+ * Register admin notice to inform users about WPGraphQL Logging.
+ */
+ public function register_admin_notice(): void {
+ $template = __DIR__ . '/View/Templates/WPGraphQLLoggerNotice.php';
+
+ if ( ! file_exists( $template ) ) {
+ return;
+ }
+
+ require $template; // phpcs:ignore WordPressVIPMinimum.Files.IncludingFile.UsingVariable
+ }
+
+ /**
+ * Process the AJAX request.
+ */
+ public function process_ajax_request(): void {
+ $key = self::ADMIN_NOTICE_KEY;
+ if ( ! isset( $_POST['action'] ) || esc_attr( $key ) !== $_POST['action'] ) {
+ return;
+ }
+
+ check_ajax_referer( $key );
+
+ self::dismiss_admin_notice();
+ }
+
+ /**
+ * Check if the admin notice is dismissed.
+ */
+ public function is_notice_dismissed(): bool {
+ $key = self::ADMIN_NOTICE_KEY;
+ return (bool) get_user_meta( get_current_user_id(), $key, true );
+ }
+
+ /**
+ * Dismiss the admin notice.
+ */
+ public static function dismiss_admin_notice(): void {
+ $key = self::ADMIN_NOTICE_KEY;
+ update_user_meta( get_current_user_id(), $key, 1 );
+ }
+}
diff --git a/plugins/wpgraphql-logging/src/Admin/SettingsPage.php b/plugins/wpgraphql-logging/src/Admin/SettingsPage.php
index 558c1f7b..2b8ecd6c 100644
--- a/plugins/wpgraphql-logging/src/Admin/SettingsPage.php
+++ b/plugins/wpgraphql-logging/src/Admin/SettingsPage.php
@@ -43,7 +43,7 @@ class SettingsPage {
/**
* Initializes the settings page.
*/
- public static function init(): ?SettingsPage {
+ public static function init(): ?self {
if ( ! current_user_can( 'manage_options' ) ) {
return null;
}
diff --git a/plugins/wpgraphql-logging/src/Admin/View/Templates/WPGraphQLLoggerNotice.php b/plugins/wpgraphql-logging/src/Admin/View/Templates/WPGraphQLLoggerNotice.php
new file mode 100644
index 00000000..834b3957
--- /dev/null
+++ b/plugins/wpgraphql-logging/src/Admin/View/Templates/WPGraphQLLoggerNotice.php
@@ -0,0 +1,39 @@
+
+
+
+
+
+
+prefix . 'wpgraphql_logging';
$wpdb->query("TRUNCATE TABLE {$table_name}");
+
+
+ // Remove admin notice dismissed meta data
+ delete_user_meta(get_current_user_id(), 'wpgraphql-logging-admin-notice');
}
-});
\ No newline at end of file
+});
diff --git a/plugins/wpgraphql-logging/tests/e2e/specs/admin-notice.spec.js b/plugins/wpgraphql-logging/tests/e2e/specs/admin-notice.spec.js
new file mode 100644
index 00000000..6235c7b8
--- /dev/null
+++ b/plugins/wpgraphql-logging/tests/e2e/specs/admin-notice.spec.js
@@ -0,0 +1,33 @@
+import { expect, test } from "@wordpress/e2e-test-utils-playwright";
+import {
+ resetPluginSettings,
+ goToLoggingSettingsPage,
+} from "../utils";
+
+test.describe("WPGraphQL Logging Admin Notice", () => {
+ test.beforeEach(async ({ admin, page }) => {
+ await resetPluginSettings(admin); // Reset user meta data
+ await goToLoggingSettingsPage(admin);
+ await expect(page.locator("h1")).toHaveText("WPGraphQL Logging Settings");
+ });
+
+ test("admin notice is displayed", async ({
+ page,
+ admin,
+ }) => {
+ await goToLoggingSettingsPage(admin);
+ await expect(
+ page.locator("#wpgraphql-logging-admin-notice")
+ ).toBeVisible();
+
+ await page.locator("#wpgraphql-logging-admin-notice.notice .notice-dismiss").click();
+ await expect(
+ page.locator("#wpgraphql-logging-admin-notice"),
+ ).not.toBeVisible();
+
+ await page.reload();
+ await expect(
+ page.locator("#wpgraphql-logging-admin-notice"),
+ ).not.toBeVisible();
+ });
+});
diff --git a/plugins/wpgraphql-logging/tests/wpunit/Admin/AdminNoticeTest.php b/plugins/wpgraphql-logging/tests/wpunit/Admin/AdminNoticeTest.php
new file mode 100644
index 00000000..9cfd6f42
--- /dev/null
+++ b/plugins/wpgraphql-logging/tests/wpunit/Admin/AdminNoticeTest.php
@@ -0,0 +1,124 @@
+factory->user->create(['role' => 'administrator']);
+ wp_set_current_user($admin_user);
+ set_current_screen('dashboard');
+ }
+
+ public function set_dismissed_notice_meta_data(): void {
+ update_user_meta( get_current_user_id(), AdminNotice::ADMIN_NOTICE_KEY, 1 );
+ }
+
+ public function unset_dismissed_notice_meta_data(): void {
+ delete_user_meta( get_current_user_id(), AdminNotice::ADMIN_NOTICE_KEY );
+ }
+
+ public function test_admin_notice_instance() {
+ $this->set_as_admin();
+
+ $reflection = new ReflectionClass( AdminNotice::class );
+ $instanceProperty = $reflection->getProperty( 'instance' );
+ $instanceProperty->setAccessible( true );
+ $instanceProperty->setValue( null );
+
+ $this->assertNull( $instanceProperty->getValue() );
+ $instance = AdminNotice::init();
+
+ $this->assertInstanceOf( AdminNotice::class, $instanceProperty->getValue() );
+ $this->assertSame( $instance, $instanceProperty->getValue(), 'AdminNotice::init() should set the static instance property' );
+ }
+
+ public function test_initialization_sets_instance_with_no_hooks_registered_as_not_admin(): void {
+ wp_set_current_user(0);
+ $instance = AdminNotice::init();
+ $this->assertFalse(has_action('admin_notices', [$instance, 'register_admin_notice']));
+ $this->assertFalse(has_action('wp_ajax_' . AdminNotice::ADMIN_NOTICE_KEY, [$instance, 'process_ajax_request']));
+ }
+
+ public function test_initialization_sets_instance_with_no_hooks_registered_as_dismissed(): void {
+ $this->set_as_admin();
+ $this->set_dismissed_notice_meta_data();
+
+ $instance = AdminNotice::init();
+ $this->assertFalse(has_action('admin_notices', [$instance, 'register_admin_notice']));
+ $this->assertFalse(has_action('wp_ajax_' . AdminNotice::ADMIN_NOTICE_KEY, [$instance, 'process_ajax_request']));
+ }
+
+ public function test_check_notice_is_displayed(): void {
+ $this->set_as_admin();
+
+ $reflection = new ReflectionClass( AdminNotice::class );
+ $instanceProperty = $reflection->getProperty( 'instance' );
+ $instanceProperty->setAccessible( true );
+ $instanceProperty->setValue( null );
+
+ $notice = AdminNotice::init(); // Notice should be now registered
+
+ $hook = has_action('admin_notices', [$notice, 'register_admin_notice']);
+ $this->assertNotFalse($hook);
+ $this->assertEquals(10, $hook);
+
+ $ajax_hook = has_action('wp_ajax_' . AdminNotice::ADMIN_NOTICE_KEY, [$notice, 'process_ajax_request']);
+ $this->assertNotFalse($ajax_hook);
+ $this->assertEquals(10, $ajax_hook);
+ }
+
+ public function test_register_admin_notice_outputs_template(): void {
+ $this->set_as_admin();
+
+ $notice = AdminNotice::init();
+
+ ob_start();
+ $notice->register_admin_notice();
+ $output = ob_get_clean();
+
+ $this->assertStringContainsString('wpgraphql-logging-admin-notice', $output);
+ $this->assertStringContainsString('notice-warning', $output);
+ $this->assertStringContainsString('is-dismissible', $output);
+ $this->assertStringContainsString('Heads up! While very useful for debugging', $output);
+ $this->assertStringContainsString('