Skip to content

Conversation

@justlevine
Copy link
Collaborator

@justlevine justlevine commented Dec 7, 2024

What does this implement/fix? Explain your changes.

This PR implements Semantic Version checking when updating WPGraphQL to a newer version.

How it works:

  • A new Admin\Updates\UpdateChecker class has been introduced to check whether an update is likely "non-breaking" (per Semantic Versioning). This class is used both to prevent auto-updates and to display update information before a manual update is applied.

  • The UpdateChecker class also checks if there are any explicitly incompatible or possibly untested WPGraphQL extensions that should be checked/updated before (auto)updating.

    • New Plugin headers for extensions:

      • Requires WPGraphQL: {x.y.z} sets a minimum plugin version.
      • WPGraphQL tested up to: {x.y.z} sets the last known compatible version.

      Plugins that use the Requires Plugins: wp-graphql or use WPGraphQL in the plugin name or description are detected with an Unknown version compatibility.

  • The PluginScreenLoader and UpdateScreenLoader classes inject the notices/modals into the admin screens to warn and confirm about untested extensions that are potentially breaking.

    • A new packages/updates package contains the .scss to style our notices/modals. Additionally, webpack-remove-empty-scripts has been added to our NPM deps to prevent the generation of an empty .js file.

New filters:

  • wpgraphql_enable_major_autoupdates - allows for major versions to be auto-updateable. Defaults false.
  • wpgraphql_untested_release_type - determines which SemVer release type should be considered potentially "breaking". Defaults major.
  • graphql_get_dependents and graphql_get_possible_dependents filter the list of (possible) WPGraphQL extensions.

Todo

  • Implement APIs
  • Implement Autoupdate Prevention
  • Prevent activation ( + notice ) of plugins that don't meet the Requires WPGraphQL version
  • Inject Upgrade notice on Plugin Screen
  • Implement warning modals on Manual Update.
  • Attribute prior art.
  • (Bonus) Show Upgrade Notices from readme.txt
  • (Bonus) Explore reuse in semver-compatible extensions.

Does this close any currently open issues?

Closes #2543

Testing Instructions

  1. Install a WPGraphQL Extension. (you can copy the mock extensions from test/data/plugins into wp-content/plugins )

  2. Mock an available update:

    add_action( 'init', static function() {
     $update_plugins = get_site_transient( 'update_plugins' );
    
     if ( ! $update_plugins ) {
     	return;
     }
    
     // Prime.
     if ( empty( $update_plugins->response ) ) {
     	$update_plugins->response = [];
     }
    
     if ( isset( $update_plugins->no_update['wp-graphql/wp-graphql.php'] ) ) {
     	$update_plugins->response['wp-graphql/wp-graphql.php'] = $update_plugins->no_update['wp-graphql/wp-graphql.php'];
    
     	unset( $update_plugins->no_update['wp-graphql/wp-graphql.php'] );
     }
    
     // Stub new version.
     if ( isset( $update_plugins->response['wp-graphql/wp-graphql.php'] ) ) {
     	// Ensure array.
     	$update_plugins->response['wp-graphql/wp-graphql.php']->new_version = '4.0.0';
     }
    
     set_site_transient( 'update_plugins', $update_plugins );
    } );
  3. Updates Screen:
    a. Visit /wp-admin/update-core.php and refresh the screen a few times* until the update is available.
    b. Select WPGraphQL from the list of plugins to view the modal.
    c. Click Cancel to see WPGraphQL get unchecked.

  4. Plugins Screen
    a. Visit wp-admin/plugins.php and refresh the screen a few times* until the update is available.
    b. Click the inline Update now link to view the modal.

Warning

DO NOT try to update WPGraphQL to the fictitious version. (e.g. clicking the Update now button in the modal).
It will delete the existing wp-graphql plugin, including the sym-linked reference to the repo.

Any relevant logs, error output, GraphiQL screenshots, etc?

Plugin Screen notice:

image

Plugin Screen modal:

image

Update Screen modal:

image

Deactivated Plugin notice

image

Any other comments?

Where has this been tested?

Operating System: Ubuntu 24.04 ( wsl2 + ddev + php8.2.26)

WordPress Version: 6.7.1

*
* @return array<string,array<string,mixed>> The array of plugins that maybe use WPGraphQL as a dependency, keyed by plugin path.
*/
protected function get_possible_dependents(): array {

Choose a reason for hiding this comment

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

Function get_possible_dependents has a Cognitive Complexity of 8 (exceeds 5 allowed). Consider refactoring.

*
* @return array<string,array<string,mixed>> The array of plugins that use WPGraphQL as a dependency, keyed by plugin path.
*/
protected function get_dependents(): array {

Choose a reason for hiding this comment

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

Function get_dependents has a Cognitive Complexity of 6 (exceeds 5 allowed). Consider refactoring.

@coveralls
Copy link

coveralls commented Dec 7, 2024

Coverage Status

coverage: 84.348% (+0.4%) from 83.99%
when pulling 6ae6870 on justlevine:feat/autoupdater
into 644ebb5 on wp-graphql:develop.

return false;
}

return $default_value;

Choose a reason for hiding this comment

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

Avoid too many return statements within this method.

@justlevine justlevine added status: in progress Currently being worked on type: feature New functionality being added labels Dec 8, 2024
@jasonbahl
Copy link
Collaborator

@justlevine very cool!

Will take a look 👀

* @return array<string,array<string,mixed>> The array of untested plugin data.
* @throws \InvalidArgumentException If the WPGraphQL version is invalid.
*/
public function get_untested_plugins( string $release_type ): array {

Choose a reason for hiding this comment

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

Function get_untested_plugins has a Cognitive Complexity of 18 (exceeds 5 allowed). Consider refactoring.

*
* @return array<string,array<string,mixed>> The array of incompatible plugins.
*/
public function get_incompatible_plugins( string $version = WPGRAPHQL_VERSION, bool $active_only = false ): array {

Choose a reason for hiding this comment

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

Function get_incompatible_plugins has a Cognitive Complexity of 8 (exceeds 5 allowed). Consider refactoring.

* @param string $plugin_path The plugin path used as the key in the plugins array.
* @param string $version The current version to check against.
*/
private function is_incompatible_dependent( string $plugin_path, string $version = WPGRAPHQL_VERSION ): bool {

Choose a reason for hiding this comment

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

Function is_incompatible_dependent has a Cognitive Complexity of 6 (exceeds 5 allowed). Consider refactoring.

@justlevine
Copy link
Collaborator Author

PR is working and ready for feedback.

Of the remaining todos in the PR description:

  • (Bonus) Explore reuse in semver-compatible extensions.

I'll be testing against wp-graphql-headless-login and wp-graphql-content-blocks to see how easy it is for them to reuse the classes for their own SemVer auto-updates. This might result in some tweaks to the hooks and signature visibility, but the logic should remain intact.

  • (Bonus) Show Upgrade Notices from readme.txt

@jasonbahl this is your decision as to whether we do this in this PR or even at all.

@justlevine
Copy link
Collaborator Author

(Bonus) Explore reuse in semver-compatible extensions.

Did a basic review against wp-graphql-headless-login:

  • Using the new SemVer class to disable major extension autoupdates took ~15 LOC ✔️
  • There is no good way to reuse the *ScreenLoader classes and the underlying UpdateChecker class. That said, there's no real reason for reuse vs extension-native code, since we're currently only displaying issues regarding possibly incompatible versions, and not a general Upgrade Notice or similar. Further, we can make the necessary extensibility changes iteratively, so I see no reason to hold up review/merge for this.

Marking ready for review.


Disable autoupdates example:

add_filter(
	'auto_update_plugin', 
	static function( $should_update, $plugin ) {
		// Bail early if the plugin is not the one we're looking for.
		if ( ! isset( $plugin->plugin ) || ! isset( $plugin->new_version) ||'wp-graphql-headless-login/wp-graphql-headless-login.php' !== $plugin->plugin ) {
			return $should_update;
		}

		// If autoupdates are already disabled we don't need to check further.
		if ( false === $should_update ) {
			return $should_update;
		}

		$new_version = sanitize_text_field( $plugin->new_version );
		if ( '' === $new_version ) {
			return $should_update;
		}

		// Check if it's a major release.
		$release_type = \WPGraphQL\Admin\Updates\SemVer::get_release_type( WPGRAPHQL_LOGIN_VERSION, $new_version );
		if ( 'major' === $release_type ) {
			return false;
		}

		return $should_update;
	},
	10,
	2
);

@justlevine justlevine marked this pull request as ready for review December 20, 2024 23:57
Copy link
Collaborator

@jasonbahl jasonbahl left a comment

Choose a reason for hiding this comment

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

I'm still looking through and testing things but wanted to leave a few comments.

Comment on lines +135 to +159
/**
* Disables plugins that don't meet the minimum `Requires WPGraphQL` version.
*/
public function disable_incompatible_plugins(): void {
// Initialize the Update Checker.
$update_checker = new UpdateChecker( (object) get_plugins()['wp-graphql/wp-graphql.php'] );

// Get the incompatible plugins.
$incompatible_plugins = $update_checker->get_incompatible_plugins( WPGRAPHQL_VERSION, true );

// Deactivate the incompatible plugins.
$notice_data = [];
foreach ( $incompatible_plugins as $file => $plugin ) {
$notice_data[] = [
'name' => $plugin['Name'],
'version' => $plugin[ UpdateChecker::VERSION_HEADER ],
];
deactivate_plugins( $file );
}

// Display a notice to the user.
if ( ! empty( $notice_data ) ) {
set_transient( 'wpgraphql_incompatible_plugins', $notice_data );
}
}
Copy link
Collaborator

Choose a reason for hiding this comment

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

Upon quick scan (haven't tested or thoroughly read all the code yet), this stands out as something a bit concerning.

Mostly leaving a note for myself here, but I need to fully understand what situations will cause this. My initial reaction is that I don't want WPGraphQL to be responsible for de-activating other plugins as it could cause a lot of confusion / distrust.

That said, if this is VERY CLEARLY documented and communicated as a feature and is limited very specifically to plugins that require WPGraphQL (which I assume is already the case - need to look through and test more), then we're probably ok with it.

Copy link
Collaborator Author

@justlevine justlevine Jan 6, 2025

Choose a reason for hiding this comment

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

In addition to the point about clear documentation, remember that the context here is that a plugin sets Requires WPGraphQL to a higher version than what's currently installed. I.e. it's the plugin author who is opting into this behavior.

@jasonbahl
Copy link
Collaborator

Plugins that use the Requires Plugins: wp-graphql or use WPGraphQL in the plugin name or description are detected with an Unknown version compatibility.

Correct me if I'm mistaken, but I just want to clarify that the recommendation for extension authors now would be to use all 3 headers. (i.e. we're not replacing "Requires Plugins: wp-graphql", but enhancing things with additional headers)

i.e. a current WPGraphQL Extension plugin that had the following header:

 * Requires Plugins: wp-graphql // This header is recognized by WordPress core, and used on some existing extensions

would adjust like so:

 * Requires Plugins: wp-graphql
 * Requires WPGraphQL: 1.30 // Adjust as needed
 * WPGraphQL tested up to: 1.30 // Adjust version as testing is confirmed

…WPGraphQL plugin update available. This provides a bit more clarity and more straightforward actions to take.
- make messaging in UpdateChecker.php consistent with PluginsScreenLoader
…t_compatibility_warning_message()` function so we can manage the language for the messaging in one place.
@jasonbahl
Copy link
Collaborator

I ran this through various scenarios locally with WPGraphQL for ACF and overall I'm pretty happy with the experience. Very thorough and well thought out. I have some small adjustments I want to make to some of the language re: compatibility and recommended actions to take before updating, but other than that this is super solid and well tested. 🙌🏻

This will add a lot of confidence for users and maintainers as we navigate the future of releasing breaking changes.

@justlevine
Copy link
Collaborator Author

justlevine commented Jan 7, 2025

Plugins that use the Requires Plugins: wp-graphql or use WPGraphQL in the plugin name or description are detected with an Unknown version compatibility.

Correct me if I'm mistaken, but I just want to clarify that the recommendation for extension authors now would be to use all 3 headers. (i.e. we're not replacing "Requires Plugins: wp-graphql", but enhancing things with additional headers)

Close, but not exactly. All 3 are optional and can be used independently, depending on what the extension author is trying to accomplish.

  • Requires Plugins: wp-graphql - prevents an extension from being activated if WPGraphQL is not active, and prevents WPGraphQL from being deactivated if the extension is active.
    This would e.g. make sense for WPGraphQL IDE (that can't run at all without WPGraphQL) but you wouldnt want it for let's say CPT UI (or arguably for WPGraphQL for ACF) since you want to still be able to configure your types even if WPGraphQL is (temporarily) disabled.

  • Requires WPGraphQL: 1.30 this sets a minimum version constraint, which must be matched for the extension to be activated.
    If WPGraphQL isnt active, the extension can still be activated (e.g. CPT UI above).

  • WPGraphQL tested up to 1.30: This offers an extra level of reliability when upgrading, by allowing autoupdates for major WPGraphQL releases (e.g. requires 1.30, tested up to 3.5 ).
    If an extension is missing this header/it falls outside the incoming WPGraphQL version, autoupdates will be prevented for major releases, and the confirmation modal is shown before manually updating.

@jasonbahl
Copy link
Collaborator

@justlevine I opened a PR to your branch here with some updates to the messaging: justlevine#3

@jasonbahl
Copy link
Collaborator

Requires Plugins: wp-graphql - prevents an extension from being activated if WPGraphQL is not active, and prevents WPGraphQL from being deactivated if the extension is active.
This would e.g. make sense for WPGraphQL IDE (that can't run at all without WPGraphQL) but you wouldnt want it for let's say CPT UI (or arguably for WPGraphQL for ACF) since you want to still be able to configure your types even if WPGraphQL is (temporarily) disabled.

Ok, I get where you're going with this. WPGraphQL for ACF specifically does use some functions from WPGraphQL in the admin UI screens for configuring ACF Field Groups and Fields to show in the schema, so WPGraphQL really is required. There's likely some defensive programming to ensure the functions exist before calling them (I should probably double check 👀, but WPGraphQL really is required for it). . .but I get the spirit of the idea. Some plugins might be compatible with WPGraphQL but might not TRULY require it.

Requires WPGraphQL: 1.30 this sets a minimum version constraint, which must be matched for the extension to be activated.
If WPGraphQL isn't active, the extension can still be activated (e.g. CPT UI above).

👌🏻

WPGraphQL tested up to 1.30: This offers an extra level of reliability when upgrading, by allowing autoupdates for major WPGraphQL releases (e.g. requires 1.30, tested up to 3.5 ).
If an extension is missing this header/it falls outside the incoming WPGraphQL version, autoupdates will be prevented for major releases, and the confirmation modal is shown before manually updating.

Great summary! This will help document this feature for release. 🙌🏻

chore: update messaging around compatibility and recommended actions before updating
jasonbahl
jasonbahl previously approved these changes Jan 9, 2025
* @param array<string,array<string,mixed>> $untested_plugins The untested plugins.
* @return string The formatted HTML message.
*/
public function get_compatibility_warning_message( array $untested_plugins ): string {

Choose a reason for hiding this comment

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

Method get_compatibility_warning_message has 59 lines of code (exceeds 40 allowed). Consider refactoring.

@@ -0,0 +1,628 @@
<?php

Choose a reason for hiding this comment

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

File UpdateChecker.php has 314 lines of code (exceeds 250 allowed). Consider refactoring.

@qlty-cloud-legacy
Copy link

Code Climate has analyzed commit 6ae6870 and detected 12 issues on this pull request.

Here's the issue category breakdown:

Category Count
Complexity 12

View more on Code Climate.

@jasonbahl jasonbahl merged commit 59af4ec into wp-graphql:develop Jan 10, 2025
35 of 37 checks passed
@jasonbahl jasonbahl mentioned this pull request Jan 16, 2025
@justlevine justlevine deleted the feat/autoupdater branch February 10, 2025 22:37
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

status: in progress Currently being worked on type: feature New functionality being added

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Idea: SemVer auto-update enforcement

3 participants