Skip to content

Commit

Permalink
refactor: useUpdateLibraryMetadata with optimistic update
Browse files Browse the repository at this point in the history
  • Loading branch information
ChrisChV committed Aug 13, 2024
2 parents 2ad5b1f + 8285f8e commit 21c7e61
Show file tree
Hide file tree
Showing 22 changed files with 364 additions and 182 deletions.
1 change: 0 additions & 1 deletion .env
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,6 @@ HOTJAR_APP_ID=''
HOTJAR_VERSION=6
HOTJAR_DEBUG=false
INVITE_STUDENTS_EMAIL_TO=''
AI_TRANSLATIONS_BASE_URL=''
ENABLE_HOME_PAGE_COURSE_API_V2=false
ENABLE_CHECKLIST_QUALITY=''
ENABLE_GRADING_METHOD_IN_PROBLEMS=false
Expand Down
1 change: 0 additions & 1 deletion .env.development
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,6 @@ HOTJAR_APP_ID=''
HOTJAR_VERSION=6
HOTJAR_DEBUG=true
INVITE_STUDENTS_EMAIL_TO="[email protected]"
AI_TRANSLATIONS_BASE_URL='http://localhost:18760'
ENABLE_HOME_PAGE_COURSE_API_V2=false
ENABLE_CHECKLIST_QUALITY=true
ENABLE_GRADING_METHOD_IN_PROBLEMS=false
Expand Down
84 changes: 4 additions & 80 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 0 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,6 @@
"@dnd-kit/sortable": "^8.0.0",
"@dnd-kit/utilities": "^3.2.2",
"@edx/brand": "npm:@openedx/brand-openedx@^1.2.3",
"@edx/frontend-component-ai-translations": "^2.1.0",
"@edx/frontend-component-footer": "^14.0.3",
"@edx/frontend-component-header": "^5.3.3",
"@edx/frontend-enterprise-hotjar": "^2.0.0",
Expand Down
4 changes: 2 additions & 2 deletions src/files-and-videos/videos-page/data/thunks.js
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ export function cancelAllUploads(courseId, uploadData) {
control.abort();
});
Object.entries(uploadData).forEach(([key, value]) => {
if (value.status === RequestStatus.PENDING) {
if (value.status === RequestStatus.IN_PROGRESS) {
updateVideoUploadStatus(
courseId,
key,
Expand Down Expand Up @@ -324,7 +324,7 @@ export function addVideoFile(

if (uploadUrl && edxVideoId) {
uploadingIdsRef.current.uploadData = newUploadData({
status: RequestStatus.PENDING,
status: RequestStatus.IN_PROGRESS,
currentData: uploadingIdsRef.current.uploadData,
originalValue: { name, progress },
key: `video_${idx}`,
Expand Down
2 changes: 1 addition & 1 deletion src/files-and-videos/videos-page/messages.js
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ const messages = defineMessages({
},
videoUploadTrackerAlertEditMessage: {
id: 'course-authoring.files-and-videos.video-upload-tracker-alert.edit.message',
defaultMessage: 'Want to coninue editing in Studio during this upload?',
defaultMessage: 'Want to continue editing in Studio during this upload?',
description: 'Continue editing message for the Upload Tracker Alert',
},
videoUploadTrackerAlertEditHyperlinkLabel: {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import {
TransitionReplace,
} from '@openedx/paragon';
import { ChevronLeft, ChevronRight, Close } from '@openedx/paragon/icons';
import AITranslationsComponent from '@edx/frontend-component-ai-translations';
import { PluginSlot } from '@openedx/frontend-plugin-framework';
import OrderTranscriptForm from './OrderTranscriptForm';
import messages from './messages';
import {
Expand Down Expand Up @@ -114,18 +114,19 @@ const TranscriptSettings = ({
</TransitionReplace>
</>
)}
{(!transcriptType && isAiTranslationsEnabled) && (
<TransitionReplace>
<div data-testid="ai-translations-component">
<AITranslationsComponent
setIsAiTranslations={setIsAiTranslations}
closeTranscriptSettings={closeTranscriptSettings}
courseId={courseId}
key="ai-component"
/>
</div>
</TransitionReplace>
)}
<TransitionReplace>
<div data-testid="translations-component">
<PluginSlot
id="additonal_translations_component_slot"
pluginProps={{
setIsAiTranslations,
closeTranscriptSettings,
courseId,
additionalProps: { transcriptType, isAiTranslationsEnabled },
}}
/>
</div>
</TransitionReplace>
</div>
</Sheet>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -616,36 +616,7 @@ describe('TranscriptSettings', () => {
});
});

describe('Ai translations component fails', () => {
beforeEach(async () => {
initializeMockApp({
authenticatedUser: {
userId: 3,
username: 'abc123',
administrator: false,
roles: [],
},
});
store = initializeStore({
...initialState,
videos: {
...initialState.videos,
pageSettings: {
...initialState.videos.pageSettings,
},
},
});
axiosMock = new MockAdapter(getAuthenticatedHttpClient());

renderComponent(defaultProps);
});

it('doesn\'t display AI translations component if not enabled', () => {
expect(screen.queryByTestId('ai-translations-component')).not.toBeInTheDocument();
});
});

describe('Ai translations component success', () => {
describe('Translations component success', () => {
beforeEach(async () => {
initializeMockApp({
authenticatedUser: {
Expand All @@ -671,7 +642,7 @@ describe('TranscriptSettings', () => {
});

it('displays AI translations component if enabled', () => {
const component = screen.getByTestId('ai-translations-component');
const component = screen.getByTestId('translations-component');
expect(component).toBeInTheDocument();
});
});
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,22 @@
import React from 'react';
import PropTypes from 'prop-types';
import { ProgressBar, Stack, Truncate } from '@openedx/paragon';
import { Stack, Truncate } from '@openedx/paragon';
import UploadStatusIcon from './UploadStatusIcon';
import { RequestStatus } from '../../../data/constants';

const getVideoStatus = (status) => {
switch (status) {
case RequestStatus.IN_PROGRESS:
return 'UPLOADING';
case RequestStatus.PENDING:
return 'QUEUED';
case RequestStatus.SUCCESSFUL:
return '';
default:
return status.toUpperCase();
}
};

const UploadProgressList = ({ videosList }) => (
<div role="list" className="text-primary-500">
{videosList.map(([id, video], index) => {
Expand All @@ -17,13 +30,9 @@ const UploadProgressList = ({ videosList }) => (
</Truncate>
</div>
<div className="col-6 p-0">
{video.status === RequestStatus.FAILED ? (
<span className="row m-0 justify-content-end font-weight-bold">
{video.status.toUpperCase()}
</span>
) : (
<ProgressBar now={video.progress} variant="info" />
)}
<span className="row m-0 justify-content-end font-weight-bold">
{getVideoStatus(video.status)}
</span>
</div>
<UploadStatusIcon status={video.status} />
</Stack>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import React from 'react';
import PropTypes from 'prop-types';
import { Icon } from '@openedx/paragon';
import { Icon, Spinner } from '@openedx/paragon';
import { Check, ErrorOutline } from '@openedx/paragon/icons';
import { RequestStatus } from '../../../data/constants';

Expand All @@ -10,6 +10,14 @@ const UploadStatusIcon = ({ status }) => {
return (<Icon src={Check} />);
case RequestStatus.FAILED:
return (<Icon src={ErrorOutline} />);
case RequestStatus.IN_PROGRESS:
return (
<Spinner
animation="border"
size="sm"
screenReaderText="Loading"
/>
);
default:
return (<div style={{ width: '24px' }} />);
}
Expand Down
13 changes: 10 additions & 3 deletions src/library-authoring/EmptyStates.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import React, { useContext } from 'react';
import { useParams } from 'react-router';
import { FormattedMessage } from '@edx/frontend-platform/i18n';
import {
Button, Stack,
Expand All @@ -7,16 +8,22 @@ import { Add } from '@openedx/paragon/icons';
import { ClearFiltersButton } from '../search-manager';
import messages from './messages';
import { LibraryContext } from './common/context';
import { useContentLibrary } from './data/apiHooks';

export const NoComponents = () => {
const { openAddContentSidebar } = useContext(LibraryContext);
const { libraryId } = useParams();
const { data: libraryData } = useContentLibrary(libraryId);
const canEditLibrary = libraryData?.canEditLibrary ?? false;

return (
<Stack direction="horizontal" gap={3} className="mt-6 justify-content-center">
<FormattedMessage {...messages.noComponents} />
<Button iconBefore={Add} onClick={() => openAddContentSidebar()}>
<FormattedMessage {...messages.addComponent} />
</Button>
{canEditLibrary && (
<Button iconBefore={Add} onClick={() => openAddContentSidebar()}>
<FormattedMessage {...messages.addComponent} />
</Button>
)}
</Stack>
);
};
Expand Down
32 changes: 32 additions & 0 deletions src/library-authoring/LibraryAuthoringPage.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -240,6 +240,23 @@ describe('<LibraryAuthoringPage />', () => {
expect(getByText('You have not added any content to this library yet.')).toBeInTheDocument();
});

it('show library without components without permission', async () => {
const data = {
...libraryData,
canEditLibrary: false,
};
mockUseParams.mockReturnValue({ libraryId: libraryData.id });
axiosMock.onGet(getContentLibraryApiUrl(libraryData.id)).reply(200, data);
fetchMock.post(searchEndpoint, returnEmptyResult, { overwriteRoutes: true });

render(<RootWrapper />);

expect(await screen.findByText('Content library')).toBeInTheDocument();

expect(screen.getByText('You have not added any content to this library yet.')).toBeInTheDocument();
expect(screen.queryByRole('button', { name: /add component/i })).not.toBeInTheDocument();
});

it('show new content button', async () => {
mockUseParams.mockReturnValue({ libraryId: libraryData.id });
axiosMock.onGet(getContentLibraryApiUrl(libraryData.id)).reply(200, libraryData);
Expand All @@ -250,6 +267,21 @@ describe('<LibraryAuthoringPage />', () => {
expect(screen.getByRole('button', { name: /new/i })).toBeInTheDocument();
});

it('read only state of library', async () => {
const data = {
...libraryData,
canEditLibrary: false,
};
mockUseParams.mockReturnValue({ libraryId: libraryData.id });
axiosMock.onGet(getContentLibraryApiUrl(libraryData.id)).reply(200, data);

render(<RootWrapper />);
expect(await screen.findByRole('heading')).toBeInTheDocument();
expect(screen.queryByRole('button', { name: /new/i })).not.toBeInTheDocument();

expect(screen.getByText('Read Only')).toBeInTheDocument();
});

it('show library without search results', async () => {
mockUseParams.mockReturnValue({ libraryId: libraryData.id });
axiosMock.onGet(getContentLibraryApiUrl(libraryData.id)).reply(200, libraryData);
Expand Down
Loading

0 comments on commit 21c7e61

Please sign in to comment.