Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement audience selection panel #8751

Merged
merged 39 commits into from
Jun 5, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
43b4a47
Add `AudienceSelectionPanel`.
nfmohit May 22, 2024
759a006
Update `Checkbox` to support meta information.
nfmohit May 24, 2024
8dcb08e
Update `SelectionBox` to support meta information.
nfmohit May 24, 2024
b9127a4
Update `SelectionPanelItem` to support subtitle & meta information.
nfmohit May 24, 2024
6312845
Update styling to accommodate subtitle & meta information.
nfmohit May 24, 2024
d17c3e6
Update `AudienceItem` to support subtitle & meta information.
nfmohit May 24, 2024
d46f242
Update `AudienceItems` to compose subtitle and calculate user count.
nfmohit May 24, 2024
6e2a39d
Use suffix in `SelectionPanelItem` instead of changing `SelectionBox`.
nfmohit May 24, 2024
85ba3f0
Decrease padding only for audience selection panel.
nfmohit May 24, 2024
84909c2
Merge branch 'develop' into enhancement/#8157-audience-selection-panel.
nfmohit May 24, 2024
82f6ba0
Add `Footer`.
nfmohit May 24, 2024
1018efe
Update footer layout in mobile viewport.
nfmohit May 24, 2024
5c86de6
Add learn more link.
nfmohit May 24, 2024
66449f0
Update VRT references.
nfmohit May 25, 2024
aa4fad9
Fix border.
nfmohit May 25, 2024
6c58dc6
Add Storybook story.
nfmohit May 25, 2024
c9f1e44
Format user count.
nfmohit May 25, 2024
c4623cb
Restore border and outline in `SelectionBox`.
nfmohit May 26, 2024
ab04e44
Add test coverage for `AudienceSelectionPanel`.
nfmohit May 26, 2024
e356292
Add VRT references.
nfmohit May 26, 2024
063d65a
Remove unnecessary white space.
nfmohit May 26, 2024
7d7ebc0
Merge branch 'develop' into enhancement/#8157-audience-selection-panel.
nfmohit May 31, 2024
71f8e37
Fix crash when audiences are not configured.
nfmohit May 31, 2024
8d08a30
Remove incorrect reference to key metrics selector.
nfmohit May 31, 2024
acfe53f
Remove incorrect references to metrics.
nfmohit May 31, 2024
78a306c
Add `getConfigurableAudiences` selector.
nfmohit May 31, 2024
77606f4
Use `getConfigurableAudiences`.
nfmohit May 31, 2024
05500a1
Fix saved item logic.
nfmohit Jun 4, 2024
4438c8e
Imrpove conditions and filtering audiences logic.
hussain-t Jun 4, 2024
df28c0b
Merge branch 'develop' into enhancement/#8157-audience-selection-panel.
nfmohit Jun 5, 2024
78f0780
Apply BEM element-level class name.
nfmohit Jun 5, 2024
5be1183
Correctly reference `savedItemSlugs`.
nfmohit Jun 5, 2024
6eae55b
Fix failing tests.
nfmohit Jun 5, 2024
a577781
Remove unnecessary optional chaining.
nfmohit Jun 5, 2024
4eac143
Show user count even if it is zero.
nfmohit Jun 5, 2024
f7c8cf0
Fix casing for citation.
nfmohit Jun 5, 2024
2db9a16
Fix console error for story.
nfmohit Jun 5, 2024
31de4e0
Fix failing JS test.
nfmohit Jun 5, 2024
d3714dd
Update VRT references.
nfmohit Jun 5, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion assets/js/components/DashboardMainApp.js
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,10 @@ import { DAY_IN_SECONDS } from '../util';
import Header from './Header';
import DashboardSharingSettingsButton from './dashboard-sharing/DashboardSharingSettingsButton';
import WidgetContextRenderer from '../googlesitekit/widgets/components/WidgetContextRenderer';
import { AudienceSegmentationSetupCTAWidget } from '../modules/analytics-4/components/audience-segmentation/dashboard';
import {
AudienceSegmentationSetupCTAWidget,
AudienceSelectionPanel,
} from '../modules/analytics-4/components/audience-segmentation/dashboard';
import EntitySearchInput from './EntitySearchInput';
import DateRangeSelector from './DateRangeSelector';
import HelpMenu from './help/HelpMenu';
Expand Down Expand Up @@ -318,6 +321,8 @@ export default function DashboardMainApp() {

<MetricsSelectionPanel />

{ audienceSegmentationEnabled && <AudienceSelectionPanel /> }

<OfflineNotification />
</Fragment>
);
Expand Down
14 changes: 14 additions & 0 deletions assets/js/components/SelectionPanel/SelectionPanelItem.js
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@ export default function SelectionPanelItem( {
isItemSelected,
isItemDisabled,
onCheckboxChange,
subtitle,
suffix,
} ) {
return (
<div className="googlesitekit-selection-panel-item">
Expand All @@ -46,9 +48,19 @@ export default function SelectionPanelItem( {
title={ title }
value={ slug }
>
{ subtitle && (
<span className="googlesitekit-selection-panel-item__subtitle">
{ subtitle }
</span>
) }
{ description }
{ children }
</SelectionBox>
{ suffix && (
<span className="googlesitekit-selection-panel-item__suffix">
{ suffix }
</span>
) }
</div>
);
}
Expand All @@ -62,4 +74,6 @@ SelectionPanelItem.propTypes = {
isItemSelected: PropTypes.bool,
isItemDisabled: PropTypes.bool,
onCheckboxChange: PropTypes.func,
subtitle: PropTypes.string,
suffix: PropTypes.node,
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
/**
* Audience Selection Panel AudienceItem
*
* Site Kit by Google, Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

/**
* External dependencies
*/
import PropTypes from 'prop-types';

/**
* WordPress dependencies
*/
import { useCallback } from '@wordpress/element';

/**
* Internal dependencies
*/
import Data from 'googlesitekit-data';
import { AUDIENCE_SELECTED, AUDIENCE_SELECTION_FORM } from './constants';
import { CORE_FORMS } from '../../../../../../googlesitekit/datastore/forms/constants';
import { numFmt } from '../../../../../../util';
import { SelectionPanelItem } from '../../../../../../components/SelectionPanel';

const { useSelect, useDispatch } = Data;

export default function AudienceItem( {
slug,
title,
description,
subtitle,
userCount,
} ) {
const selectedItems = useSelect( ( select ) =>
select( CORE_FORMS ).getValue(
AUDIENCE_SELECTION_FORM,
AUDIENCE_SELECTED
)
);

const { setValues } = useDispatch( CORE_FORMS );

const onCheckboxChange = useCallback(
( event ) => {
setValues( AUDIENCE_SELECTION_FORM, {
[ AUDIENCE_SELECTED ]: event.target.checked
? selectedItems.concat( [ slug ] )
: selectedItems.filter(
( selectedItem ) => selectedItem !== slug
),
} );
},
[ selectedItems, setValues, slug ]
);

const isItemSelected = selectedItems?.includes( slug );

const id = `audience-selection-checkbox-${ slug }`;

return (
<SelectionPanelItem
id={ id }
slug={ slug }
title={ title }
subtitle={ subtitle }
description={ description }
isItemSelected={ isItemSelected }
onCheckboxChange={ onCheckboxChange }
suffix={ numFmt( userCount ) }
/>
);
}

AudienceItem.propTypes = {
slug: PropTypes.string.isRequired,
title: PropTypes.string.isRequired,
description: PropTypes.string.isRequired,
subtitle: PropTypes.string.isRequired,
userCount: PropTypes.number.isRequired,
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
/**
* Audience Selection Panel Audience Items
*
* Site Kit by Google, Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

/**
* External dependencies
*/
import PropTypes from 'prop-types';

/**
* WordPress dependencies
*/
import { __ } from '@wordpress/i18n';

/**
* External dependencies
*/
import Data from 'googlesitekit-data';
import { CORE_USER } from '../../../../../../googlesitekit/datastore/user/constants';
import {
DATE_RANGE_OFFSET,
MODULES_ANALYTICS_4,
} from '../../../../datastore/constants';
import AudienceItem from './AudienceItem';
import { SelectionPanelItems } from '../../../../../../components/SelectionPanel';

const { useSelect } = Data;

export default function AudienceItems( { savedItemSlugs = [] } ) {
const availableAudiences = useSelect( ( select ) => {
const { getConfigurableAudiences, getReport } =
select( MODULES_ANALYTICS_4 );

const audiences = getConfigurableAudiences();

if ( undefined === audiences ) {
return undefined;
}

if ( ! audiences.length ) {
return [];
}

const dateRangeDates = select( CORE_USER ).getDateRangeDates( {
offsetDays: DATE_RANGE_OFFSET,
} );

// Get the user count for the available audiences.
const report = getReport( {
...dateRangeDates,
metrics: [
{
name: 'totalUsers',
},
],
dimensions: [ { name: 'audienceResourceName' } ],
dimensionFilters: {
audienceResourceName: audiences.map( ( { name } ) => name ),
},
} );

const { rows = [] } = report || {};

return audiences.map( ( audience ) => {
const rowIndex = rows.findIndex(
( row ) => row?.dimensionValues?.[ 0 ]?.value === audience.name
);

return {
...audience,
userCount:
Number( rows[ rowIndex ]?.metricValues?.[ 0 ]?.value ) || 0,
};
} );
} );

const audiencesListReducer = (
acc,
{ audienceType, description, displayName, name, userCount }
) => {
let citation = '';

switch ( audienceType ) {
case 'DEFAULT_AUDIENCE':
citation = __(
'Created by default by Google Analytics',
'google-site-kit'
);
description = '';
break;
case 'SITE_KIT_AUDIENCE':
citation = __( 'Created by Site Kit', 'google-site-kit' );
break;
case 'USER_AUDIENCE':
citation = __(
'Already exists in your Analytics property',
'google-site-kit'
);
break;
}

return {
...acc,
[ name ]: {
title: displayName,
subtitle: description,
description: citation,
userCount,
},
};
};

const availableSavedItems = availableAudiences
?.filter( ( { name } ) => savedItemSlugs.includes( name ) )
.reduce( audiencesListReducer, {} );

const availableUnsavedItems = availableAudiences
?.filter( ( { name } ) => ! savedItemSlugs.includes( name ) )
.reduce( audiencesListReducer, {} );

return (
<SelectionPanelItems
availableItemsTitle={ __( 'Additional groups', 'google-site-kit' ) }
availableSavedItems={ availableSavedItems }
availableUnsavedItems={ availableUnsavedItems }
ItemComponent={ AudienceItem }
savedItemSlugs={ savedItemSlugs }
/>
);
}

AudienceItems.propTypes = {
savedItemSlugs: PropTypes.array,
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
/**
* Audience Selection Panel Footer
*
* Site Kit by Google, Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

/**
* External dependencies
*/
import PropTypes from 'prop-types';

/**
* WordPress dependencies
*/
import { __, _n, sprintf } from '@wordpress/i18n';

/**
* Internal dependencies
*/
import Data from 'googlesitekit-data';
import {
AUDIENCE_SELECTED,
AUDIENCE_SELECTION_FORM,
MAX_SELECTED_AUDIENCES_COUNT,
MIN_SELECTED_AUDIENCES_COUNT,
} from './constants';
import { CORE_FORMS } from '../../../../../../googlesitekit/datastore/forms/constants';
import { SelectionPanelFooter } from '../../../../../../components/SelectionPanel';

const { useSelect } = Data;

export default function Footer( { isOpen, closePanel, savedItemSlugs } ) {
const selectedItems = useSelect( ( select ) =>
select( CORE_FORMS ).getValue(
AUDIENCE_SELECTION_FORM,
AUDIENCE_SELECTED
)
);

const selectedItemsCount = selectedItems?.length || 0;
let itemLimitError;

if ( selectedItemsCount < MIN_SELECTED_AUDIENCES_COUNT ) {
itemLimitError = sprintf(
/* translators: 1: Minimum number of groups that can be selected. 2: Number of selected groups. */
_n(
'Select at least %1$d group (%2$d selected)',
'Select at least %1$d groups (%2$d selected)',
MIN_SELECTED_AUDIENCES_COUNT,
'google-site-kit'
),
MIN_SELECTED_AUDIENCES_COUNT,
selectedItemsCount
);
} else if ( selectedItemsCount > MAX_SELECTED_AUDIENCES_COUNT ) {
itemLimitError = sprintf(
/* translators: 1: Maximum number of groups that can be selected. 2: Number of selected groups. */
__( 'Select up to %1$d groups (%2$d selected)', 'google-site-kit' ),
MAX_SELECTED_AUDIENCES_COUNT,
selectedItemsCount
);
}

return (
<SelectionPanelFooter
savedItemSlugs={ savedItemSlugs }
selectedItemSlugs={ selectedItems }
saveSettings={ () => {} }
saveError={ null }
itemLimitError={ itemLimitError }
minSelectedItemCount={ MIN_SELECTED_AUDIENCES_COUNT }
maxSelectedItemCount={ MAX_SELECTED_AUDIENCES_COUNT }
isBusy={ false }
onSaveSuccess={ () => {} }
onCancel={ () => {} }
isOpen={ isOpen }
closePanel={ closePanel }
/>
);
}

Footer.propTypes = {
isOpen: PropTypes.bool,
closePanel: PropTypes.func.isRequired,
savedItemSlugs: PropTypes.array,
};
Loading
Loading