mirror of
https://github.com/overleaf/overleaf.git
synced 2024-11-29 05:03:33 -05:00
Merge pull request #11074 from overleaf/ii-tagged-trashed-projects
[web] Project dashboard archived and trashed projects appearing not hiding GitOrigin-RevId: b323be2a1104af54d3af9c9610b584fc0ab24c10
This commit is contained in:
parent
bde79780a7
commit
0d0e855c96
5 changed files with 112 additions and 10 deletions
|
@ -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() {
|
|||
<span className="name">{t('new_folder')}</span>
|
||||
</Button>
|
||||
</li>
|
||||
{sortBy(tags, tag => tag.name.toLowerCase()).map((tag, index) => {
|
||||
{sortBy(tags, tag => tag.name.toLowerCase()).map(tag => {
|
||||
return (
|
||||
<li
|
||||
className={`tag ${selectedTagId === tag._id ? 'active' : ''}`}
|
||||
key={index}
|
||||
key={tag._id}
|
||||
>
|
||||
<Button
|
||||
className="tag-name"
|
||||
|
@ -59,7 +64,9 @@ export default function TagsList() {
|
|||
</span>
|
||||
<span className="name">
|
||||
{tag.name}{' '}
|
||||
<span className="subdued"> ({tag.project_ids?.length})</span>
|
||||
<span className="subdued">
|
||||
({projectsPerTag[tag._id].length})
|
||||
</span>
|
||||
</span>
|
||||
</Button>
|
||||
<span className="dropdown tag-menu">
|
||||
|
|
|
@ -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<React.SetStateAction<Sort>>
|
||||
tags: Tag[]
|
||||
untaggedProjectsCount: number
|
||||
projectsPerTag: Record<Tag['_id'], Project[]>
|
||||
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<Record<Tag['_id'], Project[]>>((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,
|
||||
]
|
||||
)
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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('<ProjectListRoot />', 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(<ProjectListRoot />, {
|
||||
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<HTMLButtonElement>('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<HTMLInputElement>('checkbox')
|
||||
|
|
|
@ -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),
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue