Skip to content

Commit

Permalink
Merge pull request #20522 from wincher-ab/feature/wincher-modal-impro…
Browse files Browse the repository at this point in the history
…vements

Feature/wincher modal improvements
  • Loading branch information
enricobattocchi authored Oct 19, 2023
2 parents 0258bbe + 6a4a84c commit 55c136e
Show file tree
Hide file tree
Showing 30 changed files with 1,131 additions and 280 deletions.
1 change: 1 addition & 0 deletions admin/class-admin-asset-manager.php
Original file line number Diff line number Diff line change
Expand Up @@ -482,6 +482,7 @@ protected function load_renamed_scripts() {
'feature-flag' => 'feature-flag-package',
'helpers' => 'helpers-package',
'jed' => 'jed-package',
'chart.js' => 'chart.js-package',
'legacy-components' => 'components-package',
'network-admin-script' => 'network-admin',
'redux' => 'redux-package',
Expand Down
34 changes: 18 additions & 16 deletions config/webpack/externals.js
Original file line number Diff line number Diff line change
@@ -1,38 +1,40 @@
const { camelCaseDash } = require( "@wordpress/dependency-extraction-webpack-plugin/lib/util" );
const {
camelCaseDash,
} = require("@wordpress/dependency-extraction-webpack-plugin/lib/util");

/**
* Yoast dependencies, declared as such in the package.json.
*/
const { dependencies } = require( "../../packages/js/package" );
const legacyYoastPackages = [ "yoast-components", "yoastseo" ];
const additionalPackages = [
const { dependencies } = require("../../packages/js/package");
const legacyYoastPackages = ["yoast-components", "yoastseo"];
const additionalPackages = [
"draft-js",
"styled-components",
"jed",
"prop-types",
"redux",
"@reduxjs/toolkit",
"react-helmet",
"chart.js",
];

const YOAST_PACKAGE_NAMESPACE = "@yoast/";

// Fetch all packages from the dependencies list.
const yoastPackages = Object.keys( dependencies )
.filter(
( packageName ) =>
packageName.startsWith( YOAST_PACKAGE_NAMESPACE ) ||
legacyYoastPackages.includes( packageName ) ||
additionalPackages.includes( packageName )
);
const yoastPackages = Object.keys(dependencies).filter(
(packageName) =>
packageName.startsWith(YOAST_PACKAGE_NAMESPACE) ||
legacyYoastPackages.includes(packageName) ||
additionalPackages.includes(packageName)
);

/**
* Convert Yoast packages to externals configuration.
*/
const yoastExternals = yoastPackages.reduce( ( memo, packageName ) => {
let useablePackageName = packageName.replace( YOAST_PACKAGE_NAMESPACE, "" );
const yoastExternals = yoastPackages.reduce((memo, packageName) => {
let useablePackageName = packageName.replace(YOAST_PACKAGE_NAMESPACE, "");

switch ( useablePackageName ) {
switch (useablePackageName) {
case "components":
useablePackageName = "components-new";
break;
Expand All @@ -47,9 +49,9 @@ const yoastExternals = yoastPackages.reduce( ( memo, packageName ) => {
break;
}

memo[ packageName ] = camelCaseDash( useablePackageName );
memo[packageName] = camelCaseDash(useablePackageName);
return memo;
}, {} );
}, {});

module.exports = {
YOAST_PACKAGE_NAMESPACE,
Expand Down
2 changes: 1 addition & 1 deletion config/webpack/webpack.config.base.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ const { yoastExternals } = require( "./externals" );
let analyzerPort = 8888;

module.exports = function( { entry, output, combinedOutputFile, cssExtractFileName } ) {
const exclude = /node_modules[/\\](?!(yoast-components|gutenberg|yoastseo|@wordpress|@yoast|parse5)[/\\]).*/;
const exclude = /node_modules[/\\](?!(yoast-components|gutenberg|yoastseo|@wordpress|@yoast|parse5|chart.js)[/\\]).*/;
// The index of the babel-loader rule.
let ruleIndex = 0;
if ( process.env.NODE_ENV !== "production" ) {
Expand Down
6 changes: 5 additions & 1 deletion packages/babel-preset/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,11 @@ module.exports = ( api ) => {

return {
presets: [ "@wordpress/babel-preset-default" ],
plugins: [ "@babel/plugin-proposal-optional-chaining", "@babel/plugin-transform-runtime" ],
plugins: [
"@babel/plugin-proposal-optional-chaining",
"@babel/plugin-transform-runtime",
"@babel/plugin-transform-class-properties",
],
sourceType: "unambiguous",
};
};
1 change: 1 addition & 0 deletions packages/babel-preset/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
"private": false,
"dependencies": {
"@babel/plugin-proposal-optional-chaining": "^7.17.12",
"@babel/plugin-transform-class-properties": "^7.22.5",
"@babel/plugin-transform-runtime": "^7.17.12",
"@wordpress/babel-preset-default": "^6.13.0"
},
Expand Down
19 changes: 11 additions & 8 deletions packages/js/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,9 @@
"@draft-js-plugins/mention": "^5.0.0",
"@headlessui/react": "^1.7.8",
"@heroicons/react": "^1.0.6",
"@wordpress/api-fetch": "^6.13.0",
"@reduxjs/toolkit": "^1.8.3",
"@wordpress/a11y": "^2.15.1",
"@wordpress/api-fetch": "^6.13.0",
"@wordpress/block-editor": "^5.3.1",
"@wordpress/blocks": "^11.1.2",
"@wordpress/components": "^13.0.3",
Expand Down Expand Up @@ -40,9 +41,13 @@
"@yoast/ui-library": "^3.2.1",
"a11y-speak": "git+https://github.com/Yoast/a11y-speak.git#master",
"babel-polyfill": "^6.26.0",
"bowser": "^2.11.0",
"chart.js": "^4.2.1",
"chartjs-adapter-moment": "^1.0.1",
"classnames": "^2.3.2",
"draft-js": "^0.11.7",
"find-with-regex": "~1.0.2",
"formik": "^2.2.9",
"interpolate-components": "^1.1.0",
"jed": "^1.1.1",
"lodash": "^4.17.21",
Expand All @@ -51,22 +56,20 @@
"moment-duration-format": "^2.2.2",
"prop-types": "^15.5.10",
"react-animate-height": "^2.0.23",
"react-aria-live": "^2.0.5",
"react-chartjs-2": "^5.2.0",
"react-helmet": "^6.1.0",
"react-hotkeys-hook": "^4.0.5",
"react-intl": "^2.4.0",
"react-redux": "^5.0.6",
"react-router-dom": "^6.3.0",
"react-select": "^3.1.0",
"redux": "^3.7.2",
"redux-thunk": "^2.2.0",
"styled-components": "^5.3.6",
"yoast-components": "^5.24.0",
"yoastseo": "^1.91.1",
"formik": "^2.2.9",
"@reduxjs/toolkit": "^1.8.3",
"react-router-dom": "^6.3.0",
"yup": "^0.32.11",
"bowser": "^2.11.0",
"react-hotkeys-hook": "^4.0.5",
"react-aria-live": "^2.0.5"
"yup": "^0.32.11"
},
"devDependencies": {
"@babel/core": "^7.17.9",
Expand Down
96 changes: 78 additions & 18 deletions packages/js/src/components/WincherKeyphrasesTable.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
import PropTypes from "prop-types";
import { Fragment, useRef, useState, useEffect, useCallback, useMemo } from "@wordpress/element";
import { __, sprintf } from "@wordpress/i18n";
import { isEmpty, filter, debounce, without, difference } from "lodash";
import { isEmpty, filter, debounce, without, difference, orderBy } from "lodash";
import styled from "styled-components";

/* Yoast dependencies */
Expand All @@ -19,6 +19,7 @@ import {
} from "../helpers/wincherEndpoints";

import { handleAPIResponse } from "../helpers/api";
import { Checkbox } from "@yoast/components";

const GetMoreInsightsLink = makeOutboundLink();

Expand All @@ -33,15 +34,24 @@ const FocusKeyphraseFootnote = styled.span`
}
`;

const ViewColumn = styled.th`
min-width: 60px;
`;

const TableWrapper = styled.div`
width: 100%;
overflow-y: auto;
`;

const SelectKeyphraseCheckboxWrapper = styled.th`
pointer-events: ${ props => props.isDisabled ? "none" : "initial" };
padding-right: 0 !important;
& > div {
margin: 0px;
}
`;

const KeyphraseThWrapper = styled.th`
padding-left: 2px !important;
`;

/**
* Hook that returns the previous value.
*
Expand Down Expand Up @@ -87,6 +97,9 @@ const WincherKeyphrasesTable = ( props ) => {
websiteId,
focusKeyphrase,
newRequest,
startAt,
selectedKeyphrases,
onSelectKeyphrases,
} = props;

const interval = useRef();
Expand Down Expand Up @@ -124,7 +137,7 @@ const WincherKeyphrasesTable = ( props ) => {
abortController.current.abort();
}
abortController.current = typeof AbortController === "undefined" ? null : new AbortController();
return debouncedGetKeyphrases( keyphrases, permalink, abortController.current.signal );
return debouncedGetKeyphrases( keyphrases, startAt, permalink, abortController.current.signal );
},
( response ) => {
setRequestSucceeded( response );
Expand All @@ -140,6 +153,7 @@ const WincherKeyphrasesTable = ( props ) => {
setTrackedKeyphrases,
keyphrases,
permalink,
startAt,
] );

/**
Expand Down Expand Up @@ -227,8 +241,12 @@ const WincherKeyphrasesTable = ( props ) => {
// Fetch initial data and re-fetch if the permalink or keyphrases change.
const prevPermalink = usePrevious( permalink );
const prevKeyphrases = usePrevious( keyphrases );
const prevStartAt = usePrevious( startAt );
const hasParams = permalink && startAt;

useEffect( () => {
if ( isLoggedIn && permalink && ( permalink !== prevPermalink || difference( keyphrases, prevKeyphrases ).length ) ) {
if ( isLoggedIn && hasParams &&
( permalink !== prevPermalink || difference( keyphrases, prevKeyphrases ).length || startAt !== prevStartAt ) ) {
getTrackedKeyphrases();
}
}, [
Expand All @@ -238,6 +256,9 @@ const WincherKeyphrasesTable = ( props ) => {
keyphrases,
prevKeyphrases,
getTrackedKeyphrases,
hasParams,
startAt,
prevStartAt,
] );

// Tracks remaining keyphrases if trackAll is set and we have data.
Expand Down Expand Up @@ -298,24 +319,48 @@ const WincherKeyphrasesTable = ( props ) => {

const isDataLoading = isLoggedIn && trackedKeyphrases === null;

const trackedKeywordsWithHistory = useMemo( () => isEmpty( trackedKeyphrases ) ? [] : Object.values( trackedKeyphrases )
.filter( keyword => ! isEmpty( keyword?.position?.history ) )
.map( keyword => keyword.keyword ), [ trackedKeyphrases ] );

const areAllSelected = useMemo( () => selectedKeyphrases.length > 0 && trackedKeywordsWithHistory.length > 0 &&
trackedKeywordsWithHistory.every( selected => selectedKeyphrases.includes( selected ) ),
[ selectedKeyphrases, trackedKeywordsWithHistory ] );

/**
* Select or deselect all keyphrases.
*
* @returns {void}
*/
const onSelectAllKeyphrases = useCallback( () => {
onSelectKeyphrases( areAllSelected ? [] : trackedKeywordsWithHistory );
}, [ onSelectKeyphrases, areAllSelected, trackedKeywordsWithHistory ] );

const sortedKeyphrases = useMemo( () => orderBy( keyphrases, [
( keyphrase ) => Object.values( trackedKeyphrases || {} )
.map( trackedKeyphrase => trackedKeyphrase.keyword ).includes( keyphrase ),
], [ "desc" ] ), [ keyphrases, trackedKeyphrases ] );

return (
keyphrases && ! isEmpty( keyphrases ) && <Fragment>
<TableWrapper>
<table className="yoast yoast-table">
<thead>
<tr>
<th
scope="col"
abbr={ __( "Tracking", "wordpress-seo" ) }
>
{ __( "Tracking", "wordpress-seo" ) }
</th>
<th
<SelectKeyphraseCheckboxWrapper isDisabled={ trackedKeywordsWithHistory.length === 0 }>
<Checkbox
id="select-all"
onChange={ onSelectAllKeyphrases }
checked={ areAllSelected }
label=""
/>
</SelectKeyphraseCheckboxWrapper>
<KeyphraseThWrapper
scope="col"
abbr={ __( "Keyphrase", "wordpress-seo" ) }
>
{ __( "Keyphrase", "wordpress-seo" ) }
</th>
</KeyphraseThWrapper>
<th
scope="col"
abbr={ __( "Position", "wordpress-seo" ) }
Expand All @@ -328,12 +373,23 @@ const WincherKeyphrasesTable = ( props ) => {
>
{ __( "Position over time", "wordpress-seo" ) }
</th>
<ViewColumn className="yoast-table--nobreak" />
<th
scope="col"
abbr={ __( "Last updated", "wordpress-seo" ) }
>
{ __( "Last updated", "wordpress-seo" ) }
</th>
<th
scope="col"
abbr={ __( "Tracking", "wordpress-seo" ) }
>
{ __( "Tracking", "wordpress-seo" ) }
</th>
</tr>
</thead>
<tbody>
{
keyphrases.map( ( keyphrase, index ) => {
sortedKeyphrases.map( ( keyphrase, index ) => {
return ( <WincherTableRow
key={ `trackable-keyphrase-${index}` }
keyphrase={ keyphrase }
Expand All @@ -344,6 +400,8 @@ const WincherKeyphrasesTable = ( props ) => {
websiteId={ websiteId }
isDisabled={ ! isLoggedIn }
isLoading={ isDataLoading || loadingKeyphrases.indexOf( keyphrase.toLowerCase() ) >= 0 }
isSelected={ selectedKeyphrases.includes( keyphrase ) }
onSelectKeyphrases={ onSelectKeyphrases }
/> );
} )
}
Expand Down Expand Up @@ -385,14 +443,16 @@ WincherKeyphrasesTable.propTypes = {
websiteId: PropTypes.string,
permalink: PropTypes.string.isRequired,
focusKeyphrase: PropTypes.string,
startAt: PropTypes.string,
selectedKeyphrases: PropTypes.arrayOf( PropTypes.string ).isRequired,
onSelectKeyphrases: PropTypes.func.isRequired,
};

WincherKeyphrasesTable.defaultProps = {
isLoggedIn: false,
isNewlyAuthenticated: false,
keyphrases: [],
trackAll: false,
trackedKeyphrases: null,
websiteId: "",
focusKeyphrase: "",
};
Expand Down
5 changes: 3 additions & 2 deletions packages/js/src/components/WincherPerformanceReport.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import { Alert, NewButton } from "@yoast/components";
import WincherNoTrackedKeyphrasesAlert from "./modals/WincherNoTrackedKeyphrasesAlert";
import { getKeyphrasePosition, PositionOverTimeChart } from "./WincherTableRow";
import WincherReconnectAlert from "./modals/WincherReconnectAlert";
import WincherUpgradeCallout from "./modals/WincherUpgradeCallout";
import WincherUpgradeCallout, { useTrackingInfo } from "./modals/WincherUpgradeCallout";

const ViewLink = makeOutboundLink();
const GetMoreInsightsLink = makeOutboundLink();
Expand Down Expand Up @@ -465,12 +465,13 @@ const WincherPerformanceReport = ( props ) => {
const data = isLoggedIn ? props.data : fakeWincherPerformanceData;
const isBlurred = ! isLoggedIn;
const hasResults = checkHasResults( data );
const trackingInfo = useTrackingInfo( isLoggedIn );

return (
<WicnherSEOPerformanceContainer
className={ className }
>
{ isLoggedIn && <WincherUpgradeCallout isTitleShortened={ true } /> }
{ isLoggedIn && <WincherUpgradeCallout isTitleShortened={ true } trackingInfo={ trackingInfo } /> }

<GetUserMessage { ...props } data={ data } isConnectSuccess={ isConnectSuccess && isLoggedIn } />

Expand Down
Loading

0 comments on commit 55c136e

Please sign in to comment.