Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
4 changes: 2 additions & 2 deletions src/course-home/dates-tab/DatesTab.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import { useModel } from '../../generic/model-store';

import SuggestedScheduleHeader from '../suggested-schedule-messaging/SuggestedScheduleHeader';
import ShiftDatesAlert from '../suggested-schedule-messaging/ShiftDatesAlert';
import UpgradeToCompleteAlert from '../suggested-schedule-messaging/UpgradeToCompleteAlert';
import { BannerDatesUpgradeSlot } from '../../plugin-slots/BannerDatesUpgradeSlot';
import UpgradeToShiftDatesAlert from '../suggested-schedule-messaging/UpgradeToShiftDatesAlert';

const DatesTab = () => {
Expand Down Expand Up @@ -51,7 +51,7 @@ const DatesTab = () => {
<>
<ShiftDatesAlert model="dates" fetch={fetchDatesTab} />
<SuggestedScheduleHeader />
<UpgradeToCompleteAlert logUpgradeLinkClick={logUpgradeLinkClick} />
<BannerDatesUpgradeSlot courseId={courseId} logUpgradeLinkClick={logUpgradeLinkClick} />
<UpgradeToShiftDatesAlert logUpgradeLinkClick={logUpgradeLinkClick} model="dates" />
</>
)}
Expand Down
34 changes: 4 additions & 30 deletions src/course-home/dates-tab/DatesTab.test.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -171,9 +171,9 @@ describe('DatesTab', () => {
await waitFor(() => expect(screen.getByText('We’ve built a suggested schedule to help you stay on track. But don’t worry—it’s flexible so you can learn at your own pace.')).toBeInTheDocument());
});

