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

Add functionality to reset page order #129

Merged
merged 10 commits into from
Feb 28, 2023

Conversation

ruscoe
Copy link
Contributor

@ruscoe ruscoe commented Jan 29, 2023

Description of the Change

In the page admin section, the Simple Page Ordering plugin help section has been modified to include a "Reset page order" link. Clicking this link first displays a confirmation dialog then, if the user confirms, resets the "menu_order" field of all pages to 0, resetting any changes made with this plugin.

I think this is the most minimally invasive solution, adding little code to achieve the purpose. I chose to place the reset link in the help menu to minimize chances of accidental order resets. Plus, if someone needs to reset the order of their posts, they have probably made a mistake and will be looking for help.

Closes #24

How to test the Change

  • Install and enable the Simple Page Ordering plugin
  • Navigate to the page admin section /wp-admin/edit.php?post_type=page
  • Add some pages
  • Drag some pages up or down so the order changes
  • Refresh the page to ensure the order remains (AJAX calls have been successful)
  • At the top of the page, click "Help", then "Simple Page Ordering", then "Reset page order"
  • Click "OK"
  • If the page refreshes and the pages now appear in their original order, this change is functioning properly

Changelog Entry

Added - Feature to reset page order

Credits

Props @pattonwebz, @ruscoe, @Sidsector9, @dkotter

Checklist:

  • I agree to follow this project's Code of Conduct.
  • I have updated the documentation accordingly.
  • I have added tests to cover my change.
  • All new and existing tests pass.

@ruscoe ruscoe requested a review from a team as a code owner January 29, 2023 21:31
@ruscoe ruscoe requested review from Sidsector9 and removed request for a team January 29, 2023 21:31
Copy link
Member

@Sidsector9 Sidsector9 left a comment

Choose a reason for hiding this comment

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

Hi @ruscoe

Thank you so much for working on this and congratulations on your first PR at 10up!

I have reviewed your code and I have a concern.

Since the REST endpoint in open to public, anyone with access to it can reset the page ordering.

Watch this video below where I am able to reset the ordering through Postman:

spo-129.mp4

For this feature, I would suggest you to use wp_ajax_{$action} instead.

You can use the following:

OR

If you would like to go ahead with the current implementation, then you can pass a nonce and verify it in the permissions_callback.

@ruscoe
Copy link
Contributor Author

ruscoe commented Jan 30, 2023

@Sidsector9 Very good point! Thank you for reviewing and catching that. I'll take your suggestion and submit an updated PR shortly.

@ruscoe
Copy link
Contributor Author

ruscoe commented Jan 30, 2023

@Sidsector9 Changes made! It's now using the admin AJAX functionality rather than a REST endpoint. Is there something better I could do to avoid the code duplication between ajax_simple_page_ordering and the new ajax_reset_simple_page_ordering or is that as efficient as I can get it?

@ruscoe ruscoe requested a review from Sidsector9 January 30, 2023 06:15
@Sidsector9
Copy link
Member

@ruscoe Thanks for working on those changes, tested well for me 👍

No pressure at all, but would you be comfortable at writing end-to-end tests for the same?
We use Cypress, you can see the tests for existing features here - https://github.com/10up/simple-page-ordering/tree/develop/tests/cypress/integration

Else let us know if you'd like someone from our team to pick up writing the tests.

Answering your question:

Is there something better I could do to avoid the code duplication between ajax_simple_page_ordering and the new ajax_reset_simple_page_ordering or is that as efficient as I can get it?

Looking at the code, I think we can let it pass for now as the duplication is less. In the future when the number of AJAX handlers increase using the same set of checks, then we can look at refactoring it under a common handler.

@ruscoe
Copy link
Contributor Author

ruscoe commented Jan 30, 2023

@Sidsector9 I'd like to write the tests. It'll be a great opportunity for me to learn how they work. Will update soon!

@ruscoe
Copy link
Contributor Author

ruscoe commented Jan 30, 2023

@Sidsector9 Thank you for all your assistance with this! I've just committed my first attempt at a Cypress test. I took a lot of guidance from the other tests.

The tests pass for me and look correct when I run them through the command line and UI / browser aspect of Cypress. Let me know what you think!

const newSecondText = cy.get(`${second} .row-title`);

cy.get(`${first} .row-title`).then($el => {
newFirstText.should('have.text', $el.text());
Copy link
Member

Choose a reason for hiding this comment

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

I think this will always be true, as newFirstText and cy.get('${first} .row-title') refer to the same element.

Copy link
Member

Choose a reason for hiding this comment

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

We need to save the original post names so that we can verify their positions after swapping and reverting.
This is something I was trying today, I used cy.as() to store the text values and refer them later on for verification:

describe( 'Test Reset Page Order Change', () => {
	it( 'Can reset pages order', () => {
		cy.login();
		cy.visit('/wp-admin/edit.php?post_type=page');

		const firstRow = '.wp-list-table tbody tr:nth-child(1)';
		const secondRow = '.wp-list-table tbody tr:nth-child(2)';

		// Alias titles as `firstRowText` and `secondRowText` for convenience.
		cy.get( firstRow ).find( '.row-title' ).invoke( 'text' ).as( 'firstRowText' );
		cy.get( secondRow ).find( '.row-title' ).invoke( 'text' ).as( 'secondRowText' );

		// Swap position of `Page 1` with `Page 2`.
		cy.get( firstRow ).drag( secondRow );

		// Verifies if 1st row has title `Page 2`.
		cy.get( firstRow ).find( '.row-title' ).invoke( 'text' ).then( function( text ) {
			expect( text ).to.eq( this.secondRowText );
		} );

		// Verifies if 2nd row has title `Page 1`.
		cy.get( secondRow ).find( '.row-title' ).invoke( 'text' ).then( function( text ) {
			expect( text ).to.eq( this.firstRowText );
		} );

		// Now reset the page order and verify original values are back.
		cy.get( '#contextual-help-link' ).click();
		cy.get( '#tab-link-simple_page_ordering_help_tab' ).click();
		cy.get( '#simple-page-ordering-reset' ).click();
		cy.on( 'window:confirm', () => true );

		// Perform a reload as Cypress won't after window:confirm.
		cy.reload();

		// Verifies if 1st row has title `Page 1`.
		cy.get( firstRow ).find( '.row-title' ).invoke( 'text' ).then( function( text ) {
			expect( text ).to.eq( this.firstRowText );
		} );

		// Verifies if 2nd row has title `Page 2`.
		cy.get( secondRow ).find( '.row-title' ).invoke( 'text' ).then( function( text ) {
			expect( text ).to.eq( this.secondRowText );
		} );
	} );
} );

cy.on(`window:confirm`, () => true);

// wait for the page to reload before checking for reset post order.
cy.wait(1000);
Copy link
Member

Choose a reason for hiding this comment

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

Cypress considers using cy.wait() as anti-pattern. We can replace this with cy.reload() instead because window.location.reload() will not actually reload the page in cypress.

@Sidsector9
Copy link
Member

@ruscoe Thanks so much for the tests! I have reviewed and requested some minor changes.
You can use npm run cypress:open for visual reference.

@ruscoe
Copy link
Contributor Author

ruscoe commented Jan 31, 2023

@Sidsector9 Thank you! I replaced my test code with your modified code and it works perfectly. Do you mind if I just commit your modified test in place of mine?

}

// reset the order of all posts of given post type
$wpdb->update( 'wp_posts', array( 'menu_order' => 0 ), array( 'post_type' => $post_type ) );
Copy link
Member

Choose a reason for hiding this comment

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

@dkotter This PR works well. The only area that I am concerned about is the performance impact due to this line.
Would this be an issue for an extremely large database? If so, should we look into performing the updates in batches?

Copy link
Collaborator

Choose a reason for hiding this comment

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

Looking at the code wpdb::update runs, seems like the final query will be something like:

UPDATE 'wp_posts' SET 'menu_order' = 0 WHERE 'post_type' = 'page'

I think that should scale fairly well and we won't need to worry about doing any sort of batching here. That said, @ruscoe if you'd be able to do some quick testing on various sizes to see the performance impact, that would help us be more confident about merging this in. Something like profiling this code when run on a site that has 10 pages, 100 pages and 1000 pages, that would be helpful.

In addition, I think best practice here is to utilize the format properties of the update method. Something like

Suggested change
$wpdb->update( 'wp_posts', array( 'menu_order' => 0 ), array( 'post_type' => $post_type ) );
$wpdb->update( 'wp_posts', array( 'menu_order' => 0 ), array( 'post_type' => $post_type ), array( '%d' ), array( '%s' ) );

Copy link
Contributor Author

@ruscoe ruscoe Feb 16, 2023

Choose a reason for hiding this comment

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

@dkotter No problem at all! I profiled by measuring execution time.

Just to show my process, first I used a quick shell script to create pages:

#!/bin/bash

for i in {1..10}
do
  wp post create --post_type=page --post_title="Page $i"
done

Then used the following to test:

$start_time = microtime( true );
$wpdb->update( 'wp_posts', array( 'menu_order' => 0 ), array( 'post_type' => 'page' ), array( '%d' ), array( '%s' ) );
$end_time = microtime( true );
$total_time = ( $end_time - $start_time );
echo $total_time;

Taking the average of five test runs each:

10 posts: 0.0041 microseconds

100 posts: 0.0042 microseconds

1000 posts: 0.0116 microseconds

5000 posts: 0.0299 microseconds

How does that look?

Copy link
Collaborator

Choose a reason for hiding this comment

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

Thanks @ruscoe! Definitely see an increase in time as the number of posts increase but the overall amount of time is so low that this doesn't seem like it will cause any problems. This looks good to go on my end

@Sidsector9 Sidsector9 merged commit 025072b into 10up:develop Feb 28, 2023
@jeffpaul jeffpaul added this to the 2.5.0 milestone Feb 28, 2023
@dkotter dkotter mentioned this pull request Jun 21, 2023
4 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Reset ordering
4 participants