Skip to content

feat: refactor program dashboard to use react query#827

Open
asharma12-sonata wants to merge 72 commits into
openedx:masterfrom
edx:abhishek/refactor-program-dashboard
Open

feat: refactor program dashboard to use react query#827
asharma12-sonata wants to merge 72 commits into
openedx:masterfrom
edx:abhishek/refactor-program-dashboard

Conversation

@asharma12-sonata
Copy link
Copy Markdown

@asharma12-sonata asharma12-sonata commented Apr 10, 2026

This PR includes changes from

#751

This PR adds the programs dashboard in accordance with the above proposal. This is a conversion of the legacy programs dashboard that lives in edx-platform. This PR converts the legacy frontend into a React based frontend that lives under its own route. The route is conditionally rendered based on a new ENABLE_PROGRAM_DASHBOARD environment variable, not to be confused with the ENABLE_PROGRAMS variable, which only handles the rendering of the "Programs" tab. This is done so that operators can choose to either use the legacy frontend or the new React-based frontend.

In order to create a new route in this MFE, the App.jsx file had to be refactored. The LearnerDashboardHeader and FooterSlot were moved out of App.jsx and into index.jsx. This aligns with the way other MFEs are setup. The h1 tag for the app was also moved to the LearnerDashboardHeader so that it would appear on all routes. The Header logic has also been refactored so that the correct tab is highlighted based on the pathname of the page.

All other changes are related to the Program Dashboard itself. The dashboard uses the /api/dashboard/v0/programs/ endpoint to retrieve enrollment data.

Directory structure

src/
└── containers/
    └── ProgramDashboard/
        ├── data/
        │   └── types.d.ts
        └── ProgramsList/
            ├── ExploreProgramsCTA.test.tsx
            ├── ExploreProgramsCTA.tsx
            ├── index.scss
            ├── index.test.tsx
            ├── index.tsx
            ├── messages.ts
            ├── ProgramListCard.test.tsx
            ├── ProgramListCard.tsx
            ├── ProgressCategoryBubbles.test.tsx
            └── ProgressCategoryBubbles.tsx

Other changes are related to using react query for program dashboard.

Programs Dashboard

image image image

deborahgu and others added 30 commits October 15, 2025 18:56
our github actions should point to our release branch
)

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Maxwell Frank <92897870+MaxFrank13@users.noreply.github.com>
)

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
)

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
…#783)

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
…edx#784)

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
@arbrandes arbrandes self-requested a review April 24, 2026 17:45
@arbrandes
Copy link
Copy Markdown
Contributor

Alright, will do, thanks!

Looks like there's some commit titles that might need fixing up in the meantime.

Copy link
Copy Markdown
Contributor

@arbrandes arbrandes left a comment

Choose a reason for hiding this comment

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

Looks great! I do have some minor suggestions inline, though.

Comment thread src/index.jsx
<LearnerDashboardHeader />
<Routes>
<Route path="/" element={<PageWrap><App /></PageWrap>} />
{getConfig().ENABLE_PROGRAM_DASHBOARD && (
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.

Can you please add the new variable to src/config/index.js? It's also common practice to add it to the .env files, in the way of documentation.

And while we're at it, mind adding some documentation about this new feature to the README?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Variable added.

Comment thread src/containers/LearnerDashboardHeader/index.jsx
isLoading,
isError: errorState,
error,
} = useProgramsListData() as {
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.

If you cast the return value of the hook, you're second-guessing Typescript. If we want a strong type for the data (which we do!) the typing should happen in the hook itself.

Parameterize useQuery in src/data/hooks/queryHooks.ts:37-42 with useQuery<ProgramData[]>({...}) (and give fetchProgramsListData a return type in api.ts). Then the consumer can destructure without an as cast and the runtime guards still make sense

Copy link
Copy Markdown
Author

@asharma12-sonata asharma12-sonata Apr 29, 2026

Choose a reason for hiding this comment

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

@arbrandes Implementing this change as suggested would move us away from the current pattern. I couldn’t find any similar implementations in the referenced files—please let me know if I missed something.

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.

There was no pattern in Learner Dashboard. It didn't use Typescript. We're setting the pattern with this, which makes it doubly important that we do it right. Please try doing it as I suggested.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

@asharma12-sonata I'm happy to assist with this 👍

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

@arbrandes i have made these required changes, please have a look and let me know if there is anything else needed.
CC: @MaxFrank13

import { logError } from '@edx/frontend-platform/logging';

import appMessages from 'messages';
import { useProgramsListData } from '../../../data/hooks/queryHooks';
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.

Probably best to follow the useInitializeLearnerHome pattern: expose useProgramsListData from the index barrel, and import instead from ../../../data/hooks.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Updated

Comment thread src/index.jsx Outdated
{/* {getConfig().ENABLE_PROGRAM_DASHBOARD && ( */}
<Route path="programs" element={<PageWrap><ProgramsList /></PageWrap>} />
)}
{/* )} */}
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

We want to keep the check here. These lines should not be commented out anymore.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

It was pushed by mistake, corrected now.

import urls from 'data/services/lms/urls';
import { stringifyUrl } from 'data/services/lms/utils';
import eventNames from 'tracking/constants';
import { ProgramData } from '../../types';
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

nit: Should be ProgramsData to match what is in the types file

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Naming corrected to match.

@arbrandes arbrandes self-requested a review May 7, 2026 21:37
Copy link
Copy Markdown
Contributor

@arbrandes arbrandes left a comment

Choose a reason for hiding this comment

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

I have a few change requests inline, but the major one that I just noticed now is the following:

The masquerade bar renders on /programs too. But masquerade is parameter-based: useInitializeLearnerHome passes the masqueraded user into initializeList(masqueradeUser) and re-keys its query on it. The new programs path does neither. So, basically, masquerading doesn't work for programs.

If it's not supposed to work, the question is: should the masquerade bar show?

If it is supposed to work, then this needs to be addressed.

Comment on lines +4 to +8
programDashboardPageTitle: {
defaultMessage: 'Program Dashboard',
id: 'program.dashboard.page.title',
description: 'Page title for Program Dashboard',
},
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 unused. Please remove.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Removed

Comment thread src/data/types/index.ts Outdated
isLoading: boolean;
isError: boolean;
error: Error | null;
}
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.

It seems nothing is using this export (or anything from ProgramDashboard/data/types). Either consume this from useProgramsListData, or remove the file.

Copy link
Copy Markdown
Author

@asharma12-sonata asharma12-sonata May 11, 2026

Choose a reason for hiding this comment

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

updated this file to have types from containers and utilized same from here.

Comment thread src/data/hooks/queryHooks.test.tsx Outdated
Comment on lines +15 to +29
const mockGet = jest.fn(() => ({
data: {},
}));
const mockLMSBaseUrl = 'http://test-lms-base-url';

jest.mock('@edx/frontend-platform/auth', () => ({
getAuthenticatedHttpClient: jest.fn(() => ({
get: mockGet,
})),
}));
jest.mock('@edx/frontend-platform', () => ({
getConfig: jest.fn(() => ({
LMS_BASE_URL: mockLMSBaseUrl,
})),
}));
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.

I suspect none of this scaffolding is necessary in this file.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Removed

uuid: string,
title: string,
type: string,
banner_image: {
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.

Please use camel case:

Suggested change
banner_image: {
bannerImage: {

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Updated

Comment thread src/data/services/lms/api.ts Outdated
import { stringifyUrl } from 'data/services/lms/utils';
import eventNames from 'tracking/constants';

import { ProgramData } from '../../../containers/ProgramDashboard/data/types';
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.

A service file should not be importing from a container/UX file. Please move that type to src/data/types.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Updated

Comment thread src/data/hooks/queryHooks.ts Outdated
} from 'data/services/lms/api';
import { learnerDashboardQueryKeys } from './queryKeys';
import { fetchProgramsListData } from '../services/lms/api';
import { ProgramData } from '../../containers/ProgramDashboard/data/types';
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.

Same here: a data file should not be importing from a container.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Updated

Comment thread src/data/services/lms/api.ts Outdated
};

const fetchProgramsListData = async (): Promise<ProgramData[]> => {
const url = `${getConfig().LMS_BASE_URL}/api/dashboard/v0/programs/`;
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.

Please route the URL through data/services/lms/urls.js, like all the others.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Updated

};

const getOrgImageUrl = (): string => {
// Otherwise use the logoImageUrl and key for the organization
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.

Stale comment. Please remove.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Updated

{hasEnrollments ? (
formatMessage(messages.exploreProgramsCTAText)
) : (
<h2 className="text-center">
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.

I don't think an <h2> is the right semantic choice here. Probably just a <p>.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Updated to p

Comment thread src/index.jsx Outdated
<Routes>
<Route path="/" element={<PageWrap><App /></PageWrap>} />
{getConfig().ENABLE_PROGRAM_DASHBOARD && (
<Route path="programs" element={<PageWrap><ProgramsList /></PageWrap>} />
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.

What you have here works, but this is more explicitly correct:

Suggested change
<Route path="programs" element={<PageWrap><ProgramsList /></PageWrap>} />
<Route path="/programs" element={<PageWrap><ProgramsList /></PageWrap>} />

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Updated

@arbrandes arbrandes self-requested a review May 11, 2026 12:16
@arbrandes
Copy link
Copy Markdown
Contributor

@MaxFrank13 and @asharma12-sonata, what's the intended behavior of the masquerade bar, for programs?

@MaxFrank13
Copy link
Copy Markdown
Member

masquerade

Hey @arbrandes! Apologies for not addressing this when you originally made the request. We discussed this the other day and were thinking that we would move the MasqueradeBar so that it only appears for the Courses view. Most likely moving it to a file in the Dashboard directory — index or perhaps DashboardLayout. Since the masquerade feature doesn't exist in the legacy Program Dashboard, it seemed to make sense that this PR wouldn't bring that feature in.

Any thoughts on this? Happy to reconsider if needed 👍

@arbrandes
Copy link
Copy Markdown
Contributor

the masquerade feature doesn't exist in the legacy Program Dashboard

Ok, that answers the fundamental question. As for what to do:

move the MasqueradeBar so that it only appears for the Courses view

This makes sense to me.

@asharma12-sonata
Copy link
Copy Markdown
Author

asharma12-sonata commented May 18, 2026

the masquerade feature doesn't exist in the legacy Program Dashboard

Ok, that answers the fundamental question. As for what to do:

move the MasqueradeBar so that it only appears for the Courses view

This makes sense to me.

Updated masquerade bar to appear for programs only.
@arbrandes @MaxFrank13

@asharma12-sonata asharma12-sonata force-pushed the abhishek/refactor-program-dashboard branch from 5e18827 to a674cc2 Compare May 21, 2026 09:27
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

9 participants