diff --git a/assets/admin.scss b/assets/admin.scss index 7dbc4d2..7401367 100644 --- a/assets/admin.scss +++ b/assets/admin.scss @@ -2,7 +2,11 @@ .vrts_list_table_page { - .testing-status { + .vrts-testing-status { + display: flex; + flex-direction: column; + align-items: flex-start; + margin: 0; &--paused { color: $alert-red; @@ -76,3 +80,61 @@ margin-bottom: 0; } } + +.vrts-tooltip { + position: relative; + + &-icon { + color: $blue-50; + display: flex; + align-items: center; + justify-content: center; + + &::before { + font-size: 1rem; + } + + svg { + fill: currentcolor; + } + } + + &-content { + display: block; + position: absolute; + visibility: hidden; + padding: 0.5rem; + width: 240px; + z-index: 1000002; + top: -10px; + right: 20px; + } + + &-content-inner { + display: block; + background: #000; + border-radius: 2px; + color: #f0f0f0; + font-size: 12px; + line-height: 1.4; + padding: 0.725rem; + + a { + color: #fff; + } + } + + &:hover &-content { + visibility: visible; + } +} + +.vrts-testing-toogle { + display: flex; + align-items: center; + gap: 0.25rem; + + .vrts-tooltip { + margin-left: auto; + } +} diff --git a/assets/editor.scss b/assets/editor.scss index b39b1a8..2084518 100644 --- a/assets/editor.scss +++ b/assets/editor.scss @@ -13,42 +13,8 @@ } } - .testing-status { - display: flex; - justify-content: space-between; - gap: 1rem; - margin-bottom: 0; - - &--paused { - color: $alert-red; - } - - &--running { - color: $alert-green; - } - - &-wrapper { - margin: 1.5rem 0; - - > :first-child { - margin-top: 0; - } - - > :last-child { - margin-bottom: 0; - } - - .howto { - margin-top: 0.25rem; - font-size: 0.75rem; - color: $gray-700; - } - } - } - .figure { margin: 0; - aspect-ratio: 16/9; &-link { outline: none; @@ -60,6 +26,7 @@ } &-image { + aspect-ratio: 16/9; border: solid 1px #bfbfbf; width: 100%; height: 100%; @@ -77,6 +44,10 @@ &-title { margin-bottom: 0.5rem; + display: flex; + gap: 0.5rem; + justify-content: space-between; + align-items: center; } label { @@ -91,3 +62,50 @@ } } } + +.vrts-testing-status { + position: relative; + display: flex; + justify-content: space-between; + gap: 1rem; + margin-bottom: 0; + + &--paused { + color: $alert-red; + } + + &--waiting { + color: $alert-yellow; + } + + &--running { + color: $alert-green; + } + + &-wrapper { + display: flex; + flex-direction: column; + gap: 5px; + margin: 1.5rem 0; + + > :first-child { + margin-top: 0; + } + + > :last-child { + margin-bottom: 0; + } + + .dashicons { + display: none; + } + } + + &-info { + display: flex; + justify-content: space-between; + gap: 5px; + font-size: 0.75rem; + color: $gray-700; + } +} diff --git a/assets/scripts/onboarding.js b/assets/scripts/onboarding.js index efd8e37..23dc277 100644 --- a/assets/scripts/onboarding.js +++ b/assets/scripts/onboarding.js @@ -10,22 +10,29 @@ if ( window.vrts_admin_vars.onboarding ) { const onboarding = driver( { overlayColor: 'rgba(44, 51, 56, 0.35)', stageRadius: 0, - animate: false, - stagePadding: 10, + stagePadding: 0, popoverOffset: 20, allowClose: false, showProgress: ! isHighlight, + popoverClass: isHighlight + ? 'vrts-onboarding-nonblocking' + : 'vrts-onboarding', + disableActiveInteraction: false, progressText: __( '{{current}} of {{total}}', 'visual-regression-tests' ), - nextBtnText: __( 'Next', 'visual-regression-tests' ), prevBtnText: __( 'Previous', 'visual-regression-tests' ), + nextBtnText: __( 'Next', 'visual-regression-tests' ), doneBtnText: __( 'Got it!', 'visual-regression-tests' ), onPopoverRender: ( popover, { config, state } ) => { const steps = config.steps; const hasNextStep = steps[ state.activeIndex + 1 ]; + config.stagePadding = + window.vrts_admin_vars.onboarding.steps[ state.activeIndex ] + .padding || 0; + popover.previousButton.classList.add( 'button', 'button-secondary', @@ -53,14 +60,12 @@ if ( window.vrts_admin_vars.onboarding ) { const hasNextStep = steps[ state.activeIndex + 1 ]; if ( ! hasNextStep ) { - saveOnboarding(); onboarding.destroy(); } else { onboarding.moveNext(); } }, onCloseClick: () => { - saveOnboarding(); onboarding.destroy(); }, steps: window.vrts_admin_vars.onboarding.steps.map( ( step ) => { @@ -77,6 +82,7 @@ if ( window.vrts_admin_vars.onboarding ) { } ); onboarding.drive(); + saveOnboarding(); } async function saveOnboarding() { diff --git a/assets/scripts/relative-time-element.js b/assets/scripts/relative-time-element.js new file mode 100644 index 0000000..3720cf5 --- /dev/null +++ b/assets/scripts/relative-time-element.js @@ -0,0 +1,88 @@ +class RelativeTimeElement extends window.HTMLElement { + // constructor (element) { + // this.element = element + // // get date from utc timestamp + + // // this.time = new Date( element.getAttribute( 'time' ) ); + // // this.update(); + // } + + // observe attribute time + static get observedAttributes() { + return [ 'time' ]; + } + + // update time when attribute time changes + attributeChangedCallback( name, oldValue, newValue ) { + if ( name === 'time' ) { + this.time = new Date( newValue ); + this.update(); + } + } + + // connectedCallback() { + // this.time = new Date( this.getAttribute( 'time' ) ); + // this.update(); + // } + + update() { + this.innerText = `${ extractDate( this.time ) } at ${ extractTime( + this.time + ) }`; + } +} + +window.customElements.define( 'vrts-relative-time', RelativeTimeElement ); + +function extractDate( inputDate ) { + const { __ } = wp.i18n; + const today = new Date(); + // Set the time to midnight for an accurate date comparison + today.setHours( 0, 0, 0, 0 ); + + // Create a Date object for the input date + const comparisonDate = new Date( inputDate ); + comparisonDate.setHours( 0, 0, 0, 0 ); + + // Calculate the difference in days + const difference = ( comparisonDate - today ) / ( 1000 * 3600 * 24 ); + + // Determine if the date is today, tomorrow, or yesterday + if ( difference === 0 ) { + return __( 'Today', 'visual-regression-testing' ); + } else if ( difference === 1 ) { + return __( 'Tomorrow', 'visual-regression-testing' ); + } else if ( difference === -1 ) { + return __( 'Yesterday', 'visual-regression-testing' ); + } + return dateFormat( inputDate, 'Y/m/d' ); +} + +function extractTime( inputDate ) { + return dateFormat( inputDate, 'g:i a' ); +} + +// format date like in php date_format +function dateFormat( date, format ) { + const pad = ( number ) => ( number < 10 ? `0${ number }` : number ); + const d = pad( date.getDate() ); + const m = pad( date.getMonth() + 1 ); + const y = date.getFullYear(); + const Y = date.getFullYear(); + const H = date.getHours(); + const i = pad( date.getMinutes() ); + const s = date.getSeconds(); + const g = date.getHours() % 12 || 12; + const a = date.getHours() >= 12 ? 'pm' : 'am'; + + return format + .replace( 'd', d ) + .replace( 'm', m ) + .replace( 'y', y ) + .replace( 'Y', Y ) + .replace( 'H', H ) + .replace( 'i', i ) + .replace( 's', s ) + .replace( 'g', g ) + .replace( 'a', a ); +} diff --git a/assets/styles/onboarding.scss b/assets/styles/onboarding.scss index db6717a..803dc48 100644 --- a/assets/styles/onboarding.scss +++ b/assets/styles/onboarding.scss @@ -1,17 +1,17 @@ .driver { - &-active-element { + &-no-interaction { pointer-events: none; } &-overlay { - animation: onboading-animate 0.2s ease-in-out; + animation: vrts-onboarding-animate 0.2s ease-in-out; pointer-events: none; } &-popover { all: unset; - animation: onboading-animate 0.2s; + animation: vrts-onboarding-animate 0.2s; box-sizing: border-box; background-color: #fff; color: #1e1e1e; @@ -19,9 +19,9 @@ padding: 15px; border-radius: 2px; min-width: 250px; - max-width: 350px; - box-shadow: 4px 4px 4px 0 rgba(30, 30, 30, 0.12); - z-index: 1000000000; + max-width: 345px; + box-shadow: 2px 2px 8px 4px rgba(30, 30, 30, 0.12); + z-index: 100099; position: fixed; top: 0; right: 0; @@ -164,22 +164,22 @@ &-side-left.driver-popover-arrow-align-start, &-side-right.driver-popover-arrow-align-start { - top: 20px; + top: 8px; } &-side-top.driver-popover-arrow-align-start, &-side-bottom.driver-popover-arrow-align-start { - left: 20px; + left: 8px; } &-align-end.driver-popover-arrow-side-left, &-align-end.driver-popover-arrow-side-right { - bottom: 20px; + bottom: 8px; } &-side-top.driver-popover-arrow-align-end, &-side-bottom.driver-popover-arrow-align-end { - right: 20px; + right: 8px; } &-side-left.driver-popover-arrow-align-center, @@ -196,7 +196,11 @@ } } -@keyframes onboading-animate { +.vrts-onboarding-nonblocking ~ .driver-overlay { + display: none; +} + +@keyframes vrts-onboarding-animate { 0% { opacity: 0; diff --git a/components/admin-notification/script.js b/components/admin-notification/script.js index 6c64d54..48aae9a 100644 --- a/components/admin-notification/script.js +++ b/components/admin-notification/script.js @@ -1,4 +1,7 @@ /* global jQuery, ajaxurl */ + +import 'scripts/relative-time-element'; + jQuery( document ).ready( function ( $ ) { $( document ).on( 'click', '.vrts-notice .notice-dismiss', ( event ) => { if ( ajaxurl ) { @@ -20,97 +23,4 @@ jQuery( document ).ready( function ( $ ) { } ); } } ); - if ( ! window.customElements.get( 'vrts-relative-time' ) ) { - window.customElements.define( - 'vrts-relative-time', - RelativeTimeElement - ); - } } ); - -class RelativeTimeElement extends window.HTMLElement { - // constructor (element) { - // this.element = element - // // get date from utc timestamp - - // // this.time = new Date( element.getAttribute( 'time' ) ); - // // this.update(); - // } - - // observe attribute time - static get observedAttributes() { - return [ 'time' ]; - } - - // update time when attribute time changes - attributeChangedCallback( name, oldValue, newValue ) { - if ( name === 'time' ) { - this.time = new Date( newValue ); - this.update(); - } - } - - // connectedCallback() { - // this.time = new Date( this.getAttribute( 'time' ) ); - // this.update(); - // } - - update() { - this.innerText = `${ extractDate( this.time ) } at ${ extractTime( - this.time - ) }`; - } -} - -function extractDate( inputDate ) { - const { __ } = wp.i18n; - const today = new Date(); - // Set the time to midnight for an accurate date comparison - today.setHours( 0, 0, 0, 0 ); - - // Create a Date object for the input date - const comparisonDate = new Date( inputDate ); - comparisonDate.setHours( 0, 0, 0, 0 ); - - // Calculate the difference in days - const difference = ( comparisonDate - today ) / ( 1000 * 3600 * 24 ); - - // Determine if the date is today, tomorrow, or yesterday - if ( difference === 0 ) { - return __( 'Today', 'visual-regression-testing' ); - } else if ( difference === 1 ) { - return __( 'Tomorrow', 'visual-regression-testing' ); - } else if ( difference === -1 ) { - return __( 'Yesterday', 'visual-regression-testing' ); - } - return dateFormat( inputDate, 'Y/m/d' ); -} - -function extractTime( inputDate ) { - return dateFormat( inputDate, 'g:i a' ); -} - -// format date like in php date_format -function dateFormat( date, format ) { - const pad = ( number ) => ( number < 10 ? `0${ number }` : number ); - const d = pad( date.getDate() ); - const m = pad( date.getMonth() + 1 ); - const y = date.getFullYear(); - const Y = date.getFullYear(); - const H = date.getHours(); - const i = pad( date.getMinutes() ); - const s = date.getSeconds(); - const g = date.getHours() % 12 || 12; - const a = date.getHours() >= 12 ? 'pm' : 'am'; - - return format - .replace( 'd', d ) - .replace( 'm', m ) - .replace( 'y', y ) - .replace( 'Y', Y ) - .replace( 'H', H ) - .replace( 'i', i ) - .replace( 's', s ) - .replace( 'g', g ) - .replace( 'a', a ); -} diff --git a/components/alerts-page/_style.scss b/components/alerts-page/_style.scss index 9f96425..85aa247 100644 --- a/components/alerts-page/_style.scss +++ b/components/alerts-page/_style.scss @@ -12,7 +12,6 @@ $navigation-item-border-height: 3px; .alert-status { display: block; - margin-top: 4px; &--false-positive { color: $alert-red; diff --git a/components/alerts-page/views/alerts-page-edit.php b/components/alerts-page/views/alerts-page-edit.php index 6205247..a1046d9 100644 --- a/components/alerts-page/views/alerts-page-edit.php +++ b/components/alerts-page/views/alerts-page-edit.php @@ -83,9 +83,7 @@ @@ -107,7 +105,7 @@
- +
- +
diff --git a/components/metabox-classic-editor/_style.scss b/components/metabox-classic-editor/_style.scss index 085f1a2..63d9253 100644 --- a/components/metabox-classic-editor/_style.scss +++ b/components/metabox-classic-editor/_style.scss @@ -13,42 +13,8 @@ } } - .testing-status { - display: flex; - justify-content: space-between; - gap: 1rem; - margin-bottom: 0; - - &--paused { - color: $alert-red; - } - - &--running { - color: $alert-green; - } - - &-wrapper { - margin: 1.5rem 0; - - > :first-child { - margin-top: 0; - } - - > :last-child { - margin-bottom: 0; - } - - .howto { - margin-top: 0.25rem; - font-size: 0.75rem; - color: $gray-700; - } - } - } - .figure { margin: 0; - aspect-ratio: 16/9; &-link { outline: none; @@ -60,6 +26,7 @@ } &-image { + aspect-ratio: 16/9; border: solid 1px #bfbfbf; width: 100%; height: 100%; @@ -72,22 +39,60 @@ } } - .howto-run-tests { - margin-top: 0.5rem; - margin-top: 0.25rem; - font-size: 0.75rem; - color: $gray-700; + .vrts-testing-status { + display: flex; + justify-content: space-between; + gap: 1rem; + margin-bottom: 0; + + &--paused { + color: $alert-red; + } + + &--waiting { + color: $alert-yellow; + } + + &--running { + color: $alert-green; + } + + &-wrapper { + display: flex; + flex-direction: column; + gap: 5px; + margin: 1.5rem 0; + + > * { + margin: 0; + } + + .dashicons { + display: none; + } + } + + &-info { + display: flex; + justify-content: space-between; + gap: 5px; + font-size: 0.75rem; + color: $gray-700; + } } .settings { &-title { margin-bottom: 0.5rem; + display: flex; + gap: 0.5rem; + justify-content: space-between; + align-items: center; } textarea { width: 100%; - margin-top: 0.5rem; } } } diff --git a/components/metabox-classic-editor/index.php b/components/metabox-classic-editor/index.php index 19d3ca1..ba48b49 100644 --- a/components/metabox-classic-editor/index.php +++ b/components/metabox-classic-editor/index.php @@ -6,15 +6,28 @@ ?> -

+

value="1" /> - -
- + + + + + + ', + '' + ); + ?> + + +

-
-

- - - - - - +

+

+ +

-

- [ 'href' => [] ] ] ); ?> +

+

-

-
- - - <?php esc_html_e( 'Snapshot', 'visual-regression-tests' ); ?> - - - <?php esc_html_e( 'Snapshot', 'visual-regression-tests' ); ?> - -
- -

- -

- -

-
-
+
+

+ + + + +

+
+ +
+
-

-
diff --git a/editor/components/metabox/index.js b/editor/components/metabox/index.js index 2ae84cb..412ff88 100644 --- a/editor/components/metabox/index.js +++ b/editor/components/metabox/index.js @@ -1,12 +1,11 @@ // Native -import { ToggleControl } from '@wordpress/components'; +import { Flex, Icon, ToggleControl } from '@wordpress/components'; import { useState, useEffect } from '@wordpress/element'; import { select, subscribe } from '@wordpress/data'; -import { __ } from '@wordpress/i18n'; -import DOMPurify from 'dompurify'; +import { info as infoIcon } from '@wordpress/icons'; +import { __, sprintf } from '@wordpress/i18n'; // Custom -import Screenshot from 'editor/components/screenshot'; import { NotificationNewTestAdded, NotificationUnlockMoreTests, @@ -20,10 +19,8 @@ import apiFetch from '@wordpress/api-fetch'; const Metabox = () => { const upgradeUrl = window.vrts_editor_vars.upgrade_url; const pluginUrl = window.vrts_editor_vars.plugin_url; - const testingStatusInstructions = - window.vrts_editor_vars.testing_status_instructions; - const placeholderImageDataUrl = - window.vrts_editor_vars.placeholder_image_data_url; + const testStatus = window.vrts_editor_vars.test_status; + const screenshot = window.vrts_editor_vars.screenshot; const postId = select( 'core/editor' ).getCurrentPostId(); const [ postStatus, setPostStatus ] = useState( @@ -66,6 +63,7 @@ const Metabox = () => { method: 'DELETE', } ); setTest( response || {} ); + setNewTest( false ); if ( previousServiceTestId ) { setCredits( { ...credits, @@ -157,67 +155,96 @@ const Metabox = () => { return ; } - let testingStatusText = __( 'Running', 'visual-regression-tests' ); - if ( test.current_alert_id ) { - testingStatusText = __( 'Paused', 'visual-regression-tests' ); - } else if ( ! test.status ) { - testingStatusText = __( 'Disabled', 'visual-regression-tests' ); - } - return ( <> - + + + + + + + + ', + '' + ), + } } + > + + + { metaboxNotification } { test.id && ( <> -
-

+

+

- { __( 'Status', 'visual-regression-tests' ) } + { __( + 'Test Status', + 'visual-regression-tests' + ) } - { testingStatusText } + { testStatus.text }

+ /> +
+
+

+ + { __( 'Snapshot', 'visual-regression-tests' ) } + + +

+
) } - { test.id && !! test.status && ( - - ) } { test.id && ( <> diff --git a/editor/components/screenshot/index.js b/editor/components/screenshot/index.js deleted file mode 100644 index fc8e648..0000000 --- a/editor/components/screenshot/index.js +++ /dev/null @@ -1,86 +0,0 @@ -import { __ } from '@wordpress/i18n'; -import { useMemo, useState } from '@wordpress/element'; - -const Screenshot = ( { url = '', finishDate = '', placeholderUrl = '' } ) => { - const [ datetime, setDatetime ] = useState( '' ); - useMemo( () => { - if ( finishDate && finishDate.length ) { - const date = new Date( finishDate ); - setDatetime( - date.toLocaleDateString() + - ' at ' + - date.toLocaleTimeString( undefined, { timeStyle: 'short' } ) - ); - } - }, [ finishDate ] ); - return ( - <> -

- { __( 'Snapshot', 'visual-regression-tests' ) } -

-
- { url && ( - - { - - ) } - { ! url && ( - { - ) } - -
- { datetime && ( -

- { __( - 'Snapshot created on', - 'visual-regression-tests' - ) }{ ' ' } - { datetime } -

- ) } - { ! datetime && ( -

- { __( - 'Snapshot: in progress', - 'visual-regression-tests' - ) }{ ' ' } - { datetime } -

- ) } -

- { __( - 'Snapshot gets auto-generated upon publishing or updating the page.', - 'visual-regression-tests' - ) } -

-
-
- - ); -}; - -export default Screenshot; diff --git a/editor/components/settings/index.js b/editor/components/settings/index.js index b6ac6a3..bb87b6e 100644 --- a/editor/components/settings/index.js +++ b/editor/components/settings/index.js @@ -1,8 +1,8 @@ import { __, sprintf } from '@wordpress/i18n'; -import { TextareaControl } from '@wordpress/components'; +import { Icon, TextareaControl } from '@wordpress/components'; import { useState } from '@wordpress/element'; +import { info as infoIcon } from '@wordpress/icons'; import { dispatch } from '@wordpress/data'; -import DOMPurify from 'dompurify'; const Settings = ( { test = {} } ) => { const [ testState, setTestState ] = useState( { @@ -23,7 +23,31 @@ const Settings = ( { test = {} } ) => { <>

- { __( 'Settings', 'visual-regression-tests' ) } + { __( + 'Hide elements from VRTs', + 'visual-regression-tests' + ) } + + + + + + ', + '' + ), + } } + > + +

{ onChange={ ( value ) => { updateTest( value ); } } - label={ - <> - { __( - 'Exclude elements on this page: ', - 'visual-regression-tests' - ) } - ', - '' - ), - { ADD_ATTR: [ 'target' ] } - ), - } } - /> - - } />
diff --git a/includes/core/class-plugin.php b/includes/core/class-plugin.php index e5a61d7..299a1ac 100644 --- a/includes/core/class-plugin.php +++ b/includes/core/class-plugin.php @@ -214,6 +214,9 @@ public function component( $name, $data = [] ) { * @return string Desired plugin information. */ public function get_plugin_info( $info ) { + if ( ! function_exists( 'get_plugin_data' ) ) { + require_once ABSPATH . 'wp-admin/includes/plugin.php'; + } $plugin = get_plugin_data( $this->plugin_file ); $infos = [ diff --git a/includes/features/class-alerts-page.php b/includes/features/class-alerts-page.php index 77da92b..ee96cf9 100644 --- a/includes/features/class-alerts-page.php +++ b/includes/features/class-alerts-page.php @@ -142,7 +142,7 @@ public function render_page() { 'next_alert_id' => Alert::get_pagination_next_alert_id( $alert_id, 'edit' === $action ? [ 0 ] : [ 1, 2 ] ), 'prev_next_alert_id' => Alert::get_pagination_prev_alert_id( $alert_id, 'edit' === $action ? [ 0 ] : [ 1, 2 ] ), 'current' => Alert::get_pagination_current_position( $alert_id, 'edit' === $action ? [ 0 ] : [ 1, 2 ] ), - 'total' => Alert::get_total_items( 'edit' === $action ? null : 'resolved' ), + 'total' => Alert::get_total_items( 'edit' === $action ? null : 'archived' ), 'prev_link' => $base_link . '&action=' . $action . '&alert_id=' . Alert::get_pagination_prev_alert_id( $alert_id, 'edit' === $action ? [ 0 ] : [ 1, 2 ] ), 'next_link' => $base_link . '&action=' . $action . '&alert_id=' . Alert::get_pagination_next_alert_id( $alert_id, 'edit' === $action ? [ 0 ] : [ 1, 2 ] ), ], @@ -203,7 +203,7 @@ public function submit_edit_alert() { $insert_alert = static::unmark_as_false_positive( $alert_id ); } else { $is_false_positive = isset( $_POST['submit_alert_false_positive'] ); - $insert_alert = static::resolve_alert( $alert_id, $is_false_positive ); + $insert_alert = static::archive_alert( $alert_id, $is_false_positive ); } }//end if @@ -287,7 +287,7 @@ public function submit_edit_alert_settings() { } /** - * Handle column actions (restore, resolve delete). + * Handle column actions (restore, archive, delete). */ public function process_column_actions() { if ( ! isset( $_GET['action'] ) && ! isset( $_GET['alert_id'] ) ) { @@ -330,21 +330,21 @@ public function process_column_actions() { if ( $alert_id && 'restore' === $action ) { $set_alert = $this->restore_alert( $alert_id ); - // Keep reloading resolved filtered page after restored alert, because this action can only be triggered from this page. - $page_url = "{$page_url}&status=resolved"; + // Keep reloading archived filtered page after restored alert, because this action can only be triggered from this page. + $page_url = "{$page_url}&status=archived"; } - // Resolve Alert. - if ( $alert_id && 'resolve' === $action ) { - $set_alert = $this->resolve_alert( $alert_id ); + // Archive Alert. + if ( $alert_id && 'archive' === $action ) { + $set_alert = $this->archive_alert( $alert_id ); } // Delete Alert. if ( $alert_id && 'delete' === $action ) { $set_alert = $this->delete_alert( $alert_id ); - // Keep reloading resolved filtered page after deleted alert, because this action can only be triggered from this page. - $page_url = "{$page_url}&status=resolved"; + // Keep reloading archived filtered page after deleted alert, because this action can only be triggered from this page. + $page_url = "{$page_url}&status=archived"; } if ( is_wp_error( $set_alert ) ) { @@ -358,19 +358,21 @@ public function process_column_actions() { } /** - * Resolve alert. + * Archive alert. * * @param int $alert_id the id of the alert. * @param bool $is_false_positive is the alert a false positive. */ - public static function resolve_alert( $alert_id = null, $is_false_positive = false ) { + public static function archive_alert( $alert_id = null, $is_false_positive = false ) { // Set the alert state. $new_alert_state = $is_false_positive ? 2 : 1; $alert_result = Alert::set_alert_state( $alert_id, $new_alert_state ); // Add the alert from tests table -> this should stop testing. - $alert = (object) Alert::get_item( $alert_id ); - Test::set_alert( $alert->post_id, null ); + $alert = Alert::get_item( $alert_id ); + + $latest_alert_id = Alert::get_latest_alert_id_by_post_id( $alert->post_id ); + Test::set_alert( $alert->post_id, $latest_alert_id ); if ( $is_false_positive ) { $service = new Service(); @@ -408,7 +410,9 @@ public static function restore_alert( $alert_id = null ) { // Remove the alert from tests table -> this should continue testing. $alert = (object) Alert::get_item( $alert_id ); - Test::set_alert( $alert->post_id, $alert_id ); + + $latest_alert_id = Alert::get_latest_alert_id_by_post_id( $alert->post_id ); + Test::set_alert( $alert->post_id, $latest_alert_id ); } /** @@ -419,9 +423,13 @@ public static function restore_alert( $alert_id = null ) { public static function delete_alert( $alert_id = null ) { // Remove the alert from tests table, only to be sure that. $alert = (object) Alert::get_item( $alert_id ); - Test::set_alert( $alert->post_id, null ); // Remove the alert from the database. Alert::delete( $alert_id ); + + // Set the latest alert to the test. + $latest_alert_id = Alert::get_latest_alert_id_by_post_id( $alert->post_id ); + Test::set_alert( $alert->post_id, $latest_alert_id ); + } } diff --git a/includes/features/class-email-notifications.php b/includes/features/class-email-notifications.php index 2e264f6..90181d6 100644 --- a/includes/features/class-email-notifications.php +++ b/includes/features/class-email-notifications.php @@ -27,17 +27,10 @@ public function send_email( $differences, $post_id, $alert_id ) { esc_url( get_the_permalink( $post_id ) ) ); - /** - * It replaces common plain text characters with formatted entities - * so we remove it as in plain text emails it displays it as e.g. ’s - */ - remove_filter( 'the_title', 'wptexturize' ); - remove_filter( 'the_title', 'convert_chars' ); - $message = esc_html_x( 'Howdy,', 'notification email', 'visual-regression-tests' ) . "\n\n" . esc_html_x( 'New visual differences have been detected on the following page:', 'notification email', 'visual-regression-tests' ) . "\n\n" . - get_the_title( $post_id ) . "\n\n" . - esc_html_x( 'Review and resolve the alert to resume testing:', 'notification email', 'visual-regression-tests' ) . "\n" . + wp_specialchars_decode( get_the_title( $post_id ) ) . "\n\n" . + esc_html_x( 'View the alert:', 'notification email', 'visual-regression-tests' ) . "\n" . esc_url( $admin_url ) . 'admin.php?page=vrts-alerts&action=edit&alert_id=' . $alert_id . "\n\n" . sprintf( /* translators: %1$s: the home url */ @@ -52,7 +45,7 @@ public function send_email( $differences, $post_id, $alert_id ) { } if ( $notification_email ) { - $sent = wp_mail( $notification_email, $subject, $message, $headers ); + $sent = wp_mail( $notification_email, wp_specialchars_decode( $subject ), $message, $headers ); if ( $sent ) { return true; } else { diff --git a/includes/features/class-enqueue-scripts.php b/includes/features/class-enqueue-scripts.php index adc8d2d..19f1d69 100644 --- a/includes/features/class-enqueue-scripts.php +++ b/includes/features/class-enqueue-scripts.php @@ -98,21 +98,7 @@ public function enqueue_block_editor_assets() { wp_enqueue_script( 'vrts-editor' ); // Localize scripts. - global $post; - $alert_id = Test::get_alert_id( $post->ID ); - $testing_status_instructions = ''; - if ( $alert_id ) { - $base_link = admin_url( 'admin.php?page=vrts-alerts&action=edit&alert_id=' ); - $testing_status_instructions .= sprintf( - /* translators: %1$s and %2$s: link wrapper. */ - esc_html__( 'Resolve %1$salert%2$s to resume testing', 'visual-regression-tests' ), - '', - '' - ); - } - - $test_id = Test::get_item_id( $post->ID ); - $test = (object) Test::get_item( $test_id ); + $test = (object) Test::get_item_by_post_id( $post->ID ); wp_localize_script( 'vrts-editor', @@ -121,16 +107,15 @@ public function enqueue_block_editor_assets() { 'plugin_name' => vrts()->get_plugin_info( 'name' ), 'rest_url' => esc_url_raw( rest_url() ), 'has_post_alert' => Test::has_post_alert( $post->ID ), - 'test_status' => (bool) Test::get_status( $post->ID ), 'base_screenshot_url' => Test::get_base_screenshot_url( $post->ID ), 'base_screenshot_date' => Date_Time_Helpers::get_formatted_date_time( Test::get_base_screenshot_date( $post->ID ) ), - 'testing_status_instructions' => $testing_status_instructions, - 'placeholder_image_data_url' => vrts()->get_snapshot_placeholder_image(), 'remaining_tests' => Subscription::get_remaining_tests(), 'total_tests' => Subscription::get_total_tests(), 'upgrade_url' => admin_url( 'admin.php?page=vrts-upgrade' ), 'plugin_url' => admin_url( 'admin.php?page=vrts' ), 'is_connected' => Service::is_connected(), + 'test_status' => Test::get_status_data( $test ), + 'screenshot' => Test::get_screenshot_data( $test ), 'test_settings' => [ 'test_id' => isset( $test->id ) ? $test->id : null, 'hide_css_selectors' => isset( $test->hide_css_selectors ) ? $test->hide_css_selectors : null, diff --git a/includes/features/class-metaboxes.php b/includes/features/class-metaboxes.php index 5f4e599..c8c2075 100644 --- a/includes/features/class-metaboxes.php +++ b/includes/features/class-metaboxes.php @@ -122,7 +122,7 @@ public function render_metabox() { $base_link = admin_url( 'admin.php?page=vrts-alerts&action=edit&alert_id=' ); $testing_status_instructions .= sprintf( /* translators: %1$s and %2$s: link wrapper. */ - esc_html__( 'Resolve %1$salert%2$s to resume testing', 'visual-regression-tests' ), + esc_html__( '%1$sView Alert%2$s', 'visual-regression-tests' ), '', '' ); @@ -134,18 +134,19 @@ public function render_metabox() { vrts()->component('metabox-classic-editor', [ 'post_id' => $post_id, 'nonce' => $this->nonce, + 'plugin_url' => admin_url( 'admin.php?page=vrts' ), 'run_tests_checked' => $run_tests_checked, 'field_test_status_key' => self::$field_test_status_key, 'has_post_alert' => Test::has_post_alert( $post_id ), - 'test_status' => (bool) Test::get_status( $post_id ), 'base_screenshot_url' => Test::get_base_screenshot_url( $post_id ), 'base_screenshot_date' => $base_screenshot_date, 'testing_status_instructions' => $testing_status_instructions, - 'placeholder_image_data_url' => vrts()->get_snapshot_placeholder_image(), 'is_new_test' => self::is_new_test( $post_id ), 'remaining_tests' => Subscription::get_remaining_tests(), 'total_tests' => Subscription::get_total_tests(), 'is_connected' => Service::is_connected(), + 'test_status' => Test::get_status_data( $test ), + 'screenshot' => Test::get_screenshot_data( $test ), 'test_settings' => [ 'test_id' => isset( $test->id ) ? $test->id : null, 'hide_css_selectors' => isset( $test->hide_css_selectors ) ? $test->hide_css_selectors : null, diff --git a/includes/features/class-onboarding.php b/includes/features/class-onboarding.php index 6fab090..171ea73 100644 --- a/includes/features/class-onboarding.php +++ b/includes/features/class-onboarding.php @@ -3,6 +3,7 @@ namespace Vrts\Features; use Vrts\Models\Alert; +use Vrts\Models\Test; class Onboarding { @@ -78,20 +79,159 @@ public function has_user_completed_onboarding( $onboarding_id ) { public function get_onboardings() { return [ [ - 'id' => 'false-positives', + 'id' => 'tests-welcome', + 'permission_callback' => [ $this, 'should_display_tests_welcome_onboarding' ], + 'steps' => [ + [ + 'title' => wp_kses_post( __( '👋 Howdy, welcome aboard!', 'visual-regression-tests' ) ), + 'description' => wp_kses_post( __( "With our VRTs plugin, you can effortlessly maintain your website's visual consistency.

Automatically detect visual changes and receive Alerts to achieve pixel-perfect precision.", 'visual-regression-tests' ) ), + ], + [ + 'title' => wp_kses_post( __( '⏰ Daily Checks, automatic Alerts', 'visual-regression-tests' ) ), + 'description' => wp_kses_post( __( "Our daily Test Run takes screenshots of your Test pages and performs split-screen comparisons.

Rest assured, you'll be notified via email, as soon as a visual change is detected.", 'visual-regression-tests' ) ), + ], + [ + 'side' => 'right', + 'align' => 'start', + 'padding' => 8, + 'element' => '#show-modal-add-new', + 'title' => wp_kses_post( __( "🚀 Let's get started!", 'visual-regression-tests' ) ), + 'description' => wp_kses_post( __( 'Add your first Test here to enable VRTs for the selected page.', 'visual-regression-tests' ) ), + ], + ], + ], + [ + 'id' => 'first-test', + 'permission_callback' => [ $this, 'should_display_first_test_onboarding' ], + 'steps' => [ + [ + 'side' => 'bottom', + 'align' => 'center', + 'element' => '.wp-list-table tbody tr:first-child', + 'title' => wp_kses_post( __( '🥳 Yay, you created your first VRT!', 'visual-regression-tests' ) ), + 'description' => wp_kses_post( __( 'Starting from tomorrow, your Test will run daily, ensuring consistent monitoring of your page.', 'visual-regression-tests' ) ), + ], + [ + 'element' => '.vrts_navigation_item a[href$="admin.php?page=vrts-settings"]', + 'title' => wp_kses_post( __( '🛠️ Fine-tune your setup', 'visual-regression-tests' ) ), + 'description' => wp_kses_post( __( 'Further customize your Test configuration and plugin settings for an optimized experience.', 'visual-regression-tests' ) ), + ], + ], + ], + [ + 'id' => 'run-test', + 'permission_callback' => [ $this, 'should_display_run_test_onboarding' ], + 'steps' => [ + [ + 'padding' => 2, + 'element' => '.wp-list-table .vrts-run-test', + 'title' => wp_kses_post( __( '🔬 Run your Test now', 'visual-regression-tests' ) ), + 'description' => wp_kses_post( __( 'Want to see your Test in action?
Give it a go and run this Test now!', 'visual-regression-tests' ) ), + ], + ], + ], + [ + 'id' => 'alerts', 'permission_callback' => [ $this, 'should_display_alerts_onboarding' ], 'steps' => [ + [ + 'side' => 'top', + 'align' => 'center', + 'element' => '#post-body-content .postbox-header', + 'title' => wp_kses_post( __( '🔍 Compare Changes', 'visual-regression-tests' ) ), + 'description' => wp_kses_post( __( 'Utilize the Difference, Split, and Side by Side views to accurately identify visual differences.', 'visual-regression-tests' ) ), + ], + [ + 'side' => 'left', + 'padding' => 10, + 'element' => '.vrts-alert-settings-postbox', + 'title' => wp_kses_post( __( '🛠️ Fine-tune Tests', 'visual-regression-tests' ) ), + 'description' => wp_kses_post( __( 'If the Alert is not accurate, adjust the Test setup by excluding elements from the page when the snapshot is created.', 'visual-regression-tests' ) ), + ], [ 'side' => 'left', + 'padding' => 10, 'element' => '#delete-action', - 'title' => wp_kses_post( __( 'Mark as false positive', 'visual-regression-tests' ) ), - 'description' => wp_kses_post( __( 'If this alert was triggered by a harmless visual change.

Once flagged, this alert will not reappear.', 'visual-regression-tests' ) ), + 'title' => wp_kses_post( __( '🚫 Mark as false positive', 'visual-regression-tests' ) ), + 'description' => wp_kses_post( __( 'You may stop this Alert from happening again. Mark it as a false positive and the plugin will filter out matching Alerts in the future.', 'visual-regression-tests' ) ), + ], + [ + 'side' => 'left', + 'padding' => 10, + 'element' => '#publishing-action', + 'title' => wp_kses_post( __( '📦 Archive your Alerts', 'visual-regression-tests' ) ), + 'description' => wp_kses_post( __( 'Organize your Alerts by archiving them after review, for easy access and future reference.', 'visual-regression-tests' ) ), ], ], ], ]; } + /** + * Should display tests welcome onboarding. + * + * @return bool + */ + public function should_display_tests_welcome_onboarding() { + $page = sanitize_text_field( wp_unslash( $_GET['page'] ?? '' ) ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Nonce verification is not required here. + if ( 'vrts' === $page ) { + $frontpage_id = get_option( 'page_on_front' ); + $is_front_page_added = ! is_null( Test::get_item_id( $frontpage_id ) ); + $next_id = Test::get_autoincrement_value(); + + if ( 1 === $next_id || ( $is_front_page_added && 2 === $next_id ) ) { + return true; + } + } + + return false; + } + + /** + * Should display first test onboarding. + * + * @return bool + */ + public function should_display_first_test_onboarding() { + $page = sanitize_text_field( wp_unslash( $_GET['page'] ?? '' ) ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Nonce verification is not required here. + $is_new_test_added = isset( $_GET['new-test-added'] ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Nonce verification is not required here. + + if ( 'vrts' === $page && $is_new_test_added ) { + $frontpage_id = get_option( 'page_on_front' ); + $is_front_page_added = ! is_null( Test::get_item_id( $frontpage_id ) ); + $next_id = Test::get_autoincrement_value(); + + if ( 2 === $next_id || ( $is_front_page_added && 3 === $next_id ) ) { + return true; + } + } + + return false; + } + + /** + * Should display run test onboarding. + * + * @return bool + */ + public function should_display_run_test_onboarding() { + $page = sanitize_text_field( wp_unslash( $_GET['page'] ?? '' ) ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Nonce verification is not required here. + $has_subscription = (bool) Subscription::get_subscription_status(); + + if ( 'vrts' === $page && $has_subscription ) { + $tests = Test::get_items(); + + foreach ( $tests as $test ) { + $status = Test::get_calculated_status( $test ); + if ( 'scheduled' === $status ) { + return true; + } + } + } + + return false; + } + /** * Should display alerts onboarding. * @@ -103,10 +243,11 @@ public function should_display_alerts_onboarding() { $alert_id = sanitize_text_field( wp_unslash( $_GET['alert_id'] ?? '' ) ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Nonce verification is not required here. if ( 'vrts-alerts' === $page && 'edit' === $action && '' !== $alert_id ) { - $alert = Alert::get_item( $alert_id ); + $archived_alerts = Alert::get_items( [ + 'filter_status' => 'archived', + ] ); - // only if the alert has option to mark as false positive. - if ( $alert && 0 === (int) $alert->alert_state && null !== $alert->comparison_id ) { + if ( ! $archived_alerts ) { return true; } } diff --git a/includes/features/class-post-update-actions.php b/includes/features/class-post-update-actions.php index bcbeca5..68d1030 100644 --- a/includes/features/class-post-update-actions.php +++ b/includes/features/class-post-update-actions.php @@ -24,8 +24,8 @@ public function __construct() { * @param int $post_id Post ID. */ public function resume_test( $post_id ) { - // If post has test and no active alerts, update the screenshot to the latest version. - if ( Test::get_item_id( $post_id ) && ! Test::has_post_alert( $post_id ) ) { + // If post has test, update the screenshot to the latest version. + if ( Test::get_item_id( $post_id ) ) { $service = new Test_Service(); $service->resume_test( $post_id ); } @@ -41,11 +41,8 @@ public function on_trash_post_action( $post_id ) { $test_id = Test::get_item_id( $post_id ); if ( $test_id ) { Test::delete( $test_id ); - // If an alert exists already, resolve it too. - $alert_id = Alert::get_alert_id_by_post_id( $post_id, 0 ); - if ( $alert_id ) { - Alert::set_alert_state( $alert_id, 1 ); - } + // If an alert exists already, archive it too. + Alert::set_alert_state_for_post_id( $post_id, 1 ); } } diff --git a/includes/features/class-service.php b/includes/features/class-service.php index ad83206..0185139 100644 --- a/includes/features/class-service.php +++ b/includes/features/class-service.php @@ -7,7 +7,7 @@ use Vrts\Services\Test_Service; class Service { - const DB_VERSION = '1.0'; + const DB_VERSION = '1.1'; const SERVICE = 'vrts_service'; const BASE_URL = VRTS_SERVICE_ENDPOINT; @@ -27,6 +27,12 @@ public static function connect_service() { if ( self::is_connected() && ! self::has_secret() ) { self::create_secret(); } + if ( $installed_version && version_compare( $installed_version, '1.1', '<' ) ) { + $service_project_id = get_option( 'vrts_project_id' ); + $service_api_route = 'sites/' . $service_project_id; + + self::rest_service_request( $service_api_route, [], 'put' ); + } } /** @@ -105,6 +111,8 @@ public static function rest_service_request( $service_api_route, $parameters = [ $args['headers']['Authorization'] = 'Bearer ' . $service_project_token; } + add_filter( 'http_headers_useragent', [ static::class, 'set_user_agent' ], 10 ); + switch ( $request_type ) { case 'get': $args = [ @@ -148,6 +156,9 @@ public static function rest_service_request( $service_api_route, $parameters = [ 'status_code' => wp_remote_retrieve_response_code( $data ), ]; } + + remove_filter( 'http_headers_useragent', [ static::class, 'set_user_agent' ], 10 ); + return $response; } @@ -322,6 +333,19 @@ public static function has_secret() { return (bool) get_option( 'vrts_project_secret' ); } + /** + * Set user agent for the request. + * + * @param string $user_agent the user agent. + */ + public static function set_user_agent( $user_agent ) { + if ( function_exists( 'vrts' ) ) { + return 'VRTs/' . vrts()->get_plugin_info( 'version' ) . ';' . $user_agent; + } else { + return $user_agent; + } + } + /** * Create secret for the project */ diff --git a/includes/features/class-settings-page.php b/includes/features/class-settings-page.php index fd0be49..931db70 100644 --- a/includes/features/class-settings-page.php +++ b/includes/features/class-settings-page.php @@ -122,7 +122,7 @@ public function add_settings() { vrts()->settings()->add_setting([ 'type' => 'text', 'id' => 'vrts_click_selectors', - 'title' => esc_html__( 'Click an element before creating a snapshot', 'visual-regression-tests' ), + 'title' => esc_html__( 'Click Element', 'visual-regression-tests' ), 'description' => sprintf( '%s
%s', sprintf( @@ -138,7 +138,7 @@ public function add_settings() { 'show_in_rest' => true, 'value_type' => 'string', 'default' => '', - 'placeholder' => esc_html__( 'e.g.: [data-cookie-accept]', 'visual-regression-tests' ), + 'placeholder' => esc_html__( 'e.g.: #accept-cookies', 'visual-regression-tests' ), ]); vrts()->settings()->add_section([ diff --git a/includes/list-tables/class-alerts-list-table.php b/includes/list-tables/class-alerts-list-table.php index e007722..0f8e5a9 100644 --- a/includes/list-tables/class-alerts-list-table.php +++ b/includes/list-tables/class-alerts-list-table.php @@ -81,7 +81,7 @@ public function column_default( $item, $column_name ) { $date_time = ''; if ( $item->target_screenshot_finish_date ) { $status = esc_html__( 'Detected', 'visual-regression-tests' ) . '
'; - $date_time = Date_Time_Helpers::get_formatted_date_time( $item->target_screenshot_finish_date ); + $date_time = Date_Time_Helpers::get_formatted_relative_date_time( $item->target_screenshot_finish_date ); } return $status . $date_time; @@ -121,8 +121,8 @@ public function column_title( $item ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- It's status request. $filter_status_query = ( isset( $_REQUEST['status'] ) ? sanitize_text_field( wp_unslash( $_REQUEST['status'] ) ) : 'all' ); - if ( 'resolved' === $filter_status_query ) { - // Actions Status "Resolved". + if ( 'archived' === $filter_status_query ) { + // Actions Status "Archived". $actions['view'] = sprintf( '%s', $base_link . '&action=view&alert_id=' . $item->id, @@ -159,10 +159,10 @@ public function column_title( $item ) { if ( $is_connected ) { $actions['trash'] = sprintf( '%s', - $base_link . '&action=resolve&alert_id=' . $item->id, + $base_link . '&action=archive&alert_id=' . $item->id, $item->id, - __( 'Resolve this alert', 'visual-regression-tests' ), - __( 'Resolve', 'visual-regression-tests' ) + __( 'Archive this alert', 'visual-regression-tests' ), + __( 'Archive', 'visual-regression-tests' ) ); } @@ -186,17 +186,18 @@ public function column_title( $item ) { */ public function column_differences( $item ) { $is_connected = Service::is_connected(); + $base_link = admin_url( 'admin.php?page=vrts-alerts' ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- It's status request. $filter_status_query = ( isset( $_REQUEST['status'] ) ? sanitize_text_field( wp_unslash( $_REQUEST['status'] ) ) : 'all' ); - if ( 'resolved' === $filter_status_query ) { - // Status "Resolved". + if ( 'archived' === $filter_status_query ) { + // Status "Archived". $differences = ceil( $item->differences / 4 ); $is_false_positive = 2 === (int) $item->alert_state; return sprintf( '%s %s', /* translators: %s: the count of pixels with a visual difference. */ esc_html( sprintf( _n( '%s pixel', '%s pixels', $differences, 'visual-regression-tests' ), $differences ) ), - $is_false_positive ? '' . esc_html__( 'False Positive', 'visual-regression-tests' ) . '' : '' + $is_false_positive ? '' . esc_html__( 'False positive', 'visual-regression-tests' ) . '' : '' ); } else { // Status "Open". @@ -207,12 +208,9 @@ public function column_differences( $item ) { /* translators: %s: the count of pixels with a visual difference. */ esc_html( sprintf( _n( '%s pixel', '%s pixels', $differences, 'visual-regression-tests' ), $differences ) ), sprintf( - /* translators: %s: link wrapper */ - esc_html__( 'Tests on %1$spage%2$s are %3$spaused%4$s', 'visual-regression-tests' ), - '', - '', - '', - '' + '%s', + $base_link . '&action=edit&alert_id=' . $item->id, + esc_html__( 'View Alert', 'visual-regression-tests' ) ) ); } else { @@ -258,14 +256,14 @@ public function get_bulk_actions() { // phpcs:ignore Processing form data without nonce verification, WordPress.Security.NonceVerification.Recommended -- Should be okay for now. $filter_status_query = ( isset( $_REQUEST['status'] ) ? sanitize_text_field( wp_unslash( $_REQUEST['status'] ) ) : 'all' ); - if ( 'resolved' === $filter_status_query ) { + if ( 'archived' === $filter_status_query ) { $actions = [ 'delete' => esc_html__( 'Delete permanently', 'visual-regression-tests' ), ]; } else { // Actions Status "Open". $actions = [ - 'set-status-resolved' => esc_html__( 'Resolve', 'visual-regression-tests' ), + 'set-status-archived' => esc_html__( 'Archive', 'visual-regression-tests' ), ]; } return $actions; @@ -284,13 +282,13 @@ public function process_bulk_action() { return; } - if ( 'set-status-resolved' === $this->current_action() ) { + if ( 'set-status-archived' === $this->current_action() ) { // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Should be okay for now. $alert_ids = wp_unslash( $_POST['id'] ?? '' ); foreach ( $alert_ids as $alert_id ) { $alert_id = intval( $alert_id ); - Alerts_Page::resolve_alert( $alert_id ); + Alerts_Page::archive_alert( $alert_id ); } } @@ -329,14 +327,14 @@ public function get_views() { $links = [ 'all' => [ - 'title' => esc_html__( 'Open', 'visual-regression-tests' ), + 'title' => esc_html__( 'New', 'visual-regression-tests' ), 'link' => $base_link, 'count' => Alert::get_total_items(), ], - 'resolved' => [ - 'title' => esc_html__( 'Resolved', 'visual-regression-tests' ), - 'link' => "{$base_link}&status=resolved", - 'count' => Alert::get_total_items( 'resolved' ), + 'archived' => [ + 'title' => esc_html__( 'Archived', 'visual-regression-tests' ), + 'link' => "{$base_link}&status=archived", + 'count' => Alert::get_total_items( 'archived' ), ], ]; diff --git a/includes/list-tables/class-tests-list-table.php b/includes/list-tables/class-tests-list-table.php index f04511e..72ca7a4 100644 --- a/includes/list-tables/class-tests-list-table.php +++ b/includes/list-tables/class-tests-list-table.php @@ -248,15 +248,10 @@ public function get_views() { 'link' => $base_link, 'count' => Test::get_total_items(), ], - 'running' => [ - 'title' => esc_html__( 'Running', 'visual-regression-tests' ), - 'link' => "{$base_link}&status=running", - 'count' => Test::get_total_items( 'running' ), - ], - 'paused' => [ - 'title' => esc_html__( 'Paused', 'visual-regression-tests' ), - 'link' => "{$base_link}&status=paused", - 'count' => Test::get_total_items( 'paused' ), + 'changes-detected' => [ + 'title' => esc_html__( 'Changes detected', 'visual-regression-tests' ), + 'link' => "{$base_link}&status=changes-detected", + 'count' => Test::get_total_items( 'changes-detected' ), ], ]; @@ -297,10 +292,6 @@ public function prepare_items() { $sortable = $this->get_sortable_columns(); $this->_column_headers = [ $columns, $hidden, $sortable ]; - $per_page = $this->get_items_per_page( 'vrts_tests_per_page', 20 ); - $current_page = $this->get_pagenum(); - $offset = ( $current_page - 1 ) * $per_page; - // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- It's the list order parameter. $order = isset( $_REQUEST['order'] ) && 'asc' === $_REQUEST['order'] ? 'ASC' : 'DESC'; @@ -314,8 +305,7 @@ public function prepare_items() { $filter_status_query = isset( $_REQUEST['status'] ) && '' !== $_REQUEST['status'] ? sanitize_text_field( wp_unslash( $_REQUEST['status'] ) ) : null; $args = [ - 'offset' => $offset, - 'number' => $per_page, + 'number' => -1, 'order' => $order, 'orderby' => $order_by, 's' => $search_query, @@ -335,7 +325,8 @@ public function prepare_items() { $this->set_pagination_args([ 'total_items' => $total_items, - 'per_page' => $per_page, + // we set it to a high number to avoid pagination. + 'per_page' => 100000, ]); } @@ -423,126 +414,13 @@ public function inline_edit() { * @return string */ private function render_column_status( $item ) { - $is_connected = Service::is_connected(); - $has_subscription = Subscription::get_subscription_status(); - $no_tests_left = intval( Subscription::get_remaining_tests() ) === 0; - $has_remote_test = ! empty( $item->service_test_id ); - $has_base_screenshot = ! empty( $item->base_screenshot_date ); - $has_comparison = ! empty( $item->last_comparison_date ); - $is_running = (bool) $item->is_running; - - $test_status = 'passed'; - if ( ! (bool) $is_connected ) { - $test_status = 'disconnected'; - } elseif ( $item->current_alert_id ) { - $test_status = 'has-alert'; - } elseif ( false === (bool) $item->status && ( $no_tests_left || $has_remote_test ) ) { - $test_status = 'no_credit_left'; - } elseif ( ! $has_remote_test ) { - $test_status = 'post_not_published'; - } elseif ( ! $has_base_screenshot ) { - $test_status = 'waiting'; - } elseif ( $is_running ) { - $test_status = 'running'; - } elseif ( ! $has_comparison ) { - $test_status = 'scheduled'; - }//end if - - switch ( $test_status ) { - case 'disconnected': - $class = 'testing-status--paused'; - $text = esc_html__( 'Disconnected', 'visual-regression-tests' ); - $instructions = ''; - break; - case 'has-alert': - $alert = Alert::get_item( $item->current_alert_id ); - $class = 'testing-status--paused'; - $text = esc_html__( 'Changes detected', 'visual-regression-tests' ); - $base_link = admin_url( 'admin.php?page=vrts-alerts&action=edit&alert_id=' ); - $instructions = '
'; - $instructions .= Date_Time_Helpers::get_formatted_relative_date_time( $alert->target_screenshot_finish_date ); - $instructions .= '
'; - $instructions .= sprintf( - /* translators: %1$s and %2$s: link wrapper. */ - esc_html__( '%1$s%2$s Resolve alert%3$s to resume test', 'visual-regression-tests' ), - '', - '', - '' - ); - break; - case 'no_credit_left': - $class = 'testing-status--paused'; - $text = esc_html__( 'Disabled', 'visual-regression-tests' ); - $base_link = admin_url( 'admin.php?page=vrts-upgrade' ); - $instructions = '
'; - $instructions .= sprintf( - /* translators: %1$s and %2$s: link wrapper. */ - esc_html__( '%1$sUpgrade plugin%2$s to resume testing', 'visual-regression-tests' ), - '', - '' - ); - break; - case 'post_not_published': - $class = 'testing-status--paused'; - $text = esc_html__( 'Disabled', 'visual-regression-tests' ); - $instructions = '
'; - $instructions .= esc_html__( 'Publish post to resume testing', 'visual-regression-tests' ); - break; - case 'waiting': - $class = 'testing-status--waiting'; - $text = esc_html__( 'Waiting', 'visual-regression-tests' ); - $instructions = ''; - break; - case 'running': - $class = 'testing-status--waiting'; - $text = esc_html__( 'In Progress', 'visual-regression-tests' ); - $instructions = '
'; - $instructions .= esc_html__( 'Refresh page to see result', 'visual-regression-tests' ); - break; - case 'scheduled': - $class = 'testing-status--waiting'; - $text = esc_html__( 'Scheduled', 'visual-regression-tests' ); - $instructions = '
'; - if ( $item->next_run_date ) { - $instructions .= Date_Time_Helpers::get_formatted_relative_date_time( $item->next_run_date ); - $instructions .= '
'; - } - if ( $has_subscription ) { - $instructions .= sprintf( - '%s', - admin_url( 'admin.php?page=vrts&action=run-manual-test&test_id=' ) . $item->id, - $item->id, - esc_html__( 'Run test now', 'visual-regression-tests' ), - ' ' . esc_html__( 'Run test now', 'visual-regression-tests' ) - ); - } - break; - case 'passed': - default: - $class = 'testing-status--running'; - $text = esc_html__( 'Passed', 'visual-regression-tests' ); - $instructions = '
'; - if ( $item->last_comparison_date ) { - $instructions .= Date_Time_Helpers::get_formatted_relative_date_time( $item->last_comparison_date ); - $instructions .= '
'; - } - if ( $has_subscription ) { - $instructions .= sprintf( - '%s', - admin_url( 'admin.php?page=vrts&action=run-manual-test&test_id=' ) . $item->id, - $item->id, - esc_html__( 'Run test now', 'visual-regression-tests' ), - ' ' . esc_html__( 'Run test now', 'visual-regression-tests' ) - ); - } - break; - }//end switch + $status_data = Test::get_status_data( $item ); return sprintf( - '%s%s', - $class, - $text, - $instructions + '

%s

%s

', + 'vrts-testing-status--' . $status_data['class'], + $status_data['text'], + $status_data['instructions'] ); } @@ -554,39 +432,12 @@ private function render_column_status( $item ) { * @return string */ private function render_column_snapshot( $item ) { - $base_screenshot_status = 'taken'; - if ( false === (bool) $item->status ) { - $base_screenshot_status = 'paused'; - } elseif ( ! $item->base_screenshot_date ) { - $base_screenshot_status = 'waiting'; - }//end if - - switch ( $base_screenshot_status ) { - case 'paused': - $output = esc_html__( 'On hold', 'visual-regression-tests' ); - break; - case 'waiting': - $status = esc_html__( 'In progress', 'visual-regression-tests' ); - $output = sprintf( - '%s
%s', - 'testing-status--waiting', - $status, - esc_html__( 'Refresh page to see snapshot', 'visual-regression-tests' ) - ); - break; - case 'taken': - default: - $status = sprintf( - '%s
', - Test::get_base_screenshot_url( $item->post_id ), - $item->id, - esc_html__( 'View this snapshot', 'visual-regression-tests' ), - esc_html__( 'View Snapshot', 'visual-regression-tests' ) - ); - $date_time = Date_Time_Helpers::get_formatted_relative_date_time( $item->base_screenshot_date ); - $output = $status . $date_time; - break; - }//end switch - return $output; + $screenshot_data = Test::get_screenshot_data( $item ); + + return sprintf( + '

%s

%s

', + $screenshot_data['text'], + $screenshot_data['instructions'] + ); } } diff --git a/includes/models/class-alert.php b/includes/models/class-alert.php index 550ecd7..95349eb 100644 --- a/includes/models/class-alert.php +++ b/includes/models/class-alert.php @@ -22,6 +22,7 @@ public static function get_items( $args = [] ) { $alerts_table = Alerts_Table::get_table_name(); $defaults = [ + 's' => '', 'number' => 20, 'offset' => 0, 'orderby' => 'id', @@ -32,9 +33,9 @@ public static function get_items( $args = [] ) { $args = wp_parse_args( $args, $defaults ); // 0 = Open - // 1 = Resolved. + // 1 = Archived. // 2 = False Positive. - $alert_states = ( null !== $args['filter_status'] && 'resolved' === $args['filter_status'] ) ? [ 1, 2 ] : [ 0 ]; + $alert_states = ( null !== $args['filter_status'] && 'archived' === $args['filter_status'] ) ? [ 1, 2 ] : [ 0 ]; $alert_states_placeholders = implode( ', ', array_fill( 0, count( $alert_states ), '%d' ) ); $where = $wpdb->prepare( @@ -43,7 +44,7 @@ public static function get_items( $args = [] ) { $alert_states ); - if ( null !== $args['s'] ) { + if ( '' !== $args['s'] ) { $where .= $wpdb->prepare( ' AND title LIKE %s', '%' . $wpdb->esc_like( $args['s'] ) . '%' @@ -99,14 +100,14 @@ public static function get_item( $id = 0 ) { } /** - * Get alert id by post id + * Get latest alert id by post id * * @param int $post_id the id of the post. * @param int $alert_state the state of the item. * - * @return array + * @return int */ - public static function get_alert_id_by_post_id( $post_id = 0, $alert_state = 0 ) { + public static function get_latest_alert_id_by_post_id( $post_id = 0, $alert_state = 0 ) { global $wpdb; $alerts_table = Alerts_Table::get_table_name(); @@ -118,7 +119,7 @@ public static function get_alert_id_by_post_id( $post_id = 0, $alert_state = 0 ) "SELECT id FROM $alerts_table WHERE alert_state = %d AND post_id = %d - ORDER BY id ASC + ORDER BY id DESC LIMIT 1", $alert_state, $post_id @@ -139,9 +140,9 @@ public static function get_total_items( $filter_status_query = null ) { $alerts_table = Alerts_Table::get_table_name(); // 0 = Open - // 1 = Resolved. + // 1 = Archived. // 2 = False Positive. - $alert_states = ( 'resolved' === $filter_status_query ) ? [ 1, 2 ] : [ 0 ]; + $alert_states = ( 'archived' === $filter_status_query ) ? [ 1, 2 ] : [ 0 ]; $alert_states_placeholders = implode( ', ', array_fill( 0, count( $alert_states ), '%d' ) ); $where = $wpdb->prepare( @@ -161,26 +162,19 @@ public static function get_total_items( $filter_status_query = null ) { /** - * Delete a test from database and update its post meta. + * Update alert status. * * @param int $id the id of the item. * @param int $new_alert_state the new state of the item. */ public static function set_alert_state( $id = 0, $new_alert_state = null ) { - // 0 = Open / 1 = Resolved / 2 = False Positive. + // 0 = Open / 1 = Archived / 2 = False Positive. if ( in_array( $new_alert_state, [ 0, 1, 2 ], true ) ) { global $wpdb; $alerts_table = Alerts_Table::get_table_name(); $data = [ 'alert_state' => $new_alert_state ]; - // Resume test after alert is resolved or marked false positive. - if ( in_array( $new_alert_state, [ 1, 2 ], true ) ) { - $alert = self::get_item( $id ); - $service = new Test_Service(); - $service->resume_test( $alert->post_id ); - } - // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- It's ok. return $wpdb->update( $alerts_table, $data, [ 'id' => $id ] ); } else { @@ -188,6 +182,27 @@ public static function set_alert_state( $id = 0, $new_alert_state = null ) { } } + /** + * Update all alert statuses for post id. + * + * @param int $post_id the id of the item. + * @param int $new_alert_state the new state of the item. + */ + public static function set_alert_state_for_post_id( $post_id = 0, $new_alert_state = null ) { + // 0 = Open / 1 = Archived / 2 = False Positive. + if ( in_array( $new_alert_state, [ 0, 1, 2 ], true ) ) { + global $wpdb; + + $alerts_table = Alerts_Table::get_table_name(); + $data = [ 'alert_state' => $new_alert_state ]; + + // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- It's ok. + return $wpdb->update( $alerts_table, $data, [ 'post_id' => $post_id ] ); + } else { + return false; + } + } + /** * Get the next open alert id. */ diff --git a/includes/models/class-test.php b/includes/models/class-test.php index 547aff8..fba0e2f 100644 --- a/includes/models/class-test.php +++ b/includes/models/class-test.php @@ -2,6 +2,7 @@ namespace Vrts\Models; +use Vrts\Core\Utilities\Date_Time_Helpers; use Vrts\Features\Service; use Vrts\Features\Subscription; use Vrts\Tables\Alerts_Table; @@ -16,10 +17,11 @@ class Test { * Get all test items from database * * @param array $args Optional. + * @param bool $return_count Optional. * * @return object */ - public static function get_items( $args = [] ) { + public static function get_items( $args = [], $return_count = false ) { global $wpdb; $tests_table = Tests_Table::get_table_name(); @@ -34,6 +36,7 @@ public static function get_items( $args = [] ) { $args = wp_parse_args( $args, $defaults ); + $select = $return_count ? 'SELECT COUNT(*)' : 'SELECT *'; $where = 'WHERE 1=1'; if ( isset( $args['s'] ) && null !== $args['s'] ) { @@ -44,13 +47,14 @@ public static function get_items( $args = [] ) { } if ( isset( $args['filter_status'] ) && null !== $args['filter_status'] ) { - // current_alert_id IS NOT NULL = Pause. - if ( 'paused' === $args['filter_status'] ) { - $where .= ' AND tests.current_alert_id IS NOT NULL'; + if ( 'changes-detected' === $args['filter_status'] ) { + $where .= " AND calculated_status = '6-has-alert'"; } - // current_alert_id IS NULL = Running. - if ( 'running' === $args['filter_status'] ) { - $where .= ' AND tests.current_alert_id IS NULL'; + if ( 'passed' === $args['filter_status'] ) { + $where .= " AND calculated_status = '5-passed'"; + } + if ( 'scheduled' === $args['filter_status'] ) { + $where .= " AND calculated_status = '4-scheduled'"; } } @@ -68,58 +72,64 @@ public static function get_items( $args = [] ) { $limit = $args['number'] > 100 ? 100 : $args['number']; - $limits = $wpdb->prepare( - 'LIMIT %d, %d', - $args['offset'], - $limit - ); - - $is_connected = Service::is_connected() ? 'true' : 'false'; - $no_tests_left = intval( Subscription::get_remaining_tests() ) === 0 ? 'true' : 'false'; + if ( $args['number'] < 1 ) { + $limits = ''; + } else { + $limits = $wpdb->prepare( + 'LIMIT %d, %d', + $args['offset'], + $limit + ); + } $query = " - SELECT - tests.id, - tests.status, - tests.base_screenshot_date, - tests.post_id, - tests.current_alert_id, - tests.service_test_id, - tests.hide_css_selectors, - tests.next_run_date, - tests.last_comparison_date, - tests.is_running, - posts.post_title, - CASE - WHEN $is_connected is not true THEN '0-disconnected' - WHEN tests.current_alert_id is not null THEN '7-has-alert' - WHEN tests.status > 0 and $no_tests_left THEN '1-no_credit_left' - WHEN tests.service_test_id is null THEN '2-post_not_published' - WHEN tests.base_screenshot_date is null THEN '3-waiting' - WHEN tests.is_running > 0 THEN '4-running' - WHEN tests.last_comparison_date is null THEN '5-scheduled' - else '6-passed' - END as calculated_status, - CASE - WHEN tests.current_alert_id is not null THEN alerts.target_screenshot_finish_date - WHEN tests.status > 0 and $no_tests_left THEN tests.base_screenshot_date - WHEN tests.service_test_id is null THEN tests.base_screenshot_date - WHEN tests.base_screenshot_date is null THEN tests.base_screenshot_date - WHEN tests.is_running > 0 THEN tests.base_screenshot_date - WHEN tests.last_comparison_date is null THEN tests.next_run_date - else tests.last_comparison_date - END as calculated_date - FROM $tests_table as tests - INNER JOIN $wpdb->posts as posts ON posts.id = tests.post_id - LEFT JOIN $alerts_table as alerts ON alerts.id = tests.current_alert_id + $select + FROM ( + SELECT + tests.id, + tests.status, + tests.base_screenshot_date, + tests.post_id, + tests.current_alert_id, + tests.service_test_id, + tests.hide_css_selectors, + tests.next_run_date, + tests.last_comparison_date, + tests.is_running, + posts.post_title, + CASE + WHEN tests.current_alert_id is not null THEN '6-has-alert' + WHEN tests.service_test_id is null THEN '1-post-not-published' + WHEN tests.base_screenshot_date is null THEN '2-waiting' + WHEN tests.is_running > 0 THEN '3-running' + WHEN tests.last_comparison_date is null THEN '4-scheduled' + else '5-passed' + END as calculated_status, + CASE + WHEN tests.current_alert_id is not null THEN alerts.target_screenshot_finish_date + WHEN tests.service_test_id is null THEN tests.base_screenshot_date + WHEN tests.base_screenshot_date is null THEN tests.base_screenshot_date + WHEN tests.is_running > 0 THEN tests.base_screenshot_date + WHEN tests.last_comparison_date is null THEN tests.next_run_date + else tests.last_comparison_date + END as calculated_date + FROM $tests_table as tests + INNER JOIN $wpdb->posts as posts ON posts.id = tests.post_id + LEFT JOIN $alerts_table as alerts ON alerts.id = tests.current_alert_id + GROUP BY tests.id + ) tests $where - GROUP BY tests.id $orderby $limits "; - // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared, WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Query is prepared above. - $items = $wpdb->get_results( $query ); + if ( $return_count ) { + // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared, WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Query is prepared above. + $items = $wpdb->get_var( $query ); + } else { + // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared, WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Query is prepared above. + $items = $wpdb->get_results( $query ); + } return $items; } @@ -137,7 +147,7 @@ public static function get_all_running() { // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- It's ok. return $wpdb->get_results( // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- It's ok. - "SELECT * FROM $tests_table WHERE status != 0 AND current_alert_id IS NULL" + "SELECT * FROM $tests_table WHERE status != 0" ); } @@ -268,6 +278,27 @@ public static function get_item_id( $post_id = 0 ) { ); } + /** + * Get autoincrement value + * + * @return int + */ + public static function get_autoincrement_value() { + global $wpdb; + + $tests_table = Tests_Table::get_table_name(); + + // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- It's ok. + return (int) $wpdb->get_var( + $wpdb->prepare( + // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- It's ok. + 'SELECT AUTO_INCREMENT FROM information_schema.TABLES WHERE TABLE_SCHEMA = %s AND TABLE_NAME = %s', + DB_NAME, + $tests_table + ) + ); + } + /** * Checks if post has a test * @@ -456,24 +487,10 @@ public static function get_base_screenshot_date( $post_id = 0 ) { * @return array */ public static function get_total_items( $filter_status_query = null ) { - global $wpdb; - - $tests_table = Tests_Table::get_table_name(); - $query = "SELECT COUNT(*) FROM $tests_table"; - - if ( null !== $filter_status_query ) { - // current_alert_id IS NOT NULL = Pause. - if ( 'paused' === $filter_status_query ) { - $query .= ' WHERE current_alert_id IS NOT NULL'; - } - // current_alert_id IS NULL = Running. - if ( 'running' === $filter_status_query ) { - $query .= ' WHERE current_alert_id IS NULL'; - } - } - - // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.NotPrepared -- It's prepared above - return (int) $wpdb->get_var( $query ); + return (int) self::get_items( [ + 'number' => -1, + 'filter_status' => $filter_status_query, + ], true ); } /** @@ -603,6 +620,7 @@ public static function get_all_service_test_ids() { public static function set_alert( $post_id = 0, $alert_id = 0 ) { global $wpdb; + $alert_id = 0 === $alert_id ? null : $alert_id; $tests_table = Tests_Table::get_table_name(); $data = [ 'current_alert_id' => $alert_id ]; $where = [ 'post_id' => $post_id ]; @@ -635,6 +653,60 @@ public static function get_status( $post_id = 0 ) { return $post_status; } + /** + * Get test calculated status + * + * @param int|object $test test id or test object. + * + * @return string + */ + public static function get_calculated_status( $test ) { + if ( is_int( $test ) ) { + $test = self::get_item( $test ); + } + + if ( ! Service::is_connected() ) { + return 'disconnected'; + } + + // If test doesn't exist set initial status to 'waiting'. + if ( ! isset( $test->id ) ) { + return 'waiting'; + } + + $no_tests_left = intval( Subscription::get_remaining_tests() ) === 0; + $has_remote_test = ! empty( $test->service_test_id ); + $has_base_screenshot = ! empty( $test->base_screenshot_date ); + $has_comparison = ! empty( $test->last_comparison_date ); + $is_running = (bool) $test->is_running; + + if ( $test->current_alert_id ) { + return 'has-alert'; + } + + if ( false === (bool) $test->status && ( $no_tests_left || $has_remote_test ) ) { + return 'no-credit-left'; + } + + if ( ! $has_remote_test ) { + return 'post-not-published'; + } + + if ( ! $has_base_screenshot ) { + return 'waiting'; + } + + if ( $is_running ) { + return 'running'; + } + + if ( ! $has_comparison ) { + return 'scheduled'; + } + + return 'passed'; + } + /** * Pause test. * @@ -747,7 +819,6 @@ public static function reset_base_screenshot( $test_id ) { return $wpdb->update( $table_test, [ - 'current_alert_id' => null, 'base_screenshot_url' => null, 'base_screenshot_date' => null, 'last_comparison_date' => null, @@ -787,4 +858,180 @@ public static function set_tests_running( $test_ids ) { ) ); } + + /** + * Get test status data + * + * @param int|object $test test id or test object. + * + * @return array + */ + public static function get_status_data( $test ) { + if ( is_int( $test ) ) { + $test = self::get_item( $test ); + } + + $test_status = self::get_calculated_status( $test ); + $has_subscription = Subscription::get_subscription_status(); + $instructions = ''; + + switch ( $test_status ) { + case 'disconnected': + $class = 'paused'; + $text = esc_html__( 'Disconnected', 'visual-regression-tests' ); + break; + case 'has-alert': + $alert = Alert::get_item( $test->current_alert_id ); + $class = 'paused'; + $text = esc_html__( 'Changes detected', 'visual-regression-tests' ); + $base_link = admin_url( 'admin.php?page=vrts-alerts&action=edit&alert_id=' ); + $instructions = Date_Time_Helpers::get_formatted_relative_date_time( $alert->target_screenshot_finish_date ); + $instructions .= sprintf( + /* translators: %1$s and %2$s: link wrapper. */ + esc_html__( '%1$s%2$s View Alert%3$s', 'visual-regression-tests' ), + '', + '', + '' + ); + break; + case 'no-credit-left': + $class = 'paused'; + $text = esc_html__( 'Disabled', 'visual-regression-tests' ); + $base_link = admin_url( 'admin.php?page=vrts-upgrade' ); + $instructions = sprintf( + /* translators: %1$s and %2$s: link wrapper. */ + esc_html__( '%1$sUpgrade plugin%2$s to resume testing', 'visual-regression-tests' ), + '', + '' + ); + break; + case 'post-not-published': + $class = 'paused'; + $text = esc_html__( 'Disabled', 'visual-regression-tests' ); + $instructions = esc_html__( 'Publish post to resume testing', 'visual-regression-tests' ); + break; + case 'waiting': + $class = 'waiting'; + $text = esc_html__( 'Waiting', 'visual-regression-tests' ); + break; + case 'running': + $class = 'waiting'; + $text = esc_html__( 'In Progress', 'visual-regression-tests' ); + $instructions = esc_html__( 'Refresh page to see result', 'visual-regression-tests' ); + break; + case 'scheduled': + $class = 'waiting'; + $text = esc_html__( 'Scheduled', 'visual-regression-tests' ); + if ( $test->next_run_date ) { + $instructions = Date_Time_Helpers::get_formatted_relative_date_time( $test->next_run_date ); + } + // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Nonce is not required here. + if ( $has_subscription && isset( $_GET['page'] ) && 'vrts' === $_GET['page'] ) { + $instructions .= sprintf( + '%s', + admin_url( 'admin.php?page=vrts&action=run-manual-test&test_id=' ) . $test->id, + $test->id, + esc_html__( 'Run Test', 'visual-regression-tests' ), + ' ' . esc_html__( 'Run Test', 'visual-regression-tests' ) + ); + } + break; + case 'passed': + default: + $class = 'running'; + $text = esc_html__( 'Passed', 'visual-regression-tests' ); + if ( $test->last_comparison_date ) { + $instructions .= Date_Time_Helpers::get_formatted_relative_date_time( $test->last_comparison_date ); + } + // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Nonce is not required here. + if ( $has_subscription && isset( $_GET['page'] ) && 'vrts' === $_GET['page'] ) { + $instructions .= sprintf( + '%s', + admin_url( 'admin.php?page=vrts&action=run-manual-test&test_id=' ) . $test->id, + $test->id, + esc_html__( 'Run Test', 'visual-regression-tests' ), + ' ' . esc_html__( 'Run Test', 'visual-regression-tests' ) + ); + } + break; + }//end switch + + return [ + 'status' => $test_status, + 'class' => $class, + 'text' => $text, + 'instructions' => $instructions, + ]; + } + + /** + * Get test screenshot data + * + * @param int|object $test test id or object. + * + * @return array + */ + public static function get_screenshot_data( $test ) { + if ( is_int( $test ) ) { + $test = self::get_item( $test ); + } + + $screenshot_status = 'taken'; + + if ( ! Service::is_connected() ) { + $screenshot_status = 'paused'; + } elseif ( ! isset( $test->id ) ) { + $screenshot_status = 'waiting'; + } elseif ( false === (bool) $test->status ) { + $screenshot_status = 'paused'; + } elseif ( ! $test->base_screenshot_date ) { + $screenshot_status = 'waiting'; + }//end if + + $instructions = ''; + $screenshot = sprintf( + '%s', + esc_url( vrts()->get_plugin_url( 'assets/images/vrts-snapshot-placeholder.svg' ) ), + esc_html__( 'Snapshot', 'visual-regression-tests' ) + ); + + switch ( $screenshot_status ) { + case 'paused': + $text = esc_html__( 'On hold', 'visual-regression-tests' ); + break; + case 'waiting': + $text = esc_html__( 'In progress', 'visual-regression-tests' ); + $instructions = sprintf( + '%s', + esc_html__( 'Refresh page to see snapshot', 'visual-regression-tests' ) + ); + break; + case 'taken': + default: + $text = sprintf( + '%s', + self::get_base_screenshot_url( $test->post_id ), + $test->id, + esc_html__( 'View this snapshot', 'visual-regression-tests' ), + esc_html__( 'View Snapshot', 'visual-regression-tests' ) + ); + $instructions = Date_Time_Helpers::get_formatted_relative_date_time( $test->base_screenshot_date ); + $screenshot = sprintf( + '%s', + esc_url( self::get_base_screenshot_url( $test->post_id ) ), + esc_attr( $test->id ), + esc_html__( 'View this snapshot', 'visual-regression-tests' ), + esc_url( self::get_base_screenshot_url( $test->post_id ) ), + esc_html__( 'View Snapshot', 'visual-regression-tests' ) + ); + break; + }//end switch + + return [ + 'status' => $screenshot_status, + 'text' => $text, + 'instructions' => $instructions, + 'screenshot' => $screenshot, + ]; + } } diff --git a/includes/services/class-test-service.php b/includes/services/class-test-service.php index e6b35c2..f1349ce 100644 --- a/includes/services/class-test-service.php +++ b/includes/services/class-test-service.php @@ -27,15 +27,20 @@ public function update_test_from_comparison( $alert_id, $test_id, $data ) { $comparison = $data['comparison']; if ( $comparison['screenshot']['image_url'] ?? null ) { // Update test row with new id foreign key and add latest screenshot. + $update_data = [ + 'next_run_date' => $data['next_run_at'] ?? '', + 'last_comparison_date' => $comparison['updated_at'], + 'base_screenshot_url' => $comparison['screenshot']['image_url'], + 'base_screenshot_date' => $comparison['screenshot']['updated_at'], + 'is_running' => false, + ]; + if ( $alert_id ) { + $update_data['current_alert_id'] = $alert_id; + } // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- It's ok. return $wpdb->update( $table_test, - [ - 'current_alert_id' => $alert_id, - 'next_run_date' => $data['next_run_at'] ?? '', - 'last_comparison_date' => $comparison['updated_at'], - 'is_running' => false, - ], + $update_data, [ 'service_test_id' => $test_id ] ); } @@ -84,7 +89,7 @@ public function update_test_from_api_data( $data ) { $this->update_test_from_schedule( $test_id, $data ); } elseif ( $data['comparison'] ?? null ) { $alert_id = null; - if ( $data['is_paused'] ?? null && $data['comparison']['pixels_diff'] > 1 ) { + if ( $data['comparison']['pixels_diff'] > 1 && ! $data['matches_false_positive'] ) { $comparison = $data['comparison']; $alert_service = new Alert_Service(); $alert_id = $alert_service->create_alert_from_comparison( $post_id, $test_id, $comparison ); diff --git a/package-lock.json b/package-lock.json index e503e87..d2ed574 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,9 +10,11 @@ "license": "GPL-2.0-or-later", "dependencies": { "@wordpress/components": "^22.1.0", + "@wordpress/dom-ready": "^3.56.0", "@wordpress/edit-post": "^6.19.0", "@wordpress/element": "^4.20.0", "@wordpress/i18n": "^4.22.0", + "@wordpress/icons": "^9.47.0", "@wordpress/plugins": "^4.20.0", "dompurify": "^2.4.1", "driver.js": "^1.3.1", @@ -4990,9 +4992,9 @@ } }, "node_modules/@wordpress/dom-ready": { - "version": "3.22.0", - "resolved": "https://registry.npmjs.org/@wordpress/dom-ready/-/dom-ready-3.22.0.tgz", - "integrity": "sha512-w3g9TvC3lDh1IYrH+YW5DMDoOH+dWOfU0XJ+ZTJRxJUnqPVKKTZKseZoHF7YGmbiOA63sfW8/NjaYLRmXHqnZw==", + "version": "3.56.0", + "resolved": "https://registry.npmjs.org/@wordpress/dom-ready/-/dom-ready-3.56.0.tgz", + "integrity": "sha512-f/NMkDLxBOlQ905mMOKjJNhIdiZjlJ/BdcRTxtK1MwnA91lTSVZigEw4eXApU1fK86brZ8LD5Sg5YgD+NrYQzA==", "dependencies": { "@babel/runtime": "^7.16.0" }, @@ -5121,9 +5123,9 @@ } }, "node_modules/@wordpress/escape-html": { - "version": "2.22.0", - "resolved": "https://registry.npmjs.org/@wordpress/escape-html/-/escape-html-2.22.0.tgz", - "integrity": "sha512-GUo6VLugIZxen1rdYuotvz6Vqa+5fNtVelNjXLwDqRu0iY2RXeoTux9V5bZWXPnGb54ryqfYmR4gH6F8xZhWzQ==", + "version": "2.56.0", + "resolved": "https://registry.npmjs.org/@wordpress/escape-html/-/escape-html-2.56.0.tgz", + "integrity": "sha512-f+NDe9ZyUtaoiU8VYSKRjxsKqqzinrVcpcqj+umiLhKD5ShGW8V7LcSr3JOdE4TgjHvw2eezFvRmEo/kXowmMA==", "dependencies": { "@babel/runtime": "^7.16.0" }, @@ -5242,18 +5244,92 @@ } }, "node_modules/@wordpress/icons": { - "version": "9.13.0", - "resolved": "https://registry.npmjs.org/@wordpress/icons/-/icons-9.13.0.tgz", - "integrity": "sha512-V8q55fI0rtzxRdJbQsAjUgg7V8JbWoncm5SyuvfEtmkL+IKTQUrYgaKO0DKPf7qaTPcZJlnOXUzy6XW+fxHmxA==", + "version": "9.47.0", + "resolved": "https://registry.npmjs.org/@wordpress/icons/-/icons-9.47.0.tgz", + "integrity": "sha512-IQIoEr0LxPWUOgcHnMIqU/ytg3x/swxbl8AGG1ONFks3/2tYdDk3I2/CAYgQGpaiSFIOJjNVk1keqa8DBOnciw==", "dependencies": { "@babel/runtime": "^7.16.0", - "@wordpress/element": "^4.20.0", - "@wordpress/primitives": "^3.20.0" + "@wordpress/element": "^5.33.0", + "@wordpress/primitives": "^3.54.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@wordpress/icons/node_modules/@types/react": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.1.tgz", + "integrity": "sha512-V0kuGBX3+prX+DQ/7r2qsv1NsdfnCLnTgnRJ1pYnxykBhGMz+qj+box5lq7XsO5mtZsBqpjwwTu/7wszPfMBcw==", + "dependencies": { + "@types/prop-types": "*", + "csstype": "^3.0.2" + } + }, + "node_modules/@wordpress/icons/node_modules/@types/react-dom": { + "version": "18.3.0", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.3.0.tgz", + "integrity": "sha512-EhwApuTmMBmXuFOikhQLIBUn6uFg81SwLMOAUgodJF14SOBOCMdU04gDoYi0WOJJHD144TL32z4yDqCW3dnkQg==", + "dependencies": { + "@types/react": "*" + } + }, + "node_modules/@wordpress/icons/node_modules/@wordpress/element": { + "version": "5.33.0", + "resolved": "https://registry.npmjs.org/@wordpress/element/-/element-5.33.0.tgz", + "integrity": "sha512-RNisHbRgAO5/RLyfckgHYWgKq+IKd8Yn1mJHYWp+1Fx+1K6vjlhr/1D4a81fWL15IoCTV3tYh6zYei4/fRpZog==", + "dependencies": { + "@babel/runtime": "^7.16.0", + "@types/react": "^18.0.21", + "@types/react-dom": "^18.0.6", + "@wordpress/escape-html": "^2.56.0", + "change-case": "^4.1.2", + "is-plain-object": "^5.0.0", + "react": "^18.2.0", + "react-dom": "^18.2.0" }, "engines": { "node": ">=12" } }, + "node_modules/@wordpress/icons/node_modules/is-plain-object": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-5.0.0.tgz", + "integrity": "sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/@wordpress/icons/node_modules/react": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", + "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==", + "dependencies": { + "loose-envify": "^1.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/@wordpress/icons/node_modules/react-dom": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz", + "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==", + "dependencies": { + "loose-envify": "^1.1.0", + "scheduler": "^0.23.2" + }, + "peerDependencies": { + "react": "^18.3.1" + } + }, + "node_modules/@wordpress/icons/node_modules/scheduler": { + "version": "0.23.2", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz", + "integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==", + "dependencies": { + "loose-envify": "^1.1.0" + } + }, "node_modules/@wordpress/interface": { "version": "4.21.0", "resolved": "https://registry.npmjs.org/@wordpress/interface/-/interface-4.21.0.tgz", @@ -5471,18 +5547,92 @@ } }, "node_modules/@wordpress/primitives": { - "version": "3.20.0", - "resolved": "https://registry.npmjs.org/@wordpress/primitives/-/primitives-3.20.0.tgz", - "integrity": "sha512-+30QC2bPv3sj3aYlS9q0TZh8LSYIERd49CizHqJ2/M9XCpWV6jLPwZk+k3pcOJHIpHEBNB9lB+4UG7ulj8WxkQ==", + "version": "3.54.0", + "resolved": "https://registry.npmjs.org/@wordpress/primitives/-/primitives-3.54.0.tgz", + "integrity": "sha512-2TrXDvYW3V0nlq6ZCYYvJ5obPZNtrsuIdB0iLdUavCOSBoXTROhRZY9Pxz45bB2CLlmEUs9OfL7izx9IuAg4Mw==", "dependencies": { "@babel/runtime": "^7.16.0", - "@wordpress/element": "^4.20.0", + "@wordpress/element": "^5.33.0", "classnames": "^2.3.1" }, "engines": { "node": ">=12" } }, + "node_modules/@wordpress/primitives/node_modules/@types/react": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.1.tgz", + "integrity": "sha512-V0kuGBX3+prX+DQ/7r2qsv1NsdfnCLnTgnRJ1pYnxykBhGMz+qj+box5lq7XsO5mtZsBqpjwwTu/7wszPfMBcw==", + "dependencies": { + "@types/prop-types": "*", + "csstype": "^3.0.2" + } + }, + "node_modules/@wordpress/primitives/node_modules/@types/react-dom": { + "version": "18.3.0", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.3.0.tgz", + "integrity": "sha512-EhwApuTmMBmXuFOikhQLIBUn6uFg81SwLMOAUgodJF14SOBOCMdU04gDoYi0WOJJHD144TL32z4yDqCW3dnkQg==", + "dependencies": { + "@types/react": "*" + } + }, + "node_modules/@wordpress/primitives/node_modules/@wordpress/element": { + "version": "5.33.0", + "resolved": "https://registry.npmjs.org/@wordpress/element/-/element-5.33.0.tgz", + "integrity": "sha512-RNisHbRgAO5/RLyfckgHYWgKq+IKd8Yn1mJHYWp+1Fx+1K6vjlhr/1D4a81fWL15IoCTV3tYh6zYei4/fRpZog==", + "dependencies": { + "@babel/runtime": "^7.16.0", + "@types/react": "^18.0.21", + "@types/react-dom": "^18.0.6", + "@wordpress/escape-html": "^2.56.0", + "change-case": "^4.1.2", + "is-plain-object": "^5.0.0", + "react": "^18.2.0", + "react-dom": "^18.2.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@wordpress/primitives/node_modules/is-plain-object": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-5.0.0.tgz", + "integrity": "sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/@wordpress/primitives/node_modules/react": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", + "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==", + "dependencies": { + "loose-envify": "^1.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/@wordpress/primitives/node_modules/react-dom": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz", + "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==", + "dependencies": { + "loose-envify": "^1.1.0", + "scheduler": "^0.23.2" + }, + "peerDependencies": { + "react": "^18.3.1" + } + }, + "node_modules/@wordpress/primitives/node_modules/scheduler": { + "version": "0.23.2", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz", + "integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==", + "dependencies": { + "loose-envify": "^1.1.0" + } + }, "node_modules/@wordpress/priority-queue": { "version": "2.22.0", "resolved": "https://registry.npmjs.org/@wordpress/priority-queue/-/priority-queue-2.22.0.tgz", @@ -24710,9 +24860,9 @@ } }, "@wordpress/dom-ready": { - "version": "3.22.0", - "resolved": "https://registry.npmjs.org/@wordpress/dom-ready/-/dom-ready-3.22.0.tgz", - "integrity": "sha512-w3g9TvC3lDh1IYrH+YW5DMDoOH+dWOfU0XJ+ZTJRxJUnqPVKKTZKseZoHF7YGmbiOA63sfW8/NjaYLRmXHqnZw==", + "version": "3.56.0", + "resolved": "https://registry.npmjs.org/@wordpress/dom-ready/-/dom-ready-3.56.0.tgz", + "integrity": "sha512-f/NMkDLxBOlQ905mMOKjJNhIdiZjlJ/BdcRTxtK1MwnA91lTSVZigEw4eXApU1fK86brZ8LD5Sg5YgD+NrYQzA==", "requires": { "@babel/runtime": "^7.16.0" } @@ -24820,9 +24970,9 @@ } }, "@wordpress/escape-html": { - "version": "2.22.0", - "resolved": "https://registry.npmjs.org/@wordpress/escape-html/-/escape-html-2.22.0.tgz", - "integrity": "sha512-GUo6VLugIZxen1rdYuotvz6Vqa+5fNtVelNjXLwDqRu0iY2RXeoTux9V5bZWXPnGb54ryqfYmR4gH6F8xZhWzQ==", + "version": "2.56.0", + "resolved": "https://registry.npmjs.org/@wordpress/escape-html/-/escape-html-2.56.0.tgz", + "integrity": "sha512-f+NDe9ZyUtaoiU8VYSKRjxsKqqzinrVcpcqj+umiLhKD5ShGW8V7LcSr3JOdE4TgjHvw2eezFvRmEo/kXowmMA==", "requires": { "@babel/runtime": "^7.16.0" } @@ -24898,13 +25048,77 @@ } }, "@wordpress/icons": { - "version": "9.13.0", - "resolved": "https://registry.npmjs.org/@wordpress/icons/-/icons-9.13.0.tgz", - "integrity": "sha512-V8q55fI0rtzxRdJbQsAjUgg7V8JbWoncm5SyuvfEtmkL+IKTQUrYgaKO0DKPf7qaTPcZJlnOXUzy6XW+fxHmxA==", + "version": "9.47.0", + "resolved": "https://registry.npmjs.org/@wordpress/icons/-/icons-9.47.0.tgz", + "integrity": "sha512-IQIoEr0LxPWUOgcHnMIqU/ytg3x/swxbl8AGG1ONFks3/2tYdDk3I2/CAYgQGpaiSFIOJjNVk1keqa8DBOnciw==", "requires": { "@babel/runtime": "^7.16.0", - "@wordpress/element": "^4.20.0", - "@wordpress/primitives": "^3.20.0" + "@wordpress/element": "^5.33.0", + "@wordpress/primitives": "^3.54.0" + }, + "dependencies": { + "@types/react": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.1.tgz", + "integrity": "sha512-V0kuGBX3+prX+DQ/7r2qsv1NsdfnCLnTgnRJ1pYnxykBhGMz+qj+box5lq7XsO5mtZsBqpjwwTu/7wszPfMBcw==", + "requires": { + "@types/prop-types": "*", + "csstype": "^3.0.2" + } + }, + "@types/react-dom": { + "version": "18.3.0", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.3.0.tgz", + "integrity": "sha512-EhwApuTmMBmXuFOikhQLIBUn6uFg81SwLMOAUgodJF14SOBOCMdU04gDoYi0WOJJHD144TL32z4yDqCW3dnkQg==", + "requires": { + "@types/react": "*" + } + }, + "@wordpress/element": { + "version": "5.33.0", + "resolved": "https://registry.npmjs.org/@wordpress/element/-/element-5.33.0.tgz", + "integrity": "sha512-RNisHbRgAO5/RLyfckgHYWgKq+IKd8Yn1mJHYWp+1Fx+1K6vjlhr/1D4a81fWL15IoCTV3tYh6zYei4/fRpZog==", + "requires": { + "@babel/runtime": "^7.16.0", + "@types/react": "^18.0.21", + "@types/react-dom": "^18.0.6", + "@wordpress/escape-html": "^2.56.0", + "change-case": "^4.1.2", + "is-plain-object": "^5.0.0", + "react": "^18.2.0", + "react-dom": "^18.2.0" + } + }, + "is-plain-object": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-5.0.0.tgz", + "integrity": "sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==" + }, + "react": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", + "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==", + "requires": { + "loose-envify": "^1.1.0" + } + }, + "react-dom": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz", + "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==", + "requires": { + "loose-envify": "^1.1.0", + "scheduler": "^0.23.2" + } + }, + "scheduler": { + "version": "0.23.2", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz", + "integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==", + "requires": { + "loose-envify": "^1.1.0" + } + } } }, "@wordpress/interface": { @@ -25052,13 +25266,77 @@ "requires": {} }, "@wordpress/primitives": { - "version": "3.20.0", - "resolved": "https://registry.npmjs.org/@wordpress/primitives/-/primitives-3.20.0.tgz", - "integrity": "sha512-+30QC2bPv3sj3aYlS9q0TZh8LSYIERd49CizHqJ2/M9XCpWV6jLPwZk+k3pcOJHIpHEBNB9lB+4UG7ulj8WxkQ==", + "version": "3.54.0", + "resolved": "https://registry.npmjs.org/@wordpress/primitives/-/primitives-3.54.0.tgz", + "integrity": "sha512-2TrXDvYW3V0nlq6ZCYYvJ5obPZNtrsuIdB0iLdUavCOSBoXTROhRZY9Pxz45bB2CLlmEUs9OfL7izx9IuAg4Mw==", "requires": { "@babel/runtime": "^7.16.0", - "@wordpress/element": "^4.20.0", + "@wordpress/element": "^5.33.0", "classnames": "^2.3.1" + }, + "dependencies": { + "@types/react": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.1.tgz", + "integrity": "sha512-V0kuGBX3+prX+DQ/7r2qsv1NsdfnCLnTgnRJ1pYnxykBhGMz+qj+box5lq7XsO5mtZsBqpjwwTu/7wszPfMBcw==", + "requires": { + "@types/prop-types": "*", + "csstype": "^3.0.2" + } + }, + "@types/react-dom": { + "version": "18.3.0", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.3.0.tgz", + "integrity": "sha512-EhwApuTmMBmXuFOikhQLIBUn6uFg81SwLMOAUgodJF14SOBOCMdU04gDoYi0WOJJHD144TL32z4yDqCW3dnkQg==", + "requires": { + "@types/react": "*" + } + }, + "@wordpress/element": { + "version": "5.33.0", + "resolved": "https://registry.npmjs.org/@wordpress/element/-/element-5.33.0.tgz", + "integrity": "sha512-RNisHbRgAO5/RLyfckgHYWgKq+IKd8Yn1mJHYWp+1Fx+1K6vjlhr/1D4a81fWL15IoCTV3tYh6zYei4/fRpZog==", + "requires": { + "@babel/runtime": "^7.16.0", + "@types/react": "^18.0.21", + "@types/react-dom": "^18.0.6", + "@wordpress/escape-html": "^2.56.0", + "change-case": "^4.1.2", + "is-plain-object": "^5.0.0", + "react": "^18.2.0", + "react-dom": "^18.2.0" + } + }, + "is-plain-object": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-5.0.0.tgz", + "integrity": "sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==" + }, + "react": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", + "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==", + "requires": { + "loose-envify": "^1.1.0" + } + }, + "react-dom": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz", + "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==", + "requires": { + "loose-envify": "^1.1.0", + "scheduler": "^0.23.2" + } + }, + "scheduler": { + "version": "0.23.2", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz", + "integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==", + "requires": { + "loose-envify": "^1.1.0" + } + } } }, "@wordpress/priority-queue": { diff --git a/package.json b/package.json index c5f17fe..8f59885 100644 --- a/package.json +++ b/package.json @@ -33,9 +33,11 @@ ], "dependencies": { "@wordpress/components": "^22.1.0", + "@wordpress/dom-ready": "^3.56.0", "@wordpress/edit-post": "^6.19.0", "@wordpress/element": "^4.20.0", "@wordpress/i18n": "^4.22.0", + "@wordpress/icons": "^9.47.0", "@wordpress/plugins": "^4.20.0", "dompurify": "^2.4.1", "driver.js": "^1.3.1", diff --git a/readme.txt b/readme.txt index 1263994..a82ca1d 100644 --- a/readme.txt +++ b/readme.txt @@ -125,6 +125,21 @@ The VRTs plugin primarily supports WordPress pages and posts. Automated visual t * Set up a redirect from this page to your desired URL. * Configure a test for this page. The screenshotter will follow the redirect. += My screenshots only contain a block screen from my firewall. What can I do? = + +If you are using a firewall that blocks our screenshot service, you can whitelist the IP addresses of our service. + +For Cloudflare, you can whitelist the IP addresses of our service by following these steps: + +1. Log in to your Cloudflare account. +2. Go to **Security -> WAF**. +3. Click on **Tools**. +4. In the **IP Access Rules** box enter the IP address (`49.13.14.240`) of our service. +5. Select **Allow** from the action dropdown. +6. Enter VRTs as the note. +7. Click on the **Add** button. + + == Installation == = INSTALL VRTS WITHIN WORDPRESS = diff --git a/visual-regression-tests.php b/visual-regression-tests.php index cdb2144..122f638 100644 --- a/visual-regression-tests.php +++ b/visual-regression-tests.php @@ -3,7 +3,7 @@ * Plugin Name: VRTs – Visual Regression Tests * Plugin URI: https://bleech.de/en/products/visual-regression-tests/ * Description: Test your website for unwanted visual changes. Run automatic tests and spot differences. - * Version: 1.8.0 + * Version: 1.9.0 * Requires at least: 5.0 * Requires PHP: 7.0 * Author: Bleech