Skip to content

Commit

Permalink
Implement filter for Project Spaces (#9649)
Browse files Browse the repository at this point in the history
* Implement filter for Project Spaces

* Fix search not working

* Make highlighting work

* Add changelog

* Add pagination

* Fix snapshot

* Fix unittests

* Add unittest for filter

* Fix faulty naming

* Add pagination for tiles

* Increase margin

* Update snapshots

* Remove unussed ref
  • Loading branch information
lookacat committed Sep 5, 2023
1 parent 3dae631 commit 3441554
Show file tree
Hide file tree
Showing 4 changed files with 132 additions and 8 deletions.
6 changes: 6 additions & 0 deletions changelog/unreleased/enhancement-add-project-space-filter
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
Bugfix: Add project space filter

We've added the option to search for spaces in the project space overview.

https://github.com/owncloud/web/pull/9649
https://github.com/owncloud/web/issues/9650
107 changes: 100 additions & 7 deletions packages/web-app-files/src/views/spaces/Projects.vue
Original file line number Diff line number Diff line change
Expand Up @@ -30,11 +30,18 @@
</template>
</no-content-message>
<div v-else class="spaces-list oc-mt-l">
<oc-text-input
id="spaces-filter"
v-model="filterTerm"
class="oc-ml-m oc-my-m"
:label="$gettext('Search')"
autocomplete="off"
/>
<resource-tiles
v-if="viewMode === ViewModeConstants.tilesView.name"
v-model:selectedIds="selectedResourcesIds"
class="oc-px-m"
:data="spaces"
:data="paginatedItems"
:resizable="true"
:sort-fields="sortFields"
:sort-by="sortBy"
Expand Down Expand Up @@ -64,11 +71,18 @@
:action-options="{ resources: [resource] as SpaceResource[] }"
/>
</template>
<template #footer>
<pagination :pages="totalPages" :current-page="currentPage" />
<div class="oc-text-nowrap oc-text-center oc-width-1-1 oc-my-s">
<p class="oc-text-muted">{{ footerTextTotal }}</p>
<p v-if="filterTerm" class="oc-text-muted">{{ footerTextFilter }}</p>
</div>
</template>
</resource-tiles>
<resource-table
v-else
v-model:selectedIds="selectedResourcesIds"
:resources="spaces"
:resources="paginatedItems"
class="spaces-table"
:class="{ 'spaces-table-squashed': sideBarOpen }"
:sticky="false"
Expand Down Expand Up @@ -118,6 +132,13 @@
/>
<oc-resource-icon v-else class="oc-mr-s" :resource="resource" />
</template>
<template #footer>
<pagination :pages="totalPages" :current-page="currentPage" />
<div class="oc-text-nowrap oc-text-center oc-width-1-1 oc-my-s">
<p class="oc-text-muted">{{ footerTextTotal }}</p>
<p v-if="filterTerm" class="oc-text-muted">{{ footerTextFilter }}</p>
</div>
</template>
</resource-table>
</div>
</template>
Expand All @@ -127,9 +148,11 @@
</template>

<script lang="ts">
import { onMounted, computed, defineComponent, unref } from 'vue'
import { onMounted, computed, defineComponent, unref, ref, watch, nextTick } from 'vue'
import { useTask } from 'vue-concurrency'
import { mapMutations, mapGetters } from 'vuex'
import Mark from 'mark.js'
import Fuse from 'fuse.js'
import NoContentMessage from 'web-pkg/src/components/NoContentMessage.vue'
import AppLoadingSpinner from 'web-pkg/src/components/AppLoadingSpinner.vue'
Expand All @@ -143,11 +166,16 @@ import {
useRouteQueryPersisted,
useSort,
useStore,
useRouteName
useRouteName,
SortDir,
usePagination,
useRouter,
useRoute
} from 'web-pkg/src/composables'
import { ImageDimension } from 'web-pkg/src/constants'
import Pagination from 'web-pkg/src/components/Pagination.vue'
import SpaceContextActions from '../../components/Spaces/SpaceContextActions.vue'
import { isProjectSpaceResource, SpaceResource } from 'web-client/src/helpers'
import { isProjectSpaceResource, Resource, SpaceResource } from 'web-client/src/helpers'
import SideBar from '../../components/SideBar/SideBar.vue'
import FilesViewWrapper from '../../components/FilesViewWrapper.vue'
import ResourceTiles from '../../components/FilesList/ResourceTiles.vue'
Expand All @@ -158,7 +186,7 @@ import { WebDAV } from 'web-client/src/webdav'
import { useScrollTo } from 'web-app-files/src/composables/scrollTo'
import { useSelectedResources } from 'web-app-files/src/composables'
import { sortFields as availableSortFields } from '../../helpers/ui/resourceTiles'
import { formatFileSize } from 'web-pkg/src'
import { defaultFuseOptions, formatFileSize } from 'web-pkg/src'
import { useGettext } from 'vue3-gettext'
import { spaceRoleEditor, spaceRoleManager, spaceRoleViewer } from 'web-client/src/helpers/share'
import { useKeyboardActions } from 'web-pkg/src/composables/keyboardActions'
Expand All @@ -167,6 +195,7 @@ import {
useKeyboardTableMouseActions,
useKeyboardTableActions
} from 'web-app-files/src/composables/keyboardActions'
import { orderBy } from 'lodash-es'
export default defineComponent({
components: {
Expand All @@ -175,17 +204,22 @@ export default defineComponent({
CreateSpace,
FilesViewWrapper,
NoContentMessage,
Pagination,
ResourceTiles,
ResourceTable,
SideBar,
SpaceContextActions
},
setup() {
const store = useStore()
const router = useRouter()
const route = useRoute()
const clientService = useClientService()
const { selectedResourcesIds } = useSelectedResources({ store })
const { can } = useAbility()
const { current: currentLanguage, $gettext } = useGettext()
const filterTerm = ref('')
const markInstance = ref(undefined)
const runtimeSpaces = computed((): SpaceResource[] => {
return store.getters['runtime/spaces/spaces'].filter((s) => isProjectSpaceResource(s)) || []
Expand Down Expand Up @@ -213,6 +247,44 @@ export default defineComponent({
items: runtimeSpaces,
fields: sortFields
})
const filter = (spaces: Array<Resource>, filterTerm: string) => {
if (!(filterTerm || '').trim()) {
return spaces
}
const searchEngine = new Fuse(spaces, { ...defaultFuseOptions, keys: ['name'] })
return searchEngine.search(filterTerm).map((r) => r.item)
}
const items = computed(() =>
orderBy(
filter(unref(spaces), unref(filterTerm)),
unref(sortBy),
unref(sortDir) === SortDir.Desc
)
)
const {
items: paginatedItems,
page: currentPage,
total: totalPages
} = usePagination({
items,
perPageDefault: '50',
perPageStoragePrefix: 'spaces-list'
})
watch(filterTerm, async () => {
const instance = unref(markInstance)
if (!instance) {
return
}
await router.push({ ...unref(route), query: { ...unref(route).query, page: '1' } })
instance.unmark()
instance.mark(unref(filterTerm), {
element: 'span',
className: 'highlight-mark',
exclude: ['th *', 'tfoot *']
})
})
const { scrollToResourceFromRoute } = useScrollTo()
Expand Down Expand Up @@ -284,6 +356,20 @@ export default defineComponent({
onMounted(async () => {
await loadResourcesTask.perform()
scrollToResourceFromRoute(unref(spaces))
nextTick(() => {
markInstance.value = new Mark('.spaces-table')
})
})
const footerTextTotal = computed(() => {
return $gettext('%{spaceCount} spaces in total', {
spaceCount: unref(spaces).length.toString()
})
})
const footerTextFilter = computed(() => {
return $gettext('%{spaceCount} matching spaces', {
spaceCount: unref(items).length.toString()
})
})
return {
Expand All @@ -306,7 +392,14 @@ export default defineComponent({
getTotalQuota,
getUsedQuota,
getRemainingQuota,
getMemberCount
getMemberCount,
paginatedItems,
filterTerm,
totalPages,
currentPage,
footerTextTotal,
footerTextFilter,
items
}
},
data: function () {
Expand Down
16 changes: 16 additions & 0 deletions packages/web-app-files/tests/unit/views/spaces/Projects.spec.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import Projects from '../../../../src/views/spaces/Projects.vue'
import { mock } from 'jest-mock-extended'
import { nextTick } from 'vue'
import { queryItemAsString } from 'web-pkg'

import {
createStore,
Expand All @@ -11,6 +13,12 @@ import {
RouteLocation
} from 'web-test-helpers'

jest.mock('web-pkg/src/helpers', () => ({
...jest.requireActual('web-pkg/src/helpers'),
displayPositionedDropdown: jest.fn()
}))
jest.mock('web-pkg/src/composables/appDefaults')

const spacesResources = [
{
id: '1',
Expand Down Expand Up @@ -64,6 +72,12 @@ describe('Projects view', () => {
expect(wrapper.find('.no-content-message').exists()).toBeFalsy()
expect(wrapper.find('.spaces-list').exists()).toBeTruthy()
})
it('shows only filtered spaces if filter applied', async () => {
const { wrapper } = getMountedWrapper({ spaces: spacesResources })
wrapper.vm.filterTerm = 'Some other space'
await nextTick()
expect(wrapper.vm.items).toEqual([spacesResources[1]])
})
})
it('should display the "Create Space"-button when permission given', () => {
const { wrapper } = getMountedWrapper({
Expand All @@ -75,6 +89,8 @@ describe('Projects view', () => {
})

function getMountedWrapper({ mocks = {}, spaces = [], abilities = [], stubAppBar = true } = {}) {
jest.mocked(queryItemAsString).mockImplementationOnce(() => '1')
jest.mocked(queryItemAsString).mockImplementationOnce(() => '100')
const defaultMocks = {
...defaultComponentMocks({
currentRoute: mock<RouteLocation>({ name: 'files-spaces-projects' })
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,16 @@ exports[`Projects view different files view states lists all available project s
<div id="files-view">
<app-bar-stub breadcrumbs="[object Object]" breadcrumbscontextactionsitems="" hasbulkactions="true" hasfileextensions="false" hashiddenfiles="false" haspagination="false" hassharesnavigation="false" hassidebartoggle="true" hasviewoptions="true" showactionsonselection="true" sidebaropen="false" viewmodedefault="resource-tiles" viewmodes="[object Object],[object Object]"></app-bar-stub>
<div class="spaces-list oc-mt-l">
<resource-table-stub arepathsdisplayed="false" areresourcesclickable="true" arethumbnailsdisplayed="true" class="spaces-table" dragdrop="false" fieldsdisplayed="image,name,manager,members,totalQuota,usedQuota,remainingQuota,status,mdate" hasactions="true" headerposition="0" hover="true" isselectable="true" paddingx="small" resourcedomselector="[Function]" resources="[object Object],[object Object]" selectedids="" sort-fields="[object Object],[object Object],[object Object],[object Object],[object Object],[object Object],[object Object],[object Object],[object Object]" sortby="[Function]" sortdir="desc" sticky="false" viewmode="resource-table"></resource-table-stub>
<div class="oc-ml-m oc-my-m">
<label class="oc-label" for="spaces-filter">Search</label>
<div class="oc-position-relative">
<!--v-if-->
<input aria-invalid="false" autocomplete="off" class="oc-text-input oc-input oc-rounded" id="spaces-filter" type="text">
<!--v-if-->
</div>
<!--v-if-->
</div>
<resource-table-stub arepathsdisplayed="false" areresourcesclickable="true" arethumbnailsdisplayed="true" class="spaces-table" dragdrop="false" fieldsdisplayed="image,name,manager,members,totalQuota,usedQuota,remainingQuota,status,mdate" hasactions="true" headerposition="0" hover="true" isselectable="true" paddingx="small" resourcedomselector="[Function]" resources="" selectedids="" sort-fields="[object Object],[object Object],[object Object],[object Object],[object Object],[object Object],[object Object],[object Object],[object Object]" sortby="[Function]" sortdir="desc" sticky="false" viewmode="resource-table"></resource-table-stub>
</div>
</div>
</div>
Expand Down

0 comments on commit 3441554

Please sign in to comment.