Skip to content
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

feat: add component Details sidebar [FC-0062] #1303

Merged
Show file tree
Hide file tree
Changes from 3 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
28 changes: 28 additions & 0 deletions src/library-authoring/component-info/ComponentDetails.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import {
initializeMocks,
render,
screen,
} from '../../testUtils';
import { mockLibraryBlockMetadata } from '../data/api.mocks';
import ComponentDetails from './ComponentDetails';

describe('<ComponentDetails />', () => {
it('should render the component usage', async () => {
initializeMocks();
mockLibraryBlockMetadata.applyMock();
render(<ComponentDetails usageKey={mockLibraryBlockMetadata.usageKeyNeverPublished} />);
expect(await screen.findByText('Component Usage')).toBeInTheDocument();
// TODO: replace with actual data when implement tag list
expect(screen.queryByText('This will show the courses that use this component.')).toBeInTheDocument();
});

it('should render the component history', async () => {
initializeMocks();
mockLibraryBlockMetadata.applyMock();
render(<ComponentDetails usageKey={mockLibraryBlockMetadata.usageKeyNeverPublished} />);
// Show created date
expect(await screen.findByText('June 20, 2024')).toBeInTheDocument();
// Show modified date
expect(await screen.findByText('June 21, 2024')).toBeInTheDocument();
});
});
47 changes: 47 additions & 0 deletions src/library-authoring/component-info/ComponentDetails.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import { useIntl } from '@edx/frontend-platform/i18n';
import { Stack } from '@openedx/paragon';

import { useLibraryBlockMetadata } from '../data/apiHooks';
import HistoryWidget from '../generic/history-widget';
import { ComponentDeveloperInfo } from './ComponentDeveloperInfo';
import messages from './messages';

interface ComponentDetailsProps {
usageKey: string;
}

const ComponentDetails = ({ usageKey }: ComponentDetailsProps) => {
const intl = useIntl();
const { data: componentMetadata } = useLibraryBlockMetadata(usageKey);

// istanbul ignore if: this should never happen
if (!componentMetadata) {
return null;
Copy link
Contributor

Choose a reason for hiding this comment

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

I think we can throw an error instead of null. (ex.)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Thank you @ChrisChV!
I added a better error handling here (a9a7b21), adding a loading and error state, as this could have an error from api.

}

return (
<Stack gap={3}>
<div>
<h3 className="h5">
{intl.formatMessage(messages.detailsTabUsageTitle)}
</h3>
<small>This will show the courses that use this component.</small>
</div>
<hr className="w-100" />
<div>
<h3 className="h5">
{intl.formatMessage(messages.detailsTabHistoryTitle)}
</h3>
<HistoryWidget
{...componentMetadata}
/>
</div>
{
// istanbul ignore next: this is only shown in development
(process.env.NODE_ENV === 'development' ? <ComponentDeveloperInfo usageKey={usageKey} /> : null)
}
</Stack>
);
};

export default ComponentDetails;
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ export const ComponentDeveloperInfo: React.FC<Props> = ({ usageKey }) => {
const { data: olx, isLoading: isOLXLoading } = useXBlockOLX(usageKey);
return (
<>
<hr />
<hr className="w-100" />
<h3 className="h5">Developer Component Details</h3>
<p><small>(This panel is only visible in development builds.)</small></p>
<dl>
Expand Down
8 changes: 2 additions & 6 deletions src/library-authoring/component-info/ComponentInfo.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import { Link } from 'react-router-dom';

import { getEditUrl } from '../components/utils';
import { ComponentMenu } from '../components';
import { ComponentDeveloperInfo } from './ComponentDeveloperInfo';
import ComponentDetails from './ComponentDetails';
import ComponentManagement from './ComponentManagement';
import ComponentPreview from './ComponentPreview';
import messages from './messages';
Expand Down Expand Up @@ -50,11 +50,7 @@ const ComponentInfo = ({ usageKey }: ComponentInfoProps) => {
<ComponentManagement usageKey={usageKey} />
</Tab>
<Tab eventKey="details" title={intl.formatMessage(messages.detailsTabTitle)}>
Details tab placeholder

{
(process.env.NODE_ENV === 'development' ? <ComponentDeveloperInfo usageKey={usageKey} /> : null)
}
<ComponentDetails usageKey={usageKey} />
</Tab>
</Tabs>
</Stack>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import { mockLibraryBlockMetadata } from '../data/api.mocks';
import ComponentManagement from './ComponentManagement';

/*
* FIXME: Summarize the reason here
* This function is used to get the inner text of an element.
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Missed comment from the ComponentManagent tab test

* https://stackoverflow.com/questions/47902335/innertext-is-undefined-in-jest-test
*/
const getInnerText = (element: Element) => element?.textContent
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ const ComponentManagement = ({ usageKey }: ComponentManagementProps) => {
const intl = useIntl();
const { data: componentMetadata } = useLibraryBlockMetadata(usageKey);

// istanbul ignore if: this should never happen
if (!componentMetadata) {
return null;
}
Expand Down
15 changes: 15 additions & 0 deletions src/library-authoring/component-info/messages.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,21 @@ const messages = defineMessages({
defaultMessage: 'Details',
description: 'Title for details tab',
},
detailsTabDescriptionTitle: {
id: 'course-authoring.library-authoring.component.details-tab.description-title',
defaultMessage: 'Description / Card Preview Text',
description: 'Title for the Description container in the details tab',
},
rpenido marked this conversation as resolved.
Show resolved Hide resolved
detailsTabUsageTitle: {
id: 'course-authoring.library-authoring.component.details-tab.usage-title',
defaultMessage: 'Component Usage',
description: 'Title for the Component Usage container in the details tab',
},
detailsTabHistoryTitle: {
id: 'course-authoring.library-authoring.component.details-tab.history-title',
defaultMessage: 'Component History',
description: 'Title for the Component History container in the details tab',
},
previewExpandButtonTitle: {
id: 'course-authoring.library-authoring.component.preview.expand.title',
defaultMessage: 'Expand',
Expand Down
5 changes: 5 additions & 0 deletions src/library-authoring/data/api.mocks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,7 @@ mockCreateLibraryBlock.newHtmlData = {
lastDraftCreated: '2024-07-22T21:37:49Z',
lastDraftCreatedBy: null,
created: '2024-07-22T21:37:49Z',
modified: '2024-07-22T21:37:49Z',
tagsCount: 0,
} satisfies api.LibraryBlockMetadata;
mockCreateLibraryBlock.newProblemData = {
Expand All @@ -147,6 +148,7 @@ mockCreateLibraryBlock.newProblemData = {
lastDraftCreated: '2024-07-22T21:37:49Z',
lastDraftCreatedBy: null,
created: '2024-07-22T21:37:49Z',
modified: '2024-07-22T21:37:49Z',
tagsCount: 0,
} satisfies api.LibraryBlockMetadata;
mockCreateLibraryBlock.newVideoData = {
Expand All @@ -160,6 +162,7 @@ mockCreateLibraryBlock.newVideoData = {
lastDraftCreated: '2024-07-22T21:37:49Z',
lastDraftCreatedBy: null,
created: '2024-07-22T21:37:49Z',
modified: '2024-07-22T21:37:49Z',
tagsCount: 0,
} satisfies api.LibraryBlockMetadata;
/** Apply this mock. Returns a spy object that can tell you if it's been called. */
Expand Down Expand Up @@ -234,6 +237,7 @@ mockLibraryBlockMetadata.dataNeverPublished = {
lastDraftCreatedBy: null,
hasUnpublishedChanges: false,
created: '2024-06-20T13:54:21Z',
modified: '2024-06-21T13:54:21Z',
tagsCount: 0,
} satisfies api.LibraryBlockMetadata;
mockLibraryBlockMetadata.usageKeyPublished = 'lb:Axim:TEST2:html:571fe018-f3ce-45c9-8f53-5dafcb422fd2';
Expand All @@ -248,6 +252,7 @@ mockLibraryBlockMetadata.dataPublished = {
lastDraftCreatedBy: '2024-06-20T20:00:00Z',
hasUnpublishedChanges: false,
created: '2024-06-20T13:54:21Z',
modified: '2024-06-21T13:54:21Z',
tagsCount: 0,
} satisfies api.LibraryBlockMetadata;
/** Apply this mock. Returns a spy object that can tell you if it's been called. */
Expand Down
1 change: 1 addition & 0 deletions src/library-authoring/data/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,7 @@ export interface LibraryBlockMetadata {
lastDraftCreatedBy: string | null,
hasUnpublishedChanges: boolean;
created: string | null,
modified: string | null,
tagsCount: number;
}

Expand Down
1 change: 1 addition & 0 deletions src/library-authoring/data/apiHooks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@ export const xblockQueryKeys = {
*/
export function invalidateComponentData(queryClient: QueryClient, contentLibraryId: string, usageKey: string) {
queryClient.invalidateQueries({ queryKey: xblockQueryKeys.xblockFields(usageKey) });
queryClient.invalidateQueries({ queryKey: xblockQueryKeys.componentMetadata(usageKey) });
queryClient.invalidateQueries({ predicate: (query) => libraryQueryPredicate(query, contentLibraryId) });
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
.history-widget-bar {
border-left: 8px solid $info-300;
border-radius: 4px;
padding-left: 1rem;
}

52 changes: 52 additions & 0 deletions src/library-authoring/generic/history-widget/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import { FormattedMessage, FormattedDate } from '@edx/frontend-platform/i18n';
import { Stack } from '@openedx/paragon';

import messages from './messages';

const CustomFormattedDate = ({ date }: { date: string }) => (
<FormattedDate
value={date}
year="numeric"
month="long"
day="2-digit"
/>
);

type HistoryWidgedProps = {
modified: string | null;
created: string | null;
};

/**
* This component displays the history of an entity (Last Modified and Created dates)
*
* This component doesn't handle fetching the data or any other side effects. It only displays the dates.
*
* @example
* ```tsx
* const { data: componentMetadata } = useLibraryBlockMetadata(usageKey);
*
* return <HistoryWidget {...componentMetadata} />;
* ```
*/
const HistoryWidget = ({
modified,
created,
}: HistoryWidgedProps) => (
<Stack className="history-widget-bar small" gap={3}>
{modified && (
<div>
<div className="text-muted"><FormattedMessage {...messages.lastModifiedTitle} /> </div>
<CustomFormattedDate date={modified} />
</div>
)}
{created && (
<div>
<div className="text-muted"><FormattedMessage {...messages.createdTitle} /> </div>
<CustomFormattedDate date={created} />
</div>
)}
</Stack>
);

export default HistoryWidget;
16 changes: 16 additions & 0 deletions src/library-authoring/generic/history-widget/messages.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { defineMessages } from '@edx/frontend-platform/i18n';

const messages = defineMessages({
lastModifiedTitle: {
id: 'course-authoring.library-authoring.generic.history-widget.last-modified',
defaultMessage: 'Last Modified',
description: 'Title of the last modified section in the library authoring sidebar.',
},
createdTitle: {
id: 'course-authoring.library-authoring.generic.history-widget.created',
defaultMessage: 'Created',
description: 'Title of the created section in the library authoring sidebar.',
},
});

export default messages;
1 change: 1 addition & 0 deletions src/library-authoring/generic/index.scss
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
@import "./status-widget/StatusWidget";
@import "./history-widget/HistoryWidget";