-
Notifications
You must be signed in to change notification settings - Fork 881
Activity Log: default to Table layout and surface disabled toolbar + date picker on free tier #48418
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
Activity Log: default to Table layout and surface disabled toolbar + date picker on free tier #48418
Changes from all commits
dfc5659
222bff9
968de28
a2bfff4
943152a
d3765b7
c13f829
210732d
3803d35
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,4 @@ | ||
| Significance: patch | ||
| Type: fixed | ||
|
|
||
| Activity Log: default the page to the Table layout, load the upsell-callout stylesheet from the main entry, and surface the disabled toolbar + disabled date-range picker on the free tier with upgrade tooltips. |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -131,6 +131,55 @@ export default function ActivityLog() { | |
| return () => observer.disconnect(); | ||
| }, [] ); | ||
|
|
||
| // On free tier, neutralize DataViews' real search + filter cluster | ||
| // (the `.dataviews__search` Stack rendered by `DataViews`'s default | ||
| // UI). We let DataViews ship its own toolbar so the page tracks | ||
| // upstream changes for free, then attach: `aria-disabled` for | ||
| // assistive tech, a `title` attribute that surfaces the upgrade | ||
| // nudge as a native browser tooltip on hover, and `tabindex="-1"` | ||
| // on every focusable descendant so the cluster is unreachable via | ||
| // keyboard. Pointer-event blocking on the children is handled in | ||
| // CSS via the `[aria-disabled="true"]` rule. | ||
| // `MutationObserver` re-applies after DataViews remounts the | ||
| // toolbar / re-renders the input (e.g., on initial fetch resolution | ||
| // or layout switch) so React's render doesn't strip the attributes. | ||
| useEffect( () => { | ||
| if ( hasActivityLogsAccess ) { | ||
| return; | ||
| } | ||
|
|
||
| const wrapper = wrapperRef.current; | ||
| if ( ! wrapper ) { | ||
| return; | ||
| } | ||
|
|
||
| const tooltipText = __( 'Upgrade your plan to use this feature.', 'jetpack-activity-log' ); | ||
|
|
||
| const apply = ( root: ParentNode ) => { | ||
| const cluster = root.querySelector< HTMLElement >( '.dataviews__search' ); | ||
| if ( ! cluster ) { | ||
| return; | ||
| } | ||
|
|
||
| if ( cluster.getAttribute( 'aria-disabled' ) !== 'true' ) { | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Recommend to just set "inert" attribute in the wrapper or overlay which doesn't let clicks/interactions through. Anything else like done here is prone to be fragile and bug prone.
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
| cluster.setAttribute( 'aria-disabled', 'true' ); | ||
| cluster.setAttribute( 'title', tooltipText ); | ||
| } | ||
|
|
||
| cluster.querySelectorAll< HTMLElement >( 'input, button, [tabindex]' ).forEach( el => { | ||
| if ( el.getAttribute( 'tabindex' ) !== '-1' ) { | ||
| el.setAttribute( 'tabindex', '-1' ); | ||
| } | ||
| } ); | ||
| }; | ||
|
|
||
| apply( wrapper ); | ||
| const observer = new MutationObserver( () => apply( wrapper ) ); | ||
| observer.observe( wrapper, { subtree: true, childList: true } ); | ||
|
|
||
| return () => observer.disconnect(); | ||
| }, [ hasActivityLogsAccess ] ); | ||
|
|
||
| // Date-range defaults to "Last 7 days" anchored at the site's calendar | ||
| // today (not the browser's) — matches Calypso's `getDefaultDateRange`. | ||
| // The range is client-only state: refreshes reset to the default | ||
|
|
@@ -328,17 +377,21 @@ export default function ActivityLog() { | |
|
|
||
| // Mounting the picker as an admin-ui `actions` slot places it in the | ||
| // AdminPage header alongside the title/subtitle — matches MSD's | ||
| // layout for the logs pages. | ||
| const headerActions = hasActivityLogsAccess ? ( | ||
| // layout for the logs pages. On free tier the picker renders as a | ||
| // disabled upgrade affordance (no popover, hover tooltip), keeping | ||
| // the page surface visually consistent with the paid version. | ||
| const headerActions = ( | ||
| <DateRangePicker | ||
| start={ dateRange.start } | ||
| end={ dateRange.end } | ||
| onChange={ onChangeDateRange } | ||
| timezoneString={ timezoneString } | ||
| gmtOffset={ gmtOffset } | ||
| locale={ locale } | ||
| disabled={ ! hasActivityLogsAccess } | ||
| disabledTooltipText={ __( 'Upgrade your plan to use this feature.', 'jetpack-activity-log' ) } | ||
| /> | ||
| ) : undefined; | ||
| ); | ||
|
|
||
| return ( | ||
| <AdminPage | ||
|
|
@@ -351,54 +404,74 @@ export default function ActivityLog() { | |
| showFooter={ false } | ||
| unwrapped | ||
| > | ||
| <div ref={ wrapperRef } className="jp-activity-log__dataviews-wrapper"> | ||
| <DataViews< Activity > | ||
| data={ logData } | ||
| isLoading={ isFetching || isLoadingList } | ||
| paginationInfo={ paginationInfo } | ||
| fields={ fields as Field< Activity >[] } | ||
| view={ view } | ||
| actions={ actions } | ||
| getItemId={ getItemId } | ||
| search | ||
| // Advertise both DataViews' built-in Activity timeline | ||
| // (the default) and a Table layout. Toggle lives in | ||
| // the cog popover's layout switcher. Each layout maps | ||
| // the event parts to the right slots: | ||
| // - Activity: `event_icon` → mediaField (left | ||
| // bullet slot), `event_title` → titleField, | ||
| // `event_description` → descriptionField, plus | ||
| // `groupBy: published_date` for day headers. | ||
| // - Table: one composite `event` column alongside | ||
| // Date / User. | ||
| // See DEFAULT_LAYOUTS in ./views for the full shape — | ||
| // it explicitly nulls slot/groupBy refs on Table so a | ||
| // round-trip Activity → Table doesn't carry those | ||
| // over and double-render as a primary column. | ||
| defaultLayouts={ DEFAULT_LAYOUTS } | ||
| onChangeView={ onChangeView } | ||
| onReset={ isViewModified ? onResetView : false } | ||
| // On the free tier, lock the perPage selector to the | ||
| // capped size and hide search/filters/sort/view-config | ||
| // by replacing the default UI with just the table (same | ||
| // switches Calypso uses at logs-activity/dataviews/ | ||
| // index.tsx:201-208). | ||
| config={ | ||
| hasActivityLogsAccess | ||
| ? undefined | ||
| : { perPageSizes: [ ACTIVITY_LOGS_DEFAULT_PAGE_SIZE ] } | ||
| } | ||
| empty={ | ||
| <p> | ||
| { view.search | ||
| ? __( 'No activity found', 'jetpack-activity-log' ) | ||
| : __( 'No activities', 'jetpack-activity-log' ) } | ||
| </p> | ||
| } | ||
| > | ||
| { hasActivityLogsAccess ? undefined : <DataViews.Layout /> } | ||
| </DataViews> | ||
| { ! hasActivityLogsAccess && ! isFetching && logData.length > 0 && <UpsellCallout /> } | ||
| <div | ||
| ref={ wrapperRef } | ||
| className={ | ||
| 'jp-activity-log__dataviews-wrapper' + | ||
| ( hasActivityLogsAccess ? '' : ' jp-activity-log__dataviews-wrapper--free-tier' ) | ||
| } | ||
| > | ||
| { /* | ||
| * Single inner div soaks up `jetpack-admin-page-layout`'s | ||
| * `.admin-ui-page > :not(...):not(...) > *` rule (which | ||
| * force-applies `flex: 1 1 auto; flex-direction: column` | ||
| * to every direct child of the page's scroll column). | ||
| * With the chain landing here, DataViews and the | ||
| * free-tier UpsellCallout are grandchildren and stack | ||
| * without competing for flex space — no `!important` | ||
| * overrides needed. | ||
| */ } | ||
| <div className="jp-activity-log__inner"> | ||
| <DataViews< Activity > | ||
| data={ logData } | ||
| isLoading={ isFetching || isLoadingList } | ||
| paginationInfo={ paginationInfo } | ||
| fields={ fields as Field< Activity >[] } | ||
| view={ view } | ||
| actions={ actions } | ||
| getItemId={ getItemId } | ||
| search | ||
| // Advertise both DataViews' built-in Activity timeline | ||
| // (the default) and a Table layout. Toggle lives in | ||
| // the cog popover's layout switcher. Each layout maps | ||
| // the event parts to the right slots: | ||
| // - Activity: `event_icon` → mediaField (left | ||
| // bullet slot), `event_title` → titleField, | ||
| // `event_description` → descriptionField, plus | ||
| // `groupBy: published_date` for day headers. | ||
| // - Table: one composite `event` column alongside | ||
| // Date / User. | ||
| // See DEFAULT_LAYOUTS in ./views for the full shape — | ||
| // it explicitly nulls slot/groupBy refs on Table so a | ||
| // round-trip Activity → Table doesn't carry those | ||
| // over and double-render as a primary column. | ||
| defaultLayouts={ DEFAULT_LAYOUTS } | ||
| onChangeView={ onChangeView } | ||
| onReset={ isViewModified ? onResetView : false } | ||
| // On the free tier, lock the perPage selector to the | ||
| // capped size. DataViews keeps rendering its real | ||
| // toolbar (search + filter toggle + cog); the | ||
| // search/filter cluster is neutralized by the | ||
| // `aria-disabled` + `tabindex="-1"` overlay | ||
| // applied in the effect above — Calypso's | ||
| // equivalent switch at logs-activity/dataviews/ | ||
| // index.tsx:201-208 hides them, but we want the | ||
| // upgrade affordance to be discoverable on hover. | ||
| config={ | ||
| hasActivityLogsAccess | ||
| ? undefined | ||
| : { perPageSizes: [ ACTIVITY_LOGS_DEFAULT_PAGE_SIZE ] } | ||
| } | ||
| empty={ | ||
| <p> | ||
| { view.search | ||
| ? __( 'No activity found', 'jetpack-activity-log' ) | ||
| : __( 'No activities', 'jetpack-activity-log' ) } | ||
| </p> | ||
| } | ||
| /> | ||
| { ! hasActivityLogsAccess && ! isFetching && logData.length > 0 && <UpsellCallout /> } | ||
| </div> | ||
| </div> | ||
| </AdminPage> | ||
| ); | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,49 +1,56 @@ | ||
| // Upsell callout rendered below the Activity Log table on free plans. | ||
| // Visual intent mirrors Calypso's `ActivityLogsCallout` layout: a centered | ||
| // card with the illustration on one side, copy + CTA on the other. | ||
| // Visual: a bordered card with the copy + CTA on the left and the | ||
| // illustration on the right (column-reverse on mobile, image moves | ||
| // below the copy). | ||
| .jp-activity-log__upsell-callout { | ||
| display: flex; | ||
| flex-direction: column-reverse; | ||
| align-items: center; | ||
| flex-direction: column; | ||
| gap: 24px; | ||
| padding: 32px 24px; | ||
| margin: 32px auto 0; | ||
| margin: 24px auto 0; | ||
| max-width: 960px; | ||
| padding: 24px; | ||
| border: 1px solid var(--wpds-color-stroke-surface-neutral-weak, #dcdcde); | ||
| border-radius: 4px; | ||
| background-color: var(--wpds-color-bg-surface-neutral, #fff); | ||
|
|
||
| @media (min-width: 782px) { | ||
| flex-direction: row; | ||
| align-items: center; | ||
| gap: 48px; | ||
| padding: 40px; | ||
| gap: 32px; | ||
| padding: 32px; | ||
| } | ||
| } | ||
|
|
||
| .jp-activity-log__upsell-callout-image { | ||
| display: block; | ||
| width: 100%; | ||
| max-width: 320px; | ||
| max-width: 360px; | ||
| height: auto; | ||
| flex-shrink: 0; | ||
| align-self: center; | ||
| } | ||
|
|
||
| .jp-activity-log__upsell-callout-content { | ||
| display: flex; | ||
| flex-direction: column; | ||
| gap: 16px; | ||
| gap: 12px; | ||
| flex: 1 1 auto; | ||
| min-width: 0; | ||
| } | ||
|
|
||
| .jp-activity-log__upsell-callout-title { | ||
| margin: 0; | ||
| font-size: 24px; | ||
| font-size: 20px; | ||
| line-height: 1.3; | ||
| font-weight: 500; | ||
| color: var(--wpds-color-fg-content-neutral, #1e1e1e); | ||
| } | ||
|
|
||
| // The <Button variant="primary"> provides its own styling; we just align | ||
| // it to the start of the content column so it doesn't stretch full-width. | ||
| // it to the start of the content column so it doesn't stretch full-width | ||
| // and add a small top margin so it sits visually distinct from the copy | ||
| // above. | ||
| .jp-activity-log__upsell-callout-content .components-button { | ||
| align-self: flex-start; | ||
| margin-top: 8px; | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This selector will go away eventually and we can't rely on it as a stable API.