Skip to content

Commit

Permalink
3743 - Broken Links (#3811)
Browse files Browse the repository at this point in the history
* 3743 - Broken Links - Add cycle link tables (#3791)

* 3743 - Add link tables

* 3743 - Reorder link types

* 3743 - Update link table

* 3743 - Update link type

* 3743 - Add locations and unique country/link constraint

* 3743 - Update link type

* 3743 - Broken links - Get all links to visit (#3812)

* 3743 - Get Descriptions and ODPs with links

* 3743 - Add Link controller -> get all links to visit

* 3743 - Broken Links - Add visit cycle links worker (#3813)

* 3743 - Add Link -> getMany

* 3743 - Add Link -> upsertMany

* 3743 - Add visit cycle links worker

* 3743 - Close worker connections

* 3743 - Fix update props

* 3743 - Broken links - Add API endpoints & activity logs (#3823)

* 3743 - Add Link api endpoints

* 3743 - Add worker activity logs

* 3743 - Rename Link to Links

* 3743 - Rename Link type folder to Links

* 3743 - Rename Link repository folder to Links

* 3743 - Rename Link api folder to Links

* 3743 - Rename Link controller folder to Links

* 3743 - Update tests Links import

* 3743 - Update activity log messages

* 3743 - Broken Links - Add sockets events and Get verification job status (#3830)

* 3743 - Add socket events

* 3743 - Add endpoint to get if the verification job is in progress

* 3743 Move getActiveVerifyJobs function

* 3743 - Add local emit event function

* 3743 - Add redux state and actions (#3832)

* 3743 - Add admin links route

* 3743 - Add translations

* 3743 - Add Admin links table

* 3743 - Update table with event listeners

* Revert "3743 - Update table with event listeners"

This reverts commit 66806e7.

* Revert "3743 - Add Admin links table"

This reverts commit d8f63fc.

* Revert "3743 - Add translations"

This reverts commit 24bbf33.

* Revert "3743 - Add admin links route"

This reverts commit 8f71842.

* 3743 - Broken Links - UI Table (#3833)

* 3743 - Add admin links route

* 3743 - Add translations

* 3743 - Add Admin links table

* 3743 - Update table with event listeners

* 3743 - Broken Links - Delete unused links and disable approve button (#3834)

* 3743 - Disable approve button when the verify job is running

* 3743 - Delete unused links

* 3743 - Soft delete unused links

* 3743 - Hide deleted links in the UI

* 3743 - Remove visits column

* 3743 - Update count on links table update

* 3743 - Add excludeDeleted default value

* 3743 - Remove visits count translation
  • Loading branch information
yaguzmang committed Jun 5, 2024
1 parent b009f88 commit 6143de3
Show file tree
Hide file tree
Showing 72 changed files with 1,715 additions and 31 deletions.
8 changes: 6 additions & 2 deletions src/client/pages/Admin/Admin.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,10 @@ const sections: Array<Section> = [
name: SectionNames.Admin.userManagement,
labelKey: 'landing.sections.userManagement',
},
{
name: SectionNames.Admin.links,
labelKey: 'landing.links.links',
},
// { name: 'dataExport', labelKey: 'common.dataExport' },
]

Expand All @@ -38,7 +42,7 @@ const Admin: React.FC = () => {
const countries = useCountries()
const user = useUser()

if (!Users.isAdministrator(user)) return <Navigate to={Routes.Root.path.absolute} replace />
if (!Users.isAdministrator(user)) return <Navigate replace to={Routes.Root.path.absolute} />

if (Objects.isEmpty(countries)) return null

Expand All @@ -52,12 +56,12 @@ const Admin: React.FC = () => {
{sections.map(({ name, labelKey }) => (
<NavLink
key={name}
to={name}
className={(navData) =>
classNames('btn admin__page-menu-button', {
disabled: navData.isActive,
})
}
to={name}
>
{t(labelKey)}
</NavLink>
Expand Down
48 changes: 48 additions & 0 deletions src/client/pages/AdminLinks/AdminLinks.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import React, { useEffect } from 'react'
import { useTranslation } from 'react-i18next'

import { ApiEndPoint } from 'meta/api/endpoint'

import { useAppDispatch } from 'client/store'
import { LinksActions, useIsVerificationInProgress, useLinksChangeListener } from 'client/store/ui/links'
import { useSectionRouteParams } from 'client/hooks/useRouteParams'
import Button from 'client/components/Buttons/Button'
import TablePaginated from 'client/components/TablePaginated'

import { useColumns } from './hooks/useColumns'
import { useListenLinksVerificationEvents } from './hooks/useListenLinksVerificationEvents'

const AdminLinks: React.FC = () => {
const columns = useColumns()
const dispatch = useAppDispatch()
const { t } = useTranslation()
const { assessmentName, cycleName } = useSectionRouteParams()

const handleVerifyLinks = () => {
dispatch(LinksActions.verifyLinks({ assessmentName, cycleName }))
}

const verifyLinksInProgress = useIsVerificationInProgress(assessmentName, cycleName)

useLinksChangeListener()
useListenLinksVerificationEvents()

useEffect(() => {
dispatch(LinksActions.getIsVerificationInProgress({ assessmentName, cycleName }))
}, [assessmentName, cycleName, dispatch])

return (
<>
<div>
<Button disabled={verifyLinksInProgress ?? true} label={t('admin.verifyLinks')} onClick={handleVerifyLinks} />
</div>
<TablePaginated
columns={columns}
gridTemplateColumns="auto min-content min-content"
path={ApiEndPoint.CycleData.Links.many()}
/>
</>
)
}

export default AdminLinks
13 changes: 13 additions & 0 deletions src/client/pages/AdminLinks/LastStatus/LastStatus.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
@import 'src/client/style/partials';

.last-status {
&.empty,
&.enotfound,
&.urlParsingError {
color: $ui-destructive;
}

&.success {
color: $ui-status-accepted;
}
}
27 changes: 27 additions & 0 deletions src/client/pages/AdminLinks/LastStatus/LastStatus.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import './LastStatus.scss'
import React from 'react'
import { useTranslation } from 'react-i18next'

import classNames from 'classnames'

import { Link as LinkType, LinkValidationStatusCode } from 'meta/cycleData'

type Props = {
link: LinkType
}

const LastStatus: React.FC<Props> = (props) => {
const { link } = props
const { t } = useTranslation()
const code = link.visits?.at(-1).code

let label = ''
if ([LinkValidationStatusCode.success, LinkValidationStatusCode.empty].includes(code)) {
label = t(`common.${code}`)
} else {
label = t(`admin.${code}`)
}

return <span className={classNames('last-status', code)}>{label}</span>
}
export default LastStatus
1 change: 1 addition & 0 deletions src/client/pages/AdminLinks/LastStatus/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { default } from './LastStatus'
43 changes: 43 additions & 0 deletions src/client/pages/AdminLinks/Link/Link.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
@import 'src/client/style/partials';

.link-cell {
align-items: center;
display: grid;
grid-column-gap: $spacing-xs;
grid-template-columns: repeat(2, auto);
justify-content: space-between;
width: 100%;
}

.link-cell__badge {
align-items: center;
background-color: $ui-status-accepted;
border-radius: 4px;
color: white;
display: grid;
font-size: $font-xxxxs;
font-weight: 600;
height: 20px;
justify-content: center;
letter-spacing: 0.4px;
line-height: 0;
padding: 0 $spacing-xxs;
text-transform: uppercase;
}

.link-cell__anchor {
display: block;
overflow: hidden;
text-decoration: none;
text-overflow: ellipsis;
white-space: nowrap;

&:hover {
text-decoration: underline;
}
}

.link-cell__badge-button-container {
display: flex;
gap: $spacing-xs;
}
58 changes: 58 additions & 0 deletions src/client/pages/AdminLinks/Link/Link.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import './Link.scss'
import React from 'react'
import { useTranslation } from 'react-i18next'

import classNames from 'classnames'
import { Objects } from 'utils/objects'

import { Link as LinkType } from 'meta/cycleData'

import { useAppDispatch } from 'client/store'
import { LinksActions, useIsVerificationInProgress } from 'client/store/ui/links'
import { useSectionRouteParams } from 'client/hooks/useRouteParams'
import Button, { ButtonSize, ButtonType } from 'client/components/Buttons/Button'

type Props = {
link: LinkType
}

const Link: React.FC<Props> = (props) => {
const { link: linkInfo } = props
const { link } = linkInfo
const { assessmentName, cycleName } = useSectionRouteParams()
const dispatch = useAppDispatch()
const { t } = useTranslation()

const verifyLinksInProgress = useIsVerificationInProgress(assessmentName, cycleName)

const approved = linkInfo.props?.approved
const withApprovalBadge = approved ?? false

const handleUpdateLink = async () => {
const newApproved = Objects.isEmpty(approved) ? true : !approved
const newProps = { ...linkInfo.props, approved: newApproved }
const newLink = { ...linkInfo, props: newProps }
dispatch(LinksActions.updateLink({ assessmentName, cycleName, link: newLink }))
}

return (
<div className={classNames('link-cell', { withApprovalBadge })}>
<a className="link-cell__anchor" href={link} rel="noreferrer" target="_blank">
{link}
</a>
<div className="link-cell__badge-button-container">
{withApprovalBadge && <div className="link-cell__badge">{t('common.approved')}</div>}
<Button
disabled={verifyLinksInProgress ?? true}
inverse
label={withApprovalBadge ? t('common.disapprove') : t('common.approve')}
onClick={handleUpdateLink}
size={ButtonSize.xs}
type={ButtonType.primary}
/>
</div>
</div>
)
}

export default Link
1 change: 1 addition & 0 deletions src/client/pages/AdminLinks/Link/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { default } from './Link'
41 changes: 41 additions & 0 deletions src/client/pages/AdminLinks/hooks/useColumns.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import React, { useMemo } from 'react'
import { useTranslation } from 'react-i18next'

import { Link as LinkType } from 'meta/cycleData'

import { Column } from 'client/components/TablePaginated'
import LastStatus from 'client/pages/AdminLinks/LastStatus'
import LinkItem from 'client/pages/AdminLinks/Link'

const LocationsCount: React.FC<{ link: LinkType }> = (props) => {
const { link } = props
const { locations } = link

return <span>{locations?.length}</span>
}

export const useColumns = (): Array<Column<LinkType>> => {
const { t } = useTranslation()

return useMemo<Array<Column<LinkType>>>(() => {
return [
{
component: ({ datum }) => <LinkItem link={datum} />,
header: t('common.link'),
key: 'link',
orderByProperty: 'link',
},
{
component: ({ datum }) => <LastStatus link={datum} />,
header: t('admin.lastStatus'),
key: 'lastStatus',
orderByProperty: 'code',
},
{
component: ({ datum }) => <LocationsCount link={datum} />,
header: t('admin.locationsCount'),
key: 'locationsCount',
},
]
}, [t])
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import { useEffect } from 'react'

import { WorkerListener } from 'bullmq'

import { ApiEndPoint } from 'meta/api/endpoint'
import { Sockets } from 'meta/socket'

import { useAppDispatch } from 'client/store'
import { LinksActions } from 'client/store/ui/links'
import { TablePaginatedActions, useTablePaginatedOrderBy, useTablePaginatedPage } from 'client/store/ui/tablePaginated'
import { useSectionRouteParams } from 'client/hooks/useRouteParams'
import { SocketClient } from 'client/service/socket'

export const useListenLinksVerificationEvents = (): void => {
const dispatch = useAppDispatch()
const { assessmentName, cycleName } = useSectionRouteParams()

const linksVerificationEvent = Sockets.getLinksVerificationEvent({ assessmentName, cycleName })

const path = ApiEndPoint.CycleData.Links.many()
const page = useTablePaginatedPage(path)
const orderBy = useTablePaginatedOrderBy(path)

useEffect(() => {
const listener = (args: [{ event: keyof WorkerListener }]): void => {
const [{ event }] = args
if (event === 'active') {
dispatch(
LinksActions.setIsVerificationInProgress({ assessmentName, cycleName, isVerificationInProgress: true })
)
} else if (event === 'completed' || event === 'failed') {
dispatch(
LinksActions.setIsVerificationInProgress({ assessmentName, cycleName, isVerificationInProgress: false })
)
const getDataProps = { assessmentName, cycleName, limit: 30, orderBy, page, path }
dispatch(TablePaginatedActions.getData(getDataProps))
dispatch(TablePaginatedActions.getCount(getDataProps))
}
}

SocketClient.on(linksVerificationEvent, listener)
return () => {
SocketClient.off(linksVerificationEvent, listener)
}
}, [assessmentName, cycleName, dispatch, linksVerificationEvent, orderBy, page, path])
}
1 change: 1 addition & 0 deletions src/client/pages/AdminLinks/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { default } from './AdminLinks'
Loading

0 comments on commit 6143de3

Please sign in to comment.