From 0d0e855c96175d8e90cb0b3ea1976d82410076a3 Mon Sep 17 00:00:00 2001
From: ilkin-overleaf <100852799+ilkin-overleaf@users.noreply.github.com>
Date: Mon, 9 Jan 2023 16:32:45 +0200
Subject: [PATCH] Merge pull request #11074 from
overleaf/ii-tagged-trashed-projects
[web] Project dashboard archived and trashed projects appearing not hiding
GitOrigin-RevId: b323be2a1104af54d3af9c9610b584fc0ab24c10
---
.../components/sidebar/tags-list.tsx | 17 ++--
.../context/project-list-context.tsx | 22 +++++-
.../js/features/project-list/util/project.ts | 4 +
.../components/project-list-root.test.tsx | 78 ++++++++++++++++++-
.../project-list/fixtures/projects-data.ts | 1 +
5 files changed, 112 insertions(+), 10 deletions(-)
diff --git a/services/web/frontend/js/features/project-list/components/sidebar/tags-list.tsx b/services/web/frontend/js/features/project-list/components/sidebar/tags-list.tsx
index 3eb379b5ff..c43dfb2dda 100644
--- a/services/web/frontend/js/features/project-list/components/sidebar/tags-list.tsx
+++ b/services/web/frontend/js/features/project-list/components/sidebar/tags-list.tsx
@@ -11,8 +11,13 @@ import useTag from '../../hooks/use-tag'
export default function TagsList() {
const { t } = useTranslation()
- const { tags, untaggedProjectsCount, selectedTagId, selectTag } =
- useProjectListContext()
+ const {
+ tags,
+ projectsPerTag,
+ untaggedProjectsCount,
+ selectedTagId,
+ selectTag,
+ } = useProjectListContext()
const {
handleSelectTag,
openCreateTagModal,
@@ -34,11 +39,11 @@ export default function TagsList() {
{t('new_folder')}
- {sortBy(tags, tag => tag.name.toLowerCase()).map((tag, index) => {
+ {sortBy(tags, tag => tag.name.toLowerCase()).map(tag => {
return (
diff --git a/services/web/frontend/js/features/project-list/context/project-list-context.tsx b/services/web/frontend/js/features/project-list/context/project-list-context.tsx
index 904d21e6d5..a5eed4f409 100644
--- a/services/web/frontend/js/features/project-list/context/project-list-context.tsx
+++ b/services/web/frontend/js/features/project-list/context/project-list-context.tsx
@@ -29,7 +29,11 @@ import getMeta from '../../../utils/meta'
import useAsync from '../../../shared/hooks/use-async'
import { getProjects } from '../util/api'
import sortProjects from '../util/sort-projects'
-import { isDeletableProject, isLeavableProject } from '../util/project'
+import {
+ isArchivedOrTrashed,
+ isDeletableProject,
+ isLeavableProject,
+} from '../util/project'
const MAX_PROJECT_PER_PAGE = 20
@@ -75,6 +79,7 @@ export type ProjectListContextValue = {
setSort: React.Dispatch>
tags: Tag[]
untaggedProjectsCount: number
+ projectsPerTag: Record
filter: Filter
selectFilter: (filter: Filter) => void
selectedTagId?: string | undefined
@@ -182,8 +187,8 @@ export function ProjectListProvider({ children }: ProjectListProviderProps) {
} else {
const tag = tags.find(tag => tag._id === selectedTagId)
if (tag) {
- filteredProjects = filteredProjects.filter(project =>
- tag?.project_ids?.includes(project.id)
+ filteredProjects = filteredProjects.filter(
+ p => !isArchivedOrTrashed(p) && tag?.project_ids?.includes(p.id)
)
} else {
setSelectedTagId(undefined)
@@ -277,6 +282,15 @@ export function ProjectListProvider({ children }: ProjectListProviderProps) {
).length
}, [tags, loadedProjects])
+ const projectsPerTag = useMemo(() => {
+ return tags.reduce>((prev, curTag) => {
+ const tagProjects = loadedProjects.filter(p => {
+ return !isArchivedOrTrashed(p) && curTag.project_ids?.includes(p.id)
+ })
+ return { ...prev, [curTag._id]: tagProjects }
+ }, {})
+ }, [tags, loadedProjects])
+
const selectFilter = useCallback(
(filter: Filter) => {
setFilter(filter)
@@ -432,6 +446,7 @@ export function ProjectListProvider({ children }: ProjectListProviderProps) {
totalProjectsCount,
untaggedProjectsCount,
updateProjectViewData,
+ projectsPerTag,
visibleProjects,
}),
[
@@ -465,6 +480,7 @@ export function ProjectListProvider({ children }: ProjectListProviderProps) {
totalProjectsCount,
untaggedProjectsCount,
updateProjectViewData,
+ projectsPerTag,
visibleProjects,
]
)
diff --git a/services/web/frontend/js/features/project-list/util/project.ts b/services/web/frontend/js/features/project-list/util/project.ts
index d005ff4be3..d40ecd757a 100644
--- a/services/web/frontend/js/features/project-list/util/project.ts
+++ b/services/web/frontend/js/features/project-list/util/project.ts
@@ -20,3 +20,7 @@ export function isDeletableProject(project: Project) {
export function isLeavableProject(project: Project) {
return project.accessLevel !== 'owner' && project.trashed
}
+
+export function isArchivedOrTrashed(project: Project) {
+ return project.archived || project.trashed
+}
diff --git a/services/web/test/frontend/features/project-list/components/project-list-root.test.tsx b/services/web/test/frontend/features/project-list/components/project-list-root.test.tsx
index 61d5fa560a..480d0f9459 100644
--- a/services/web/test/frontend/features/project-list/components/project-list-root.test.tsx
+++ b/services/web/test/frontend/features/project-list/components/project-list-root.test.tsx
@@ -12,8 +12,14 @@ import {
makeLongProjectList,
} from '../fixtures/projects-data'
-const { fullList, currentList, trashedList, leavableList, deletableList } =
- makeLongProjectList(40)
+const {
+ fullList,
+ currentList,
+ archivedList,
+ trashedList,
+ leavableList,
+ deletableList,
+} = makeLongProjectList(40)
const userId = owner.id
@@ -543,6 +549,74 @@ describe('', function () {
})
})
+ describe('tags', function () {
+ it('does not show archived or trashed project', async function () {
+ this.unmount()
+ fetchMock.restore()
+ window.metaAttributesCache.set('ol-tags', [
+ {
+ _id: this.tagId,
+ name: this.tagName,
+ project_ids: [
+ projectsData[0].id,
+ projectsData[1].id,
+ ...archivedList.map(p => p.id),
+ ...trashedList.map(p => p.id),
+ ],
+ },
+ ])
+
+ const trashProjectMock = fetchMock.post(
+ `express:/project/:projectId/trash`,
+ { status: 200 }
+ )
+
+ renderWithProjectListContext(, {
+ projects: fullList,
+ })
+
+ await screen.findByRole('table')
+
+ let visibleProjectsCount = 2
+ const [tagBtn] = screen.getAllByRole('button', {
+ name: `${this.tagName} (${visibleProjectsCount})`,
+ })
+ fireEvent.click(tagBtn)
+
+ const nonArchivedAndTrashedProjects = [
+ projectsData[0],
+ projectsData[1],
+ ]
+ nonArchivedAndTrashedProjects.forEach(p => {
+ screen.getByText(p.name)
+ })
+ const archivedAndTrashedProjects = [...archivedList, ...trashedList]
+ archivedAndTrashedProjects.forEach(p => {
+ expect(screen.queryByText(p.name)).to.be.null
+ })
+
+ const trashBtns = screen.getAllByRole('button', { name: 'Trash' })
+ for (const [index, trashBtn] of trashBtns.entries()) {
+ fireEvent.click(trashBtn)
+ fireEvent.click(screen.getByText('Confirm'))
+ await waitFor(() => {
+ expect(
+ trashProjectMock.called(
+ `/project/${projectsData[index].id}/trash`
+ )
+ ).to.be.true
+ })
+ expect(
+ screen.queryAllByText(projectsData[index].name)
+ ).to.have.length(0)
+
+ screen.getAllByRole('button', {
+ name: `${this.tagName} (${--visibleProjectsCount})`,
+ })
+ }
+ })
+ })
+
describe('tags dropdown', function () {
beforeEach(async function () {
allCheckboxes = screen.getAllByRole('checkbox')
diff --git a/services/web/test/frontend/features/project-list/fixtures/projects-data.ts b/services/web/test/frontend/features/project-list/fixtures/projects-data.ts
index 33299cb355..cafc192d63 100644
--- a/services/web/test/frontend/features/project-list/fixtures/projects-data.ts
+++ b/services/web/test/frontend/features/project-list/fixtures/projects-data.ts
@@ -162,6 +162,7 @@ export const makeLongProjectList = (listLength: number) => {
({ archived, trashed }) => !archived && !trashed
),
trashedList: longList.filter(({ trashed }) => trashed),
+ archivedList: longList.filter(({ archived }) => archived),
leavableList: longList.filter(isLeavableProject),
deletableList: longList.filter(isDeletableProject),
}