it('renders UpgradeToCompleteAlert', async () => {
it('does not render UpgradeToCompleteAlert when contentTypeGatingEnabled is false', async () => {
datesTabData.datesBannerInfo = {
contentTypeGatingEnabled: true,
contentTypeGatingEnabled: false,
missedDeadlines: false,
missedGatedContent: false,
verifiedUpgradeLink: 'http://localhost:18130/basket/add/?sku=8CF08E5',
Expand All @@ -182,8 +182,8 @@ describe('DatesTab', () => {
axiosMock.onGet(datesUrl).reply(200, datesTabData);
render(component);

await waitFor(() => expect(screen.getByText('You are auditing this course, which means that you are unable to participate in graded assignments. To complete graded assignments as part of this course, you can upgrade today.')).toBeInTheDocument());
expect(screen.getByRole('button', { name: 'Upgrade now' })).toBeInTheDocument();
expect(screen.queryByText('You are auditing this course, which means that you are unable to participate in graded assignments. To complete graded assignments as part of this course, you can upgrade today.')).not.toBeInTheDocument();
expect(screen.queryByRole('button', { name: 'Upgrade now' })).not.toBeInTheDocument();
});

it('renders UpgradeToShiftDatesAlert', async () => {
Expand Down Expand Up @@ -254,32 +254,6 @@ describe('DatesTab', () => {
expect(screen.queryByRole('button', { name: 'Shift due dates' })).not.toBeInTheDocument();
});

it('sends analytics event onClick of upgrade button in UpgradeToCompleteAlert', async () => {
sendTrackEvent.mockClear();
datesTabData.datesBannerInfo = {
contentTypeGatingEnabled: true,
missedDeadlines: false,
missedGatedContent: false,
verifiedUpgradeLink: 'http://localhost:18130/basket/add/?sku=8CF08E5',
};

axiosMock.onGet(datesUrl).reply(200, datesTabData);
render(component);

const upgradeButton = await waitFor(() => screen.getByRole('button', { name: 'Upgrade now' }));
fireEvent.click(upgradeButton);

expect(sendTrackEvent).toHaveBeenCalledTimes(1);
expect(sendTrackEvent).toHaveBeenCalledWith('edx.bi.ecommerce.upsell_links_clicked', {
org_key: 'edX',
courserun_key: courseId,
linkCategory: 'personalized_learner_schedules',
linkName: 'dates_upgrade',
linkType: 'button',
pageName: 'dates_tab',
});
});

it('sends analytics event onClick of upgrade button in UpgradeToShiftDatesAlert', async () => {
sendTrackEvent.mockClear();
datesTabData.datesBannerInfo = {
Expand Down
63 changes: 63 additions & 0 deletions src/plugin-slots/BannerDatesUpgradeSlot/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
# Banner Dates Upgrade Slot

### Slot ID: `org.openedx.frontend.learning.banner_dates_upgrade.v1`

### Slot ID Aliases

- `dates_upgrade_banner_slot`

## Description

This slot is used for rendering upgrade messaging in the dates tab banner area.

By default, the slot renders `UpgradeToCompleteAlert`. You can disable the default and fully replace it with a custom plugin experience using `keepDefault: false`.

## Props

- `courseId` - The course ID (string)
- `logUpgradeLinkClick` - Callback used for upgrade-click analytics

## Screenshots

Default banner screenshot:

![Screenshot of the dates upgrade banner](./images/update_banner_complete_alert.png)



## Example

The following `env.config.jsx` example replaces the default banner experience with a custom plugin widget:

```js
import {
DIRECT_PLUGIN,
PLUGIN_OPERATIONS,
} from '@openedx/frontend-plugin-framework';

const config = {
pluginSlots: {
dates_upgrade_banner_slot: {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's use the full id, here.

keepDefault: false,
plugins: [
{
op: PLUGIN_OPERATIONS.Insert,
widget: {
id: 'org.openedx.frontend.learning.banner_dates_upgrade.v1',
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the widget id: it's arbitrary, and ideally it'll be different from the slot id where it's used.

type: DIRECT_PLUGIN,
priority: 50,
RenderWidget: (pluginProps) => (
<div>
Replacement Banner
<button>Upgrade here</button>
</div>
),
},
},
],
},
},
};

export default config;
```
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
25 changes: 25 additions & 0 deletions src/plugin-slots/BannerDatesUpgradeSlot/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import React from 'react';

import { PluginSlot } from '@openedx/frontend-plugin-framework';
import UpgradeToCompleteAlert from '../../course-home/suggested-schedule-messaging/UpgradeToCompleteAlert';

export const BannerDatesUpgradeSlot = ({
courseId,
logUpgradeLinkClick,
}: BannerDatesUpgradeSlotProps) => (
<PluginSlot
id="org.openedx.frontend.learning.banner_dates_upgrade.v1"
idAliases={['dates_upgrade_banner_slot']}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Aliases exist to support a deprecation window for slots that are being renamed. Since this is a new slot, it shouldn't have an alias.

pluginProps={{
courseId,
logUpgradeLinkClick,
}}
>
<UpgradeToCompleteAlert logUpgradeLinkClick={logUpgradeLinkClick} />
</PluginSlot>
);

interface BannerDatesUpgradeSlotProps {
courseId: string;
logUpgradeLinkClick: () => void;
}
1 change: 1 addition & 0 deletions src/plugin-slots/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

* [`org.openedx.frontend.layout.footer.v1`](./FooterSlot/)
* [`org.openedx.frontend.layout.header_learning.v1`](./HeaderSlot/)
* [`org.openedx.frontend.learning.banner_dates_upgrade.v1`](./BannerDatesUpgradeSlot/)
* [`org.openedx.frontend.learning.content_iframe_loader.v1`](./ContentIFrameLoaderSlot/)
* [`org.openedx.frontend.learning.course_breadcrumbs.v1`](./CourseBreadcrumbsSlot/)
* [`org.openedx.frontend.learning.course_home_section_outline.v1`](./CourseHomeSectionOutlineSlot/)
Expand Down
Loading