From fcb81f291658a32bd498451644a21ee36905a9a1 Mon Sep 17 00:00:00 2001 From: Denis Bykhov <80476319+BykhovDenis@users.noreply.github.com> Date: Sat, 21 May 2022 23:17:10 +0600 Subject: [PATCH] Tags filter (#1820) Signed-off-by: Denis Bykhov <80476319+BykhovDenis@users.noreply.github.com> --- models/recruit/src/index.ts | 2 +- models/server-tags/src/index.ts | 4 + models/tags/src/index.ts | 9 +- models/tags/src/migration.ts | 34 ++- models/tags/src/plugin.ts | 3 +- packages/presentation/src/utils.ts | 16 +- .../src/components/Candidates.svelte | 35 +-- plugins/tags-resources/package.json | 1 + .../src/components/TagsFilter.svelte | 229 ++++++++++++++++++ plugins/tags-resources/src/index.ts | 2 + plugins/tags/src/index.ts | 1 + plugins/view-assets/lang/en.json | 3 +- plugins/view-assets/lang/ru.json | 3 +- .../src/components/filter/FilterBar.svelte | 31 ++- .../components/filter/FilterTypePopup.svelte | 26 +- .../src/components/filter/ObjectFilter.svelte | 31 +-- .../components/filter/TimestampFilter.svelte | 4 +- .../src/components/filter/ValueFilter.svelte | 47 ++-- plugins/view-resources/src/plugin.ts | 3 +- plugins/view/src/index.ts | 4 +- server-plugins/tags-resources/src/index.ts | 76 +++++- server-plugins/tags/src/index.ts | 4 + 22 files changed, 458 insertions(+), 110 deletions(-) create mode 100644 plugins/tags-resources/src/components/TagsFilter.svelte diff --git a/models/recruit/src/index.ts b/models/recruit/src/index.ts index ad79749ae3..6208ed9284 100644 --- a/models/recruit/src/index.ts +++ b/models/recruit/src/index.ts @@ -452,7 +452,7 @@ export function createModel (builder: Builder): void { }) builder.mixin(recruit.mixin.Candidate, core.class.Class, view.mixin.ClassFilters, { - filters: ['title', 'source', 'city', 'modifiedOn', 'onsite', 'remote'] + filters: ['title', 'source', 'city', 'skills', 'modifiedOn', 'onsite', 'remote'] }) builder.mixin(recruit.class.Applicant, core.class.Class, view.mixin.ClassFilters, { diff --git a/models/server-tags/src/index.ts b/models/server-tags/src/index.ts index 01c1f5aadd..e939fe228d 100644 --- a/models/server-tags/src/index.ts +++ b/models/server-tags/src/index.ts @@ -20,6 +20,10 @@ import serverTags from '@anticrm/server-tags' import tags from '@anticrm/tags' export function createModel (builder: Builder): void { + builder.createDoc(serverCore.class.Trigger, core.space.Model, { + trigger: serverTags.trigger.onTagReference + }) + builder.mixin, ObjectDDParticipant>( tags.class.TagElement, core.class.Class, diff --git a/models/tags/src/index.ts b/models/tags/src/index.ts index 5e5c8d0d76..2a4598a883 100644 --- a/models/tags/src/index.ts +++ b/models/tags/src/index.ts @@ -13,7 +13,7 @@ // limitations under the License. import { Class, Doc, Domain, IndexKind, Ref } from '@anticrm/core' -import { ArrOf, Builder, Index, Model, Prop, TypeRef, TypeString, UX } from '@anticrm/model' +import { ArrOf, Builder, Index, Model, Prop, TypeNumber, TypeRef, TypeString, UX } from '@anticrm/model' import core, { TAttachedDoc, TDoc } from '@anticrm/model-core' import view from '@anticrm/model-view' import { Asset, IntlString } from '@anticrm/platform' @@ -45,6 +45,9 @@ export class TTagElement extends TDoc implements TagElement { @Prop(TypeRef(tags.class.TagCategory), tags.string.CategoryLabel) category!: Ref + + @Prop(TypeNumber(), tags.string.TagReference) + refCount?: number } @Model(tags.class.TagReference, core.class.AttachedDoc, DOMAIN_TAGS) @@ -94,4 +97,8 @@ export function createModel (builder: Builder): void { builder.mixin(tags.class.TagElement, core.class.Class, view.mixin.AttributePresenter, { presenter: tags.component.TagElementPresenter }) + + builder.mixin(tags.class.TagReference, core.class.Class, view.mixin.AttributeFilter, { + component: tags.component.TagsFilter + }) } diff --git a/models/tags/src/migration.ts b/models/tags/src/migration.ts index a2869993cb..d1f84168a1 100644 --- a/models/tags/src/migration.ts +++ b/models/tags/src/migration.ts @@ -1,8 +1,36 @@ -import core, { TxOperations } from '@anticrm/core' -import { MigrateOperation, MigrationClient, MigrationUpgradeClient } from '@anticrm/model' +import core, { Ref, TxOperations } from '@anticrm/core' +import { MigrateOperation, MigrationClient, MigrationResult, MigrationUpgradeClient } from '@anticrm/model' +import { TagElement, TagReference } from '@anticrm/tags' import { DOMAIN_TAGS } from './index' import tags from './plugin' +async function updateTagRefCount (client: MigrationClient): Promise { + const tagElements = await client.find(DOMAIN_TAGS, { _class: tags.class.TagElement, refCount: { $exists: false } }) + const refs = await client.find(DOMAIN_TAGS, { + _class: tags.class.TagReference, + tag: { $in: tagElements.map((p) => p._id as Ref) } + }) + const map = new Map, number>() + for (const ref of refs) { + map.set(ref.tag, (map.get(ref.tag) ?? 0) + 1) + } + const promises: Promise[] = [] + for (const tag of map) { + promises.push( + client.update( + DOMAIN_TAGS, + { + _id: tag[0] + }, + { + refCount: tag[1] + } + ) + ) + } + await Promise.all(promises) +} + export const tagsOperation: MigrateOperation = { async migrate (client: MigrationClient): Promise { await client.update( @@ -15,6 +43,8 @@ export const tagsOperation: MigrateOperation = { category: 'recruit:category:Other' } ) + + await updateTagRefCount(client) }, async upgrade (client: MigrationUpgradeClient): Promise { const tx = new TxOperations(client, core.account.System) diff --git a/models/tags/src/plugin.ts b/models/tags/src/plugin.ts index 175ab2af54..b5f3ef0a7c 100644 --- a/models/tags/src/plugin.ts +++ b/models/tags/src/plugin.ts @@ -25,7 +25,8 @@ export default mergeIds(tagsId, tags, { Tags: '' as AnyComponent, TagReferencePresenter: '' as AnyComponent, TagsPresenter: '' as AnyComponent, - TagsItemPresenter: '' as AnyComponent + TagsItemPresenter: '' as AnyComponent, + TagsFilter: '' as AnyComponent }, string: { TagElementLabel: '' as IntlString, diff --git a/packages/presentation/src/utils.ts b/packages/presentation/src/utils.ts index 92f5877ef6..ae89796555 100644 --- a/packages/presentation/src/utils.ts +++ b/packages/presentation/src/utils.ts @@ -78,11 +78,13 @@ export class LiveQuery { private oldCallback: ((result: FindResult) => void) | undefined unsubscribe = () => {} - constructor () { - onDestroy(() => { - console.log('onDestroy query') - this.unsubscribe() - }) + constructor (dontDestroy: boolean = false) { + if (!dontDestroy) { + onDestroy(() => { + console.log('onDestroy query') + this.unsubscribe() + }) + } } query( @@ -121,8 +123,8 @@ export class LiveQuery { } } -export function createQuery (): LiveQuery { - return new LiveQuery() +export function createQuery (dontDestroy?: boolean): LiveQuery { + return new LiveQuery(dontDestroy) } export function getFileUrl (file: string): string { diff --git a/plugins/recruit-resources/src/components/Candidates.svelte b/plugins/recruit-resources/src/components/Candidates.svelte index 8293ade2ed..b4c7f0b138 100644 --- a/plugins/recruit-resources/src/components/Candidates.svelte +++ b/plugins/recruit-resources/src/components/Candidates.svelte @@ -15,10 +15,9 @@ --> + + + +
+
+ +
+
+
+ {#each categories as cat} + {#if objects.filter((el) => el.category === cat._id).length > 0} +
+ + +
+ {/if} + {/each} +
+
+
diff --git a/plugins/tags-resources/src/index.ts b/plugins/tags-resources/src/index.ts index 6a081699d2..0c3c960f0b 100644 --- a/plugins/tags-resources/src/index.ts +++ b/plugins/tags-resources/src/index.ts @@ -26,6 +26,7 @@ import TagsItemPresenter from './components/TagsItemPresenter.svelte' import TagsPresenter from './components/TagsPresenter.svelte' import TagsView from './components/TagsView.svelte' import TagElementCountPresenter from './components/TagElementCountPresenter.svelte' +import TagsFilter from './components/TagsFilter.svelte' export default async (): Promise => ({ component: { @@ -34,6 +35,7 @@ export default async (): Promise => ({ TagElementPresenter, TagsPresenter, TagsView, + TagsFilter, TagsEditor, TagsDropdownEditor, TagsItemPresenter, diff --git a/plugins/tags/src/index.ts b/plugins/tags/src/index.ts index 89bf390238..4d6ef2b5dc 100644 --- a/plugins/tags/src/index.ts +++ b/plugins/tags/src/index.ts @@ -28,6 +28,7 @@ export interface TagElement extends Doc { description: string color: number category: Ref + refCount?: number } /** diff --git a/plugins/view-assets/lang/en.json b/plugins/view-assets/lang/en.json index 3cded693be..86ddfd4430 100644 --- a/plugins/view-assets/lang/en.json +++ b/plugins/view-assets/lang/en.json @@ -45,6 +45,7 @@ "FilterIsEither": "is either of", "FilterStatesCount": "{value, plural, =1 {1 state} other {# states}}", "Before": "Before", - "After": "After" + "After": "After", + "Apply": "Apply" } } diff --git a/plugins/view-assets/lang/ru.json b/plugins/view-assets/lang/ru.json index 9bfa86a8d3..24f2dac41e 100644 --- a/plugins/view-assets/lang/ru.json +++ b/plugins/view-assets/lang/ru.json @@ -43,6 +43,7 @@ "FilterIsEither": "один из", "FilterStatesCount": "{value, plural, =1 {1 состоянию} other {# состояний}}", "Before": "До", - "After": "После" + "After": "После", + "Apply": "Применить" } } diff --git a/plugins/view-resources/src/components/filter/FilterBar.svelte b/plugins/view-resources/src/components/filter/FilterBar.svelte index cc3c5e0a40..6a5f7542b1 100644 --- a/plugins/view-resources/src/components/filter/FilterBar.svelte +++ b/plugins/view-resources/src/components/filter/FilterBar.svelte @@ -30,29 +30,29 @@ const dispatch = createEventDispatcher() let filters: Filter[] = [] - let isNew = true + let maxIndex = 0 function onChange (e: Filter | undefined) { if (e === undefined) return - if (isNew) { + const index = filters.findIndex((p) => p.index === e.index) + if (index === -1) { filters.push(e) - isNew = false filters = filters } else { - filters[filters.length - 1] = e + filters[index] = e filters = filters } } function add (e: MouseEvent) { const target = eventToHTMLElement(e) - isNew = true showPopup( FilterTypePopup, { _class, - makeQuery: (key: string) => makeQuery(query, filters, key), + query, target, + index: ++maxIndex, onChange }, target @@ -60,19 +60,21 @@ } function remove (i: number) { + filters[i]?.onRemove?.() filters.splice(i, 1) filters = filters } - function makeQuery (query: DocumentQuery, filters: Filter[], skipKey?: string): DocumentQuery { + async function makeQuery (query: DocumentQuery, filters: Filter[]): Promise { const newQuery = hierarchy.clone(query) for (let i = 0; i < filters.length; i++) { const filter = filters[i] - if (skipKey !== undefined && filter.key.key === skipKey) continue + const newValue = await filter.mode.result(filter.value, () => { + makeQuery(query, filters) + }) if (newQuery[filter.key.key] === undefined) { - newQuery[filter.key.key] = filter.mode.result(filter.value) + newQuery[filter.key.key] = newValue } else { - const newValue = filter.mode.result(filter.value) let merged = false for (const key in newValue) { if (newQuery[filter.key.key][key] === undefined) { @@ -104,14 +106,11 @@ } } if (!merged) { - Object.assign(newQuery[filter.key.key], filter.mode.result(filter.value)) + Object.assign(newQuery[filter.key.key], newValue) } } } - if (skipKey === undefined) { - dispatch('change', newQuery) - } - return newQuery + dispatch('change', newQuery) } $: makeQuery(query, filters) @@ -125,7 +124,7 @@ {#each filters as filter, i} { makeQuery(query, filters) diff --git a/plugins/view-resources/src/components/filter/FilterTypePopup.svelte b/plugins/view-resources/src/components/filter/FilterTypePopup.svelte index c669fab5c7..1c11d02821 100644 --- a/plugins/view-resources/src/components/filter/FilterTypePopup.svelte +++ b/plugins/view-resources/src/components/filter/FilterTypePopup.svelte @@ -13,7 +13,17 @@ // limitations under the License. -->
- {}} - on:change - /> +
@@ -154,7 +149,7 @@
-
+
{targets.get(value?._id)}
@@ -163,4 +158,12 @@ {/await}
+