feat: refactor program dashboard to use react query#827
feat: refactor program dashboard to use react query#827asharma12-sonata wants to merge 72 commits into
Conversation
our github actions should point to our release branch
chore: updating the workflows
…extra repositories (openedx#752)
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>
…#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>
|
Alright, will do, thanks! Looks like there's some commit titles that might need fixing up in the meantime. |
arbrandes
left a comment
There was a problem hiding this comment.
Looks great! I do have some minor suggestions inline, though.
| <LearnerDashboardHeader /> | ||
| <Routes> | ||
| <Route path="/" element={<PageWrap><App /></PageWrap>} /> | ||
| {getConfig().ENABLE_PROGRAM_DASHBOARD && ( |
There was a problem hiding this comment.
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?
| isLoading, | ||
| isError: errorState, | ||
| error, | ||
| } = useProgramsListData() as { |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
@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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
@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'; |
There was a problem hiding this comment.
Probably best to follow the useInitializeLearnerHome pattern: expose useProgramsListData from the index barrel, and import instead from ../../../data/hooks.
| {/* {getConfig().ENABLE_PROGRAM_DASHBOARD && ( */} | ||
| <Route path="programs" element={<PageWrap><ProgramsList /></PageWrap>} /> | ||
| )} | ||
| {/* )} */} |
There was a problem hiding this comment.
We want to keep the check here. These lines should not be commented out anymore.
There was a problem hiding this comment.
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'; |
There was a problem hiding this comment.
nit: Should be ProgramsData to match what is in the types file
There was a problem hiding this comment.
Naming corrected to match.
There was a problem hiding this comment.
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.
| programDashboardPageTitle: { | ||
| defaultMessage: 'Program Dashboard', | ||
| id: 'program.dashboard.page.title', | ||
| description: 'Page title for Program Dashboard', | ||
| }, |
There was a problem hiding this comment.
This is unused. Please remove.
| isLoading: boolean; | ||
| isError: boolean; | ||
| error: Error | null; | ||
| } |
There was a problem hiding this comment.
It seems nothing is using this export (or anything from ProgramDashboard/data/types). Either consume this from useProgramsListData, or remove the file.
There was a problem hiding this comment.
updated this file to have types from containers and utilized same from here.
| 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, | ||
| })), | ||
| })); |
There was a problem hiding this comment.
I suspect none of this scaffolding is necessary in this file.
| uuid: string, | ||
| title: string, | ||
| type: string, | ||
| banner_image: { |
There was a problem hiding this comment.
Please use camel case:
| banner_image: { | |
| bannerImage: { |
| import { stringifyUrl } from 'data/services/lms/utils'; | ||
| import eventNames from 'tracking/constants'; | ||
|
|
||
| import { ProgramData } from '../../../containers/ProgramDashboard/data/types'; |
There was a problem hiding this comment.
A service file should not be importing from a container/UX file. Please move that type to src/data/types.
| } from 'data/services/lms/api'; | ||
| import { learnerDashboardQueryKeys } from './queryKeys'; | ||
| import { fetchProgramsListData } from '../services/lms/api'; | ||
| import { ProgramData } from '../../containers/ProgramDashboard/data/types'; |
There was a problem hiding this comment.
Same here: a data file should not be importing from a container.
| }; | ||
|
|
||
| const fetchProgramsListData = async (): Promise<ProgramData[]> => { | ||
| const url = `${getConfig().LMS_BASE_URL}/api/dashboard/v0/programs/`; |
There was a problem hiding this comment.
Please route the URL through data/services/lms/urls.js, like all the others.
| }; | ||
|
|
||
| const getOrgImageUrl = (): string => { | ||
| // Otherwise use the logoImageUrl and key for the organization |
There was a problem hiding this comment.
Stale comment. Please remove.
| {hasEnrollments ? ( | ||
| formatMessage(messages.exploreProgramsCTAText) | ||
| ) : ( | ||
| <h2 className="text-center"> |
There was a problem hiding this comment.
I don't think an <h2> is the right semantic choice here. Probably just a <p>.
| <Routes> | ||
| <Route path="/" element={<PageWrap><App /></PageWrap>} /> | ||
| {getConfig().ENABLE_PROGRAM_DASHBOARD && ( | ||
| <Route path="programs" element={<PageWrap><ProgramsList /></PageWrap>} /> |
There was a problem hiding this comment.
What you have here works, but this is more explicitly correct:
| <Route path="programs" element={<PageWrap><ProgramsList /></PageWrap>} /> | |
| <Route path="/programs" element={<PageWrap><ProgramsList /></PageWrap>} /> |
|
@MaxFrank13 and @asharma12-sonata, what's the intended behavior of the masquerade bar, for programs? |
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 Any thoughts on this? Happy to reconsider if needed 👍 |
Ok, that answers the fundamental question. As for what to do:
This makes sense to me. |
Updated masquerade bar to appear for programs only. |
Co-Authored-By: Claude <noreply@anthropic.com>
5e18827 to
a674cc2
Compare
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
Other changes are related to using react query for program dashboard.
Programs Dashboard