Fix TSK-96 (#2052)

This commit is contained in:
Andrey Sobolev 2022-06-10 22:48:14 +07:00 committed by GitHub
parent 77ee575bbd
commit 546eac5dee
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
27 changed files with 206 additions and 120 deletions

View File

@ -35,10 +35,9 @@ import {
import attachment from '@anticrm/model-attachment'
import chunter from '@anticrm/model-chunter'
import core, { TAttachedDoc, TClass, TDoc, TSpace } from '@anticrm/model-core'
import presentation from '@anticrm/model-presentation'
import view, { actionTemplates as viewTemplates, createAction, template } from '@anticrm/model-view'
import { IntlString } from '@anticrm/platform'
import { ViewAction } from '@anticrm/view'
import tags from '@anticrm/tags'
import {
DOMAIN_STATE,
DoneState,
@ -60,7 +59,7 @@ import {
WonStateTemplate
} from '@anticrm/task'
import { AnyComponent } from '@anticrm/ui'
import tags from '@anticrm/tags'
import { ViewAction } from '@anticrm/view'
import task from './plugin'
export { createKanbanTemplate, createSequence, taskOperation } from './migration'
@ -501,17 +500,6 @@ export function createModel (builder: Builder): void {
target: task.class.TodoItem
})
builder.createDoc(
presentation.class.ObjectSearchCategory,
core.space.Model,
{
icon: task.icon.Task,
label: task.string.SearchTask,
query: task.completion.IssueQuery
},
task.completion.IssueCategory
)
createAction(
builder,
{

View File

@ -15,8 +15,6 @@
//
import type { Ref, Space } from '@anticrm/core'
import { ObjectSearchCategory, ObjectSearchFactory } from '@anticrm/model-presentation'
import type { Resource } from '@anticrm/platform'
import { mergeIds } from '@anticrm/platform'
import { TagCategory } from '@anticrm/tags'
import { KanbanTemplate, taskId } from '@anticrm/task'
@ -66,10 +64,6 @@ export default mergeIds(taskId, task, {
template: {
DefaultProject: '' as Ref<KanbanTemplate>
},
completion: {
IssueQuery: '' as Resource<ObjectSearchFactory>,
IssueCategory: '' as Ref<ObjectSearchCategory>
},
viewlet: {
TableIssue: '' as Ref<Viewlet>
}

View File

@ -51,6 +51,8 @@ import {
} from '@anticrm/tracker'
import tracker from './plugin'
import presentation from '@anticrm/model-presentation'
export { trackerOperation } from './migration'
export { default } from './plugin'
@ -568,4 +570,15 @@ export function createModel (builder: Builder): void {
builder.mixin(tracker.class.Issue, core.class.Class, view.mixin.ClassFilters, {
filters: ['status', 'priority', 'project']
})
builder.createDoc(
presentation.class.ObjectSearchCategory,
core.space.Model,
{
icon: tracker.icon.TrackerApplication,
label: tracker.string.SearchIssue,
query: tracker.completion.IssueQuery
},
tracker.completion.IssueCategory
)
}

View File

@ -15,7 +15,8 @@
//
import { Ref } from '@anticrm/core'
import { IntlString, mergeIds } from '@anticrm/platform'
import { ObjectSearchCategory, ObjectSearchFactory } from '@anticrm/model-presentation'
import { IntlString, mergeIds, Resource } from '@anticrm/platform'
import { Team, trackerId } from '@anticrm/tracker'
import tracker from '@anticrm/tracker-resources/src/plugin'
import type { AnyComponent } from '@anticrm/ui'
@ -31,7 +32,8 @@ export default mergeIds(trackerId, tracker, {
GotoBacklog: '' as IntlString,
GotoBoard: '' as IntlString,
GotoProjects: '' as IntlString,
GotoTrackerApplication: '' as IntlString
GotoTrackerApplication: '' as IntlString,
SearchIssue: '' as IntlString
},
team: {
DefaultTeam: '' as Ref<Team>
@ -45,5 +47,9 @@ export default mergeIds(trackerId, tracker, {
},
viewlet: {
List: '' as Ref<ViewletDescriptor>
},
completion: {
IssueQuery: '' as Resource<ObjectSearchFactory>,
IssueCategory: '' as Ref<ObjectSearchCategory>
}
})

View File

@ -93,7 +93,9 @@ export class LiveQuery {
callback: (result: FindResult<T>) => void,
options?: FindOptions<T>
): boolean {
console.log(_class, query, callback, options)
if (!this.needUpdate(_class, query, callback, options)) {
console.log('matched')
return false
}
this.oldCallback = callback
@ -104,6 +106,10 @@ export class LiveQuery {
const unsub = liveQuery.query(_class, query, callback, options)
this.unsubscribe = () => {
unsub()
this.oldCallback = undefined
this.oldClass = undefined
this.oldOptions = undefined
this.oldQuery = undefined
this.unsubscribe = () => {}
}
return true

View File

@ -170,8 +170,10 @@
selected = i
}}
>
<div class="ml-2 mt-1 mb-1">
<svelte:component this={item.component} value={item.doc} {...item.componentProps ?? {}} />
</div>
</div>
{/each}
{#if !items.length}
<div class="ap-menuItem empty"><Label label={plugin.string.NoItems} /></div>

View File

@ -29,6 +29,8 @@
personQuery.query(contact.class.Contact, { _id: object.contact }, (result) => {
refContact = result[0]
})
} else {
personQuery.unsubscribe()
}
let organization: Organization
@ -37,6 +39,8 @@
orgQuery.query(contact.class.Organization, { _id: object.attachedTo as Ref<Organization> }, (result) => {
organization = result[0]
})
} else {
orgQuery.unsubscribe()
}
const dispatch = createEventDispatcher()

View File

@ -34,7 +34,7 @@
</script>
<div class="flex item">
<Icon icon={recruit.icon.Application} size={'large'} />
<Icon icon={recruit.icon.Application} size={'medium'} />
<div class="ml-2">
{#if shortLabel}<Label label={shortLabel} />-{/if}{value.number}
</div>

View File

@ -31,6 +31,8 @@
templatesQ.query(task.class.KanbanTemplate, { space: folder._id as Ref<Doc> as Ref<Space> }, (result) => {
templates = result
})
} else {
templatesQ.unsubscribe()
}
let selectedId: Ref<KanbanTemplate> | undefined

View File

@ -54,6 +54,8 @@
}
}
)
} else {
elementsQuery.unsubscribe()
}
type TagElementInfo = { count: number; modifiedOn: number }

View File

@ -63,7 +63,6 @@
"AllStates": "All states",
"DoneStates": "Done states",
"States": "States",
"SearchTask": "Search for task...",
"NoDoneState": "Not done",
"ManageStatusesWithin": "Manage application statuses within",
"ManageProjectStatues": "Manage project statues",

View File

@ -63,7 +63,6 @@
"AllStates": "Все статусы",
"DoneStates": "Завершенные статусы",
"States": "Статусы",
"SearchTask": "Поиск задачи...",
"NoDoneState": "Не завершено",
"ManageStatusesWithin": "Управление статусами для",
"ManageProjectStatues": "Управление статусами задачи",

View File

@ -30,6 +30,8 @@
wonStates = result.filter((x) => x._class === task.class.WonState)
lostStates = result.filter((x) => x._class === task.class.LostState)
})
} else {
doneStatesQ.unsubscribe()
}
let hoveredDoneState: Ref<DoneState> | undefined

View File

@ -45,6 +45,8 @@
},
{ limit: 1 }
)
} else {
query.unsubscribe()
}
</script>

View File

@ -14,11 +14,10 @@
// limitations under the License.
//
import { Class, Client, Ref } from '@anticrm/core'
import { IntlString, Resources, translate } from '@anticrm/platform'
import { ObjectSearchResult } from '@anticrm/presentation'
import { SpaceWithStates, Task } from '@anticrm/task'
import { Resources } from '@anticrm/platform'
import { SpaceWithStates } from '@anticrm/task'
import { showPopup } from '@anticrm/ui'
import AssignedTasks from './components/AssignedTasks.svelte'
import CreateProject from './components/CreateProject.svelte'
import EditIssue from './components/EditIssue.svelte'
import KanbanTemplateEditor from './components/kanban/KanbanTemplateEditor.svelte'
@ -32,59 +31,17 @@ import StateEditor from './components/state/StateEditor.svelte'
import StatePresenter from './components/state/StatePresenter.svelte'
import StatusTableView from './components/StatusTableView.svelte'
import TaskHeader from './components/TaskHeader.svelte'
import TaskItem from './components/TaskItem.svelte'
import TaskPresenter from './components/TaskPresenter.svelte'
import TemplatesIcon from './components/TemplatesIcon.svelte'
import TodoItemPresenter from './components/todos/TodoItemPresenter.svelte'
import TodoItemsPopup from './components/todos/TodoItemsPopup.svelte'
import Todos from './components/todos/Todos.svelte'
import TodoStatePresenter from './components/todos/TodoStatePresenter.svelte'
import AssignedTasks from './components/AssignedTasks.svelte'
import task from './plugin'
async function editStatuses (object: SpaceWithStates): Promise<void> {
showPopup(EditStatuses, { _id: object._id, spaceClass: object._class }, 'float')
}
export async function queryTask<D extends Task> (
_class: Ref<Class<D>>,
client: Client,
search: string
): Promise<ObjectSearchResult[]> {
const cl = client.getHierarchy().getClass(_class)
const shortLabel = (await translate(cl.shortLabel ?? ('' as IntlString), {})).toUpperCase()
// Check number pattern
const sequence = (await client.findOne(task.class.Sequence, { attachedTo: _class }))?.sequence ?? 0
const named = new Map(
(await client.findAll<Task>(_class, { name: { $like: `%${search}%` } }, { limit: 200 })).map((e) => [e._id, e])
)
const nids: number[] = []
if (sequence > 0) {
for (let n = 0; n < sequence; n++) {
const v = `${n}`
if (v.includes(search)) {
nids.push(n)
}
}
const numbered = await client.findAll<Task>(_class, { number: { $in: nids } }, { limit: 200 })
for (const d of numbered) {
if (!named.has(d._id)) {
named.set(d._id, d)
}
}
}
return Array.from(named.values()).map((e) => ({
doc: e,
title: `${shortLabel}-${e.number}`,
icon: task.icon.Task,
component: TaskItem
}))
}
export type StatesBarPosition = 'start' | 'middle' | 'end' | undefined
export default async (): Promise<Resources> => ({
@ -111,8 +68,5 @@ export default async (): Promise<Resources> => ({
},
actionImpl: {
EditStatuses: editStatuses
},
completion: {
IssueQuery: async (client: Client, query: string) => await queryTask(task.class.Issue, client, query)
}
})

View File

@ -221,7 +221,6 @@ const task = plugin(taskId, {
Kanban: '' as IntlString,
ApplicationLabelTask: '' as IntlString,
Projects: '' as IntlString,
SearchTask: '' as IntlString,
ManageProjectStatues: '' as IntlString,
TodoItems: '' as IntlString
},

View File

@ -139,7 +139,8 @@
"IncludeItemsThatMatch": "Include items that match",
"AnyFilter": "any filter",
"AllFilters": "all filters",
"NoDescription": "No description"
"NoDescription": "No description",
"SearchIssue": "Search for task..."
},
"status": {}
}

View File

@ -89,7 +89,8 @@
"IncludeItemsThatMatch": "Включить элементы, которые соответствуют",
"AnyFilter": "любому фильтру",
"AllFilters": "всем фильтрам",
"NoDescription": "Нет описания"
"NoDescription": "Нет описания",
"SearchIssue": "Поиск задачи..."
},
"status": {}
}

View File

@ -0,0 +1,38 @@
<!--
// Copyright © 2020, 2021 Anticrm Platform Contributors.
// Copyright © 2021 Hardcore Engineering Inc.
//
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. You may
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//
// See the License for the specific language governing permissions and
// limitations under the License.
-->
<script lang="ts">
import { WithLookup } from '@anticrm/core'
import type { Issue, Team } from '@anticrm/tracker'
import { Icon } from '@anticrm/ui'
import tracker from '../../plugin'
import { getIssueId } from '../../utils'
export let value: WithLookup<Issue>
$: title = getIssueId(value.$lookup?.space as Team, value)
</script>
<div class="flex item">
<Icon icon={tracker.icon.TrackerApplication} size={'medium'} />
<div class="ml-2">
{title} - {value.title}
</div>
</div>
<style lang="scss">
.item {
align-items: center;
}
</style>

View File

@ -33,6 +33,8 @@
$: if (!currentTeam) {
spaceQuery.query(tracker.class.Team, { _id: value.space }, (res) => ([currentTeam] = res))
} else {
spaceQuery.unsubscribe()
}
$: issueName = currentTeam && getIssueId(currentTeam, value)
</script>

View File

@ -86,13 +86,16 @@
$: areSubIssuesLoading = !subIssues
$: parentIssue = issue.$lookup?.attachedTo ? (issue.$lookup?.attachedTo as Issue) : null
$: parentIssue &&
$: if (parentIssue) {
subIssuesQeury.query(
tracker.class.Issue,
{ space: issue.space, attachedTo: parentIssue._id },
(res) => (subIssues = res),
{ sort: { modifiedOn: SortingOrder.Descending } }
)
} else {
subIssuesQeury.unsubscribe()
}
</script>
{#if parentIssue}

View File

@ -37,19 +37,25 @@
closePopup()
projectId = loc.path[4] as Ref<Project>
console.log('PROJECT SELECTED', projectId)
})
)
const projectQuery = createQuery()
$: if (projectId !== undefined) {
console.log('call query for', projectId)
projectQuery.query(tracker.class.Project, { _id: projectId }, (result) => {
;[project] = result
project = result.shift()
console.log('recieve result for', projectId, project)
})
} else {
projectQuery.unsubscribe()
project = undefined
}
</script>
{projectId}
{JSON.stringify(project)}
{#if project}
<EditProject {project} />
{:else}

View File

@ -13,47 +13,99 @@
// limitations under the License.
//
import { Class, Client, Ref } from '@anticrm/core'
import { Resources } from '@anticrm/platform'
import NopeComponent from './components/NopeComponent.svelte'
import { ObjectSearchResult } from '@anticrm/presentation'
import { Issue, Team } from '@anticrm/tracker'
import Inbox from './components/inbox/Inbox.svelte'
import Active from './components/issues/Active.svelte'
import AssigneePresenter from './components/issues/AssigneePresenter.svelte'
import Backlog from './components/issues/Backlog.svelte'
import Board from './components/issues/Board.svelte'
import Inbox from './components/inbox/Inbox.svelte'
import Issues from './components/issues/Issues.svelte'
import MyIssues from './components/myissues/MyIssues.svelte'
import Projects from './components/projects/Projects.svelte'
import ProjectPresenter from './components/projects/ProjectPresenter.svelte'
import ProjectTitlePresenter from './components/projects/ProjectTitlePresenter.svelte'
import Views from './components/views/Views.svelte'
import IssuePresenter from './components/issues/IssuePresenter.svelte'
import TitlePresenter from './components/issues/TitlePresenter.svelte'
import PriorityPresenter from './components/issues/PriorityPresenter.svelte'
import PriorityEditor from './components/issues/PriorityEditor.svelte'
import ProjectEditor from './components/projects/ProjectEditor.svelte'
import StatusPresenter from './components/issues/StatusPresenter.svelte'
import StatusEditor from './components/issues/StatusEditor.svelte'
import SetDueDateActionPopup from './components/SetDueDateActionPopup.svelte'
import SetParentIssueActionPopup from './components/SetParentIssueActionPopup.svelte'
import DueDatePresenter from './components/issues/DueDatePresenter.svelte'
import AssigneePresenter from './components/issues/AssigneePresenter.svelte'
import EditIssue from './components/issues/edit/EditIssue.svelte'
import IssueItem from './components/issues/IssueItem.svelte'
import IssuePresenter from './components/issues/IssuePresenter.svelte'
import IssuePreview from './components/issues/IssuePreview.svelte'
import Issues from './components/issues/Issues.svelte'
import IssuesView from './components/issues/IssuesView.svelte'
import ListView from './components/issues/ListView.svelte'
import ModificationDatePresenter from './components/issues/ModificationDatePresenter.svelte'
import PriorityEditor from './components/issues/PriorityEditor.svelte'
import PriorityPresenter from './components/issues/PriorityPresenter.svelte'
import StatusEditor from './components/issues/StatusEditor.svelte'
import StatusPresenter from './components/issues/StatusPresenter.svelte'
import TitlePresenter from './components/issues/TitlePresenter.svelte'
import ViewOptionsPopup from './components/issues/ViewOptionsPopup.svelte'
import MyIssues from './components/myissues/MyIssues.svelte'
import NewIssueHeader from './components/NewIssueHeader.svelte'
import NopeComponent from './components/NopeComponent.svelte'
import EditProject from './components/projects/EditProject.svelte'
import IconPresenter from './components/projects/IconPresenter.svelte'
import LeadPresenter from './components/projects/LeadPresenter.svelte'
import TargetDatePresenter from './components/projects/TargetDatePresenter.svelte'
import ProjectEditor from './components/projects/ProjectEditor.svelte'
import ProjectMembersPresenter from './components/projects/ProjectMembersPresenter.svelte'
import ProjectStatusPresenter from './components/projects/ProjectStatusPresenter.svelte'
import EditProject from './components/projects/EditProject.svelte'
import ProjectPresenter from './components/projects/ProjectPresenter.svelte'
import Projects from './components/projects/Projects.svelte'
import ProjectStatusEditor from './components/projects/ProjectStatusEditor.svelte'
import ProjectStatusPresenter from './components/projects/ProjectStatusPresenter.svelte'
import ProjectTitlePresenter from './components/projects/ProjectTitlePresenter.svelte'
import TargetDatePresenter from './components/projects/TargetDatePresenter.svelte'
import SetDueDateActionPopup from './components/SetDueDateActionPopup.svelte'
import SetParentIssueActionPopup from './components/SetParentIssueActionPopup.svelte'
import Views from './components/views/Views.svelte'
import tracker from './plugin'
import { getIssueId } from './utils'
import ModificationDatePresenter from './components/issues/ModificationDatePresenter.svelte'
import EditIssue from './components/issues/edit/EditIssue.svelte'
import NewIssueHeader from './components/NewIssueHeader.svelte'
import ListView from './components/issues/ListView.svelte'
import IssuesView from './components/issues/IssuesView.svelte'
import IssuePreview from './components/issues/IssuePreview.svelte'
export async function queryIssue<D extends Issue> (
_class: Ref<Class<D>>,
client: Client,
search: string
): Promise<ObjectSearchResult[]> {
const teams = await client.findAll<Team>(tracker.class.Team, {})
const named = new Map(
(
await client.findAll<Issue>(
_class,
{ title: { $like: `%${search}%` } },
{
limit: 200,
lookup: { space: tracker.class.Team }
}
)
).map((e) => [e._id, e])
)
for (const currentTeam of teams) {
const nids: number[] = []
for (let n = 0; n < currentTeam.sequence; n++) {
const v = `${currentTeam.identifier}-${n}`
if (v.includes(search)) {
nids.push(n)
}
}
if (nids.length > 0) {
const numbered = await client.findAll<Issue>(
_class,
{ number: { $in: nids } },
{ limit: 200, lookup: { space: tracker.class.Team } }
)
for (const d of numbered) {
if (!named.has(d._id)) {
named.set(d._id, d)
}
}
}
}
return Array.from(named.values()).map((e) => ({
doc: e,
title: getIssueId(e.$lookup?.space as Team, e),
icon: tracker.icon.TrackerApplication,
component: IssueItem
}))
}
export default async (): Promise<Resources> => ({
component: {
NopeComponent,
@ -95,5 +147,8 @@ export default async (): Promise<Resources> => ({
},
function: {
ProjectVisible: () => false
},
completion: {
IssueQuery: async (client: Client, query: string) => await queryIssue(tracker.class.Issue, client, query)
}
})

View File

@ -63,12 +63,14 @@
})
const query = createQuery()
$: _id &&
_class &&
$: if (_id && _class) {
query.query(_class, { _id }, (result) => {
object = result[0]
realObjectClass = object._class
})
} else {
query.unsubscribe()
}
let keys: KeyedAttribute[] = []
let collectionEditors: { key: KeyedAttribute; editor: AnyComponent }[] = []

View File

@ -34,6 +34,7 @@
doc = r.shift()
})
} else {
docQuery.unsubscribe()
doc = value
}

View File

@ -29,7 +29,7 @@
let preference: ViewletPreference | undefined
const preferenceQuery = createQuery()
$: viewlet &&
$: if (viewlet) {
preferenceQuery.query(
view.class.ViewletPreference,
{
@ -41,6 +41,9 @@
},
{ limit: 1 }
)
} else {
preferenceQuery.unsubscribe()
}
const client = getClient()
const hierarchy = client.getHierarchy()

View File

@ -3,11 +3,13 @@
"version": "1.0.0",
"license": "EPL-2.0",
"scripts": {
"build": "rm -rf ./dist && cross-env NODE_ENV=production webpack --stats-error-details && echo 'done'",
"bundle": "cp -r ../../server/front/bundle.js .",
"docker:build": "docker build -t hardcoreeng/tracker-front .",
"docker:staging": "../../common/scripts/docker_tag.sh hardcoreeng/tracker-front staging",
"docker:push": "../../common/scripts/docker_tag.sh hardcoreeng/tracker-front",
"build": "echo 'disabled'",
"*build": "rm -rf ./dist && cross-env NODE_ENV=production webpack --stats-error-details && echo 'done'",
"*bundle": "cp -r ../../server/front/bundle.js .",
"bundle": "echo 'disabled'",
"*docker:build": "docker build -t hardcoreeng/tracker-front .",
"*docker:staging": "../../common/scripts/docker_tag.sh hardcoreeng/tracker-front staging",
"*docker:push": "../../common/scripts/docker_tag.sh hardcoreeng/tracker-front",
"analyze": "cross-env NODE_ENV=production webpack --json > stats.json",
"show": "webpack-bundle-analyzer stats.json dist",
"dev": "cross-env CLIENT_TYPE=dev webpack serve",
@ -15,8 +17,8 @@
"start": "cross-env NODE_ENV=production webpack serve",
"preformat-svelte": "prettier -w src/**/*.svelte",
"lint": "",
"lint:fix": "yarn preformat-svelte && eslint --fix src",
"deploy": "cp -p public/* dist && aws s3 sync dist s3://anticrm-platform --delete --acl public-read"
"lint:fix": "",
"*deploy": "cp -p public/* dist && aws s3 sync dist s3://anticrm-platform --delete --acl public-read"
},
"devDependencies": {
"cross-env": "~7.0.3",