Skip to content

Commit 36277d8

Browse files
Muhammad Faraz  MaqsoodFaraz32123
authored andcommitted
feat: course optimizer page better design
- Add filter functionality to course optimizer broken links to check different results - modify design, make use of logo with better tooltip - change message texts in different area of the page
1 parent cdb8016 commit 36277d8

13 files changed

Lines changed: 596 additions & 271 deletions

src/optimizer-page/CourseOptimizerPage.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -132,23 +132,23 @@ const CourseOptimizerPage: FC<{ courseId: string }> = ({ courseId }) => {
132132
title={intl.formatMessage(messages.headingTitle)}
133133
subtitle={intl.formatMessage(messages.headingSubtitle)}
134134
/>
135-
<p className="small">{intl.formatMessage(messages.description1)}</p>
136-
<p className="small">{intl.formatMessage(messages.description2)}</p>
135+
<p className="small opt-desc-mb">{intl.formatMessage(messages.description)}</p>
137136
<Card>
138137
<Card.Header
139138
className="h3 px-3 text-black mb-4"
140139
title={intl.formatMessage(messages.card1Title)}
141140
/>
142141
{isShowExportButton && (
143142
<Card.Section className="px-3 py-1">
143+
<p className="small"> {lastScannedAt && `${intl.formatMessage(messages.lastScannedOn)} ${intl.formatDate(lastScannedAt, { year: 'numeric', month: 'long', day: 'numeric' })}`}</p>
144144
<Button
145145
size="lg"
146146
block
147147
className="mb-4"
148148
onClick={() => dispatch(startLinkCheck(courseId))}
149149
iconBefore={SearchIcon}
150150
>
151-
{intl.formatMessage(messages.buttonTitle)} {lastScannedAt && `(${intl.formatMessage(messages.lastScannedOn)} ${intl.formatDate(lastScannedAt, { year: 'numeric', month: 'long', day: 'numeric' })})`}
151+
{intl.formatMessage(messages.buttonTitle)}
152152
</Button>
153153
</Card.Section>
154154
)}

src/optimizer-page/messages.js

Lines changed: 5 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -7,24 +7,15 @@ const messages = defineMessages({
77
},
88
headingTitle: {
99
id: 'course-authoring.course-optimizer.heading.title',
10-
defaultMessage: 'Course Optimizer',
10+
defaultMessage: 'Course optimizer',
1111
},
1212
headingSubtitle: {
1313
id: 'course-authoring.course-optimizer.heading.subtitle',
1414
defaultMessage: 'Tools',
1515
},
16-
description1: {
17-
id: 'course-authoring.course-optimizer.description1',
18-
defaultMessage: `This tool will scan the published version of your course for broken links.
19-
Unpublished changes will not be included in the scan.
20-
Note that this process will take more time for larger courses.
21-
To update the scan after you have published new changes to your course,
22-
click the "Start Scanning" button again.
23-
`,
24-
},
25-
description2: {
26-
id: 'course-authoring.course-optimizer.description2',
27-
defaultMessage: 'Broken links are links pointing to external websites, images, or videos that do not exist or are no longer available. These links can cause issues for learners when they try to access the content.',
16+
description: {
17+
id: 'course-authoring.course-optimizer.description',
18+
defaultMessage: 'This tool will scan your course for broken links, and any links that point to pages in your previous course run. Unpublished changes will not be included in the scan. Note that this process will take more time for larger courses.',
2819
},
2920
card1Title: {
3021
id: 'course-authoring.course-optimizer.card1.title',
@@ -36,7 +27,7 @@ const messages = defineMessages({
3627
},
3728
buttonTitle: {
3829
id: 'course-authoring.course-optimizer.button.title',
39-
defaultMessage: 'Start Scanning',
30+
defaultMessage: 'Start scanning',
4031
},
4132
preparingStepTitle: {
4233
id: 'course-authoring.course-optimizer.peparing-step.title',
Lines changed: 110 additions & 119 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,16 @@
11
import {
2-
Card, Icon, OverlayTrigger, Table, Tooltip,
2+
Card, Icon, Table,
33
} from '@openedx/paragon';
44
import {
5-
OpenInNew, Lock, LinkOff, InfoOutline,
5+
ArrowForwardIos,
6+
LinkOff,
67
} from '@openedx/paragon/icons';
7-
import { useIntl } from '@edx/frontend-platform/i18n';
88
import { FC } from 'react';
9-
import { Unit } from '../types';
9+
import { Filters, Unit } from '../types';
1010
import messages from './messages';
11-
import LockedInfoIcon from './LockedInfoIcon';
11+
import CustomIcon from './CustomIcon';
12+
import lockedIcon from './lockedIcon';
13+
import ManualIcon from './manualIcon';
1214

1315
const BrokenLinkHref: FC<{ href: string }> = ({ href }) => {
1416
const handleClick = (event: React.MouseEvent<HTMLAnchorElement>) => {
@@ -25,150 +27,139 @@ const BrokenLinkHref: FC<{ href: string }> = ({ href }) => {
2527
);
2628
};
2729

28-
const GoToBlock: FC<{ block: { url: string } }> = ({ block }) => {
30+
const GoToBlock: FC<{ block: { url: string, displayName: string } }> = ({ block }) => {
2931
const handleClick = (event: React.MouseEvent<HTMLAnchorElement>) => {
3032
event.preventDefault();
3133
window.open(block.url, '_blank');
3234
};
3335

3436
return (
35-
<span style={{ display: 'flex', gap: '.5rem' }}>
36-
<Icon src={OpenInNew} />
37-
<a href={block.url} onClick={handleClick} rel="noreferrer">
38-
Go to Block
37+
<div className="go-to-block-link-container">
38+
<a href={block.url} onClick={handleClick} className="broken-link" rel="noreferrer">
39+
{block.displayName}
3940
</a>
40-
</span>
41+
</div>
4142
);
4243
};
4344

44-
const RecommendedManualCheckHeading = () => {
45-
const intl = useIntl();
46-
return (
47-
<span className="d-flex align-items-center font-weight-bold py-2">
48-
{intl.formatMessage(messages.recommendedManualCheckText)}
49-
<OverlayTrigger
50-
key="top"
51-
placement="top"
52-
overlay={(
53-
<Tooltip id="tooltip-top">
54-
{intl.formatMessage(messages.recommendedManualCheckTooltip)}
55-
</Tooltip>
56-
)}
57-
>
58-
<Icon className="ml-1 pl-1" src={InfoOutline} />
59-
</OverlayTrigger>
60-
</span>
61-
);
62-
};
45+
const LinksCol: FC<{ block: { url: string, displayName: string }, href: string }> = ({ block, href }) => (
46+
<span className="links-container">
47+
<GoToBlock block={{ url: block.url, displayName: block.displayName || 'Go to block' }} />
48+
<Icon className="arrow-forward-ios" src={ArrowForwardIos} />
49+
<BrokenLinkHref href={href} />
50+
</span>
51+
);
6352

6453
interface BrokenLinkTableProps {
6554
unit: Unit;
66-
showLockedLinks: boolean;
55+
filters: Filters;
6756
}
6857

6958
type TableData = {
70-
blockLink: JSX.Element;
71-
brokenLink: JSX.Element;
59+
Links: JSX.Element;
7260
status: JSX.Element;
7361
}[];
7462

7563
const BrokenLinkTable: FC<BrokenLinkTableProps> = ({
7664
unit,
77-
showLockedLinks,
78-
}) => {
79-
const intl = useIntl();
80-
return (
81-
<Card className="unit-card rounded-sm pt-2 pl-3 pr-4 mb-2.5">
82-
<p className="unit-header">{unit.displayName}</p>
83-
<Table
84-
data={unit.blocks.reduce(
85-
(
86-
acc: TableData,
87-
block,
88-
) => {
65+
filters,
66+
}) => (
67+
<Card className="unit-card rounded-sm pt-2 pl-3 pr-4 mb-2.5">
68+
<p className="unit-header">{unit.displayName}</p>
69+
<Table
70+
data={unit.blocks.reduce(
71+
(
72+
acc: TableData,
73+
block,
74+
) => {
75+
if (
76+
filters.brokenLinks
77+
|| (!filters.brokenLinks && !filters.externalForbiddenLinks && !filters.lockedLinks)
78+
) {
8979
const blockBrokenLinks = block.brokenLinks.map((link) => ({
90-
blockLink: <GoToBlock block={block} />,
91-
blockDisplayName: block.displayName || '',
92-
brokenLink: <BrokenLinkHref href={link} />,
80+
Links: (
81+
<LinksCol
82+
block={{ url: block.url, displayName: block.displayName || 'Go to block' }}
83+
href={link}
84+
/>
85+
),
9386
status: (
94-
<span className="link-status-text">
95-
<Icon src={LinkOff} className="broken-link-icon" />
96-
<span>
97-
{intl.formatMessage(messages.brokenLinkStatus)}
98-
</span>
99-
</span>
87+
<CustomIcon
88+
icon={LinkOff}
89+
message1={messages.brokenLabel}
90+
message2={messages.brokenInfoTooltip}
91+
/>
10092
),
10193
}));
10294
acc.push(...blockBrokenLinks);
95+
}
10396

104-
if (showLockedLinks) {
105-
const blockLockedLinks = block.lockedLinks.map((link) => ({
106-
blockLink: <GoToBlock block={block} />,
107-
blockDisplayName: block.displayName || '',
108-
brokenLink: <BrokenLinkHref href={link} />,
109-
status: (
110-
<span className="link-status-text">
111-
<Icon src={Lock} className="lock-icon" />
112-
{intl.formatMessage(messages.lockedLinkStatus)}{' '}
113-
<LockedInfoIcon />
114-
</span>
115-
),
116-
}));
97+
if (
98+
filters.lockedLinks
99+
|| (!filters.brokenLinks && !filters.externalForbiddenLinks && !filters.lockedLinks)
100+
) {
101+
const blockLockedLinks = block.lockedLinks.map((link) => ({
102+
Links: (
103+
<LinksCol
104+
block={{ url: block.url, displayName: block.displayName || 'Go to block' }}
105+
href={link}
106+
/>
107+
),
108+
status: (
109+
<CustomIcon
110+
icon={lockedIcon}
111+
message1={messages.lockedLabel}
112+
message2={messages.lockedInfoTooltip}
113+
/>
114+
),
115+
}));
117116

118-
acc.push(...blockLockedLinks);
119-
}
117+
acc.push(...blockLockedLinks);
118+
}
120119

121-
if (block.externalForbiddenLinks?.length > 0) {
122-
const recommendedManualCheckHeading = {
123-
blockLink: <div />,
124-
blockDisplayName: <RecommendedManualCheckHeading />,
125-
brokenLink: <div />,
126-
status: <div />,
127-
};
128-
const externalForbiddenLinks = block.externalForbiddenLinks.map((link) => ({
129-
blockLink: <GoToBlock block={block} />,
130-
blockDisplayName: block.displayName || '',
131-
brokenLink: <BrokenLinkHref href={link} />,
132-
status: <div />,
133-
}));
120+
if (
121+
filters.externalForbiddenLinks
122+
|| (!filters.brokenLinks && !filters.externalForbiddenLinks && !filters.lockedLinks)
123+
) {
124+
const externalForbiddenLinks = block.externalForbiddenLinks.map((link) => ({
125+
Links: (
126+
<LinksCol
127+
block={{ url: block.url, displayName: block.displayName || 'Go to block' }}
128+
href={link}
129+
/>
130+
),
131+
status: (
132+
<CustomIcon
133+
icon={ManualIcon}
134+
message1={messages.manualLabel}
135+
message2={messages.manualInfoTooltip}
136+
/>
137+
),
138+
}));
134139

135-
acc.push(recommendedManualCheckHeading);
136-
acc.push(...externalForbiddenLinks);
137-
}
140+
acc.push(...externalForbiddenLinks);
141+
}
138142

139-
return acc;
140-
},
141-
[],
142-
)}
143-
columns={[
144-
{
145-
key: 'blockDisplayName',
146-
columnSortable: false,
147-
width: 'col-3',
148-
hideHeader: true,
149-
},
150-
{
151-
key: 'blockLink',
152-
columnSortable: false,
153-
width: 'col-3',
154-
hideHeader: true,
155-
},
156-
{
157-
key: 'brokenLink',
158-
columnSortable: false,
159-
width: 'col-6',
160-
hideHeader: true,
161-
},
162-
{
163-
key: 'status',
164-
columnSortable: false,
165-
width: 'col-6',
166-
hideHeader: true,
167-
},
168-
]}
169-
/>
170-
</Card>
171-
);
172-
};
143+
return acc;
144+
},
145+
[],
146+
)}
147+
columns={[
148+
{
149+
key: 'Links',
150+
columnSortable: false,
151+
width: 'col-9',
152+
hideHeader: true,
153+
},
154+
{
155+
key: 'status',
156+
columnSortable: false,
157+
width: 'col-3',
158+
hideHeader: true,
159+
},
160+
]}
161+
/>
162+
</Card>
163+
);
173164

174165
export default BrokenLinkTable;
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
import PropTypes from 'prop-types';
2+
import {
3+
Icon,
4+
OverlayTrigger,
5+
Tooltip,
6+
} from '@openedx/paragon';
7+
import { useIntl } from '@edx/frontend-platform/i18n';
8+
9+
const CustomIcon = ({
10+
icon,
11+
message1,
12+
message2,
13+
placement = 'top',
14+
}) => {
15+
const intl = useIntl();
16+
17+
return (
18+
<OverlayTrigger
19+
key="top"
20+
placement={placement}
21+
overlay={(
22+
<Tooltip variant="dark" id="tooltip-top" className={placement !== 'top' ? 'ml-3' : ''}>
23+
{intl.formatMessage(message1)}
24+
{message1 && <br />}
25+
{intl.formatMessage(message2)}
26+
</Tooltip>
27+
)}
28+
>
29+
<Icon src={icon} />
30+
</OverlayTrigger>
31+
);
32+
};
33+
34+
const messagePropsType = {
35+
id: PropTypes.string.isRequired,
36+
defaultMessage: PropTypes.string.isRequired,
37+
};
38+
39+
CustomIcon.propTypes = {
40+
icon: PropTypes.elementType.isRequired,
41+
message1: PropTypes.shape(messagePropsType).isRequired,
42+
message2: PropTypes.shape(messagePropsType).isRequired,
43+
placement: PropTypes.string,
44+
};
45+
46+
export default CustomIcon;

0 commit comments

Comments
 (0)