/**
* WordPress dependencies
*/
import apiFetch from '@safe-wordpress/api-fetch';
import { select, subscribe } from '@safe-wordpress/data';
import { render } from '@safe-wordpress/element';
import { _x } from '@safe-wordpress/i18n';
import domReady from '@safe-wordpress/dom-ready';
/**
* External dependencies
*/
import { debounce, keys, toPairs } from 'lodash';
import { registerCommands } from '@nab/commands';
import {
PreviewUrlButton,
QuotaMeter,
TestCreationButtons,
TitleAction,
} from '@nab/components';
import { store as NAB_DATA } from '@nab/data';
import { registerCoreExperiments } from '@nab/experiment-library';
import { store as NAB_EXPERIMENTS } from '@nab/experiments';
import {
areEqual,
createStagingNotice,
logError,
onElementReadyOrDomReadyTops,
} from '@nab/utils';
import { formatNumber } from '@nab/i18n';
import type {
CloudAlternativeResults,
CloudResults,
ExperimentTypeName,
Maybe,
Quota,
SubscriptionPlan,
} from '@nab/types';
/**
* Internal dependencies
*/
import './style.scss';
import { renderHelp } from './help';
import { NoticeList } from './components/notice-list';
import DefaultIcon from '../../../images/icon.svg';
type Settings = {
readonly staging: 'environment-type' | 'url' | false;
readonly subscription: SubscriptionPlan | false;
};
export function initPage( _: string, settings: Settings ): void {
registerCoreExperiments();
onElementReadyOrDomReadyTops( '.wp-header-end', renderQuotaMeter );
renderExperimentIconsASAP();
updateRunningExperimentStatuses();
domReady( () => renderPendingPageViews() );
domReady( () => formatPageViews() );
domReady( () => renderHeaderItems( settings ) );
domReady( () => updateRunningExperimentStatuses() );
domReady( () => registerCommands() );
createStagingNotice( settings.staging );
let previousTypes: ReadonlyArray< ExperimentTypeName > = [];
subscribe(
debounce( () => {
const types = keys(
select( NAB_EXPERIMENTS ).getExperimentTypes()
);
if ( areEqual( types, previousTypes ) ) {
return;
}
previousTypes = types;
renderExperimentIcons();
}, 0 ),
NAB_EXPERIMENTS
);
let prevQuota: Maybe< Quota >;
subscribe( () => {
const quota = select( NAB_DATA ).getQuota();
if ( quota === prevQuota ) {
return;
}
prevQuota = quota;
updateRunningExperimentStatuses();
}, NAB_DATA );
renderHelp();
}
// =======
// HELPERS
// =======
function renderPendingPageViews() {
const nodes = Array.from(
document.querySelectorAll< HTMLElement >(
'.nab-pending-page-views-wrapper'
)
);
nodes.forEach( ( node ) => {
const id = Number.parseInt( node.getAttribute( 'data-id' ) || '' );
if ( isNaN( id ) ) {
return;
}
try {
void apiFetch< CloudResults >( {
path: `/nab/v1/experiment/${ id }/result`,
} ).then( ( results ) => {
const views = toPairs( results ).reduce(
( acc, pair ) =>
acc +
( isAlternativeResults( pair ) ? pair[ 1 ].v || 0 : 0 ),
0
);
node.textContent = formatNumber( views );
} );
} catch ( e ) {
logError( e );
node.textContent = _x( 'Unknown', 'text', 'nelio-ab-testing' );
}
} );
}
function formatPageViews() {
const nodes = Array.from(
document.querySelectorAll< HTMLElement >( '.nab-page-views-wrapper' )
);
nodes.forEach( ( node ) => {
const value = Number.parseInt(
node.getAttribute( 'data-value' ) || ''
);
node.textContent = isNaN( value ) ? '0' : formatNumber( value );
} );
}
function renderExperimentIconsASAP() {
renderExperimentIcons();
setTimeout( () => {
if ( 'complete' !== document.readyState ) {
renderExperimentIconsASAP();
}
}, 100 );
}
function renderExperimentIcons() {
const iconWrappers = Array.from(
document.querySelectorAll< HTMLElement >( '.js-nab-experiment__icon' )
).filter( ( wrapper ) => ! wrapper.children.length );
iconWrappers.map( ( wrapper ) => {
const typeName = wrapper.getAttribute( 'data-experiment-type' ) || '';
const type = select( NAB_EXPERIMENTS ).getExperimentType( typeName );
const Icon = ( type && type.icon ) || DefaultIcon;
const name =
( type && type.title ) ||
_x( 'Unknown', 'text (experiment type)', 'nelio-ab-testing' );
wrapper.setAttribute( 'title', name );
return render( , wrapper );
} );
}
function updateRunningExperimentStatuses() {
const quota = select( NAB_DATA ).getQuota();
if ( ! quota ) {
return;
}
const running = Array.from(
document.querySelectorAll< HTMLElement >(
'span.nab-experiment__status--running'
)
);
const classname = 'nab-experiment__status--running-with-no-quota';
running.forEach( ( status ) => {
status.classList.toggle( classname, ! quota.availableQuota );
const text = document.createElement( 'span' );
text.textContent = _x(
'Running',
'text (experiment status)',
'nelio-ab-testing'
);
status.innerHTML = '';
if ( ! quota.availableQuota ) {
const icon = document.createElement( 'span' );
icon.classList.add( 'dashicons', 'dashicons-warning' );
icon.setAttribute(
'title',
_x(
'Event tracking has stopped because there’s no more quota available at the moment',
'text',
'nelio-ab-testing'
)
);
status.appendChild( icon );
}
status.appendChild( text );
} );
}
function renderHeaderItems( settings: Settings ) {
const pageTitle = document.querySelector( '.wp-heading-inline' );
if ( ! pageTitle?.parentElement ) {
return;
}
const notices = document.createElement( 'div' );
const testButtonWrapper = document.createElement( 'span' );
const titleActionWrapper = document.createElement( 'span' );
pageTitle.parentElement.insertBefore( notices, pageTitle.nextSibling );
pageTitle.parentElement.insertBefore(
titleActionWrapper,
pageTitle.nextSibling
);
pageTitle.parentElement.insertBefore(
testButtonWrapper,
pageTitle.nextSibling
);
render( , notices );
render( , testButtonWrapper );
render(
<>
>,
titleActionWrapper
);
}
function renderQuotaMeter() {
const headerEnd = document.querySelector< HTMLElement >( '.wp-header-end' );
if ( ! headerEnd?.parentElement ) {
return;
}
const quotaWrapper = document.createElement( 'div' );
render( , quotaWrapper );
headerEnd.parentElement.insertBefore( quotaWrapper, headerEnd );
}
const isAlternativeResults = (
pair: [ string, unknown ]
): pair is [ string, CloudAlternativeResults ] => 'a' === pair[ 0 ][ 0 ];