This commit is contained in:
Denis Bykhov 2023-10-04 01:31:07 +06:00 committed by GitHub
parent f1c4726749
commit 9a4fa752de
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 184 additions and 27 deletions

View File

@ -29,6 +29,7 @@ import {
Domain,
DOMAIN_BLOB,
DOMAIN_CONFIGURATION,
DOMAIN_MIGRATION,
DOMAIN_DOC_INDEX_STATE,
DOMAIN_FULLTEXT_BLOB,
DOMAIN_MODEL,
@ -49,7 +50,8 @@ import {
Space,
Timestamp,
Type,
Version
Version,
MigrationState
} from '@hcengineering/core'
import {
Hidden,
@ -244,6 +246,12 @@ export class TVersion extends TDoc implements Version {
patch!: number
}
@Model(core.class.MigrationState, core.class.Doc, DOMAIN_MIGRATION)
export class TMigrationState extends TDoc implements MigrationState {
plugin!: string
state!: string
}
@Model(core.class.PluginConfiguration, core.class.Doc, DOMAIN_MODEL)
export class TPluginConfiguration extends TDoc implements PluginConfiguration {
pluginId!: Plugin

View File

@ -13,11 +13,27 @@
// limitations under the License.
//
import core, { DOMAIN_STATUS, DOMAIN_TX, Status, TxCUD, TxOperations, TxProcessor } from '@hcengineering/core'
import { MigrateOperation, MigrationClient, MigrationUpgradeClient, createOrUpdate } from '@hcengineering/model'
import core, {
DOMAIN_STATUS,
DOMAIN_TX,
Status,
TxCUD,
TxCollectionCUD,
TxCreateDoc,
TxOperations,
TxProcessor,
TxUpdateDoc
} from '@hcengineering/core'
import {
MigrateOperation,
MigrationClient,
MigrationUpgradeClient,
createOrUpdate,
tryMigrate
} from '@hcengineering/model'
import { DOMAIN_TASK } from '@hcengineering/model-task'
import tags from '@hcengineering/tags'
import { Project, TimeReportDayType, createStatuses } from '@hcengineering/tracker'
import { Issue, Project, TimeReportDayType, TimeSpendReport, createStatuses } from '@hcengineering/tracker'
import { DOMAIN_TRACKER } from '.'
import tracker from './plugin'
import { DOMAIN_SPACE } from '@hcengineering/model-core'
@ -83,6 +99,44 @@ async function fixIconsWithEmojis (tx: TxOperations): Promise<void> {
await Promise.all(promises)
}
async function fixSpentTime (client: MigrationClient): Promise<void> {
const issues = await client.find<Issue>(DOMAIN_TASK, { reportedTime: { $gt: 0 } })
for (const issue of issues) {
const childInfo = issue.childInfo
for (const child of childInfo) {
child.reportedTime = child.reportedTime * 8
}
await client.update(DOMAIN_TASK, { _id: issue._id }, { reportedTime: issue.reportedTime * 8, childInfo })
}
const reports = await client.find<TimeSpendReport>(DOMAIN_TRACKER, {})
for (const report of reports) {
await client.update(DOMAIN_TRACKER, { _id: report._id }, { value: report.value * 8 })
}
const createTxes = await client.find<TxCollectionCUD<Issue, TimeSpendReport>>(DOMAIN_TX, {
'tx.objectClass': tracker.class.TimeSpendReport,
'tx._class': core.class.TxCreateDoc,
'tx.attributes.value': { $exists: true }
})
for (const tx of createTxes) {
await client.update(
DOMAIN_TX,
{ _id: tx._id },
{ 'tx.attributes.value': (tx.tx as TxCreateDoc<TimeSpendReport>).attributes.value * 8 }
)
}
const updateTxes = await client.find<TxCollectionCUD<Issue, TimeSpendReport>>(DOMAIN_TX, {
'tx.objectClass': tracker.class.TimeSpendReport,
'tx._class': core.class.TxUpdateDoc,
'tx.operations.value': { $exists: true }
})
for (const tx of updateTxes) {
const val = (tx.tx as TxUpdateDoc<TimeSpendReport>).operations.value
if (val !== undefined) {
await client.update(DOMAIN_TX, { _id: tx._id }, { 'tx.operations.value': val * 8 })
}
}
}
async function moveIssues (client: MigrationClient): Promise<void> {
const docs = await client.find(DOMAIN_TRACKER, { _class: tracker.class.Issue })
if (docs.length > 0) {
@ -114,8 +168,20 @@ async function fixProjectDefaultStatuses (client: MigrationClient): Promise<void
export const trackerOperation: MigrateOperation = {
async migrate (client: MigrationClient): Promise<void> {
await moveIssues(client)
await fixProjectDefaultStatuses(client)
await tryMigrate(client, 'tracker', [
{
state: 'moveIssues',
func: moveIssues
},
{
state: 'fixProjectDefaultStatuses',
func: fixProjectDefaultStatuses
},
{
state: 'reportTimeDayToHour',
func: fixSpentTime
}
])
},
async upgrade (client: MigrationUpgradeClient): Promise<void> {
const tx = new TxOperations(client, core.account.System)

View File

@ -289,6 +289,11 @@ export const DOMAIN_MODEL = 'model' as Domain
*/
export const DOMAIN_CONFIGURATION = '_configuration' as Domain
/**
* @public
*/
export const DOMAIN_MIGRATION = '_migrations' as Domain
/**
* @public
*/
@ -358,6 +363,14 @@ export interface Version extends Doc {
patch: number
}
/**
* @public
*/
export interface MigrationState extends Doc {
plugin: string
state: string
}
/**
* @public
*/

View File

@ -35,6 +35,7 @@ import type {
IndexStageState,
IndexingConfiguration,
Interface,
MigrationState,
Obj,
PluginConfiguration,
Ref,
@ -118,7 +119,8 @@ export default plugin(coreId, {
Configuration: '' as Ref<Class<Configuration>>,
Status: '' as Ref<Class<Status>>,
StatusCategory: '' as Ref<Class<StatusCategory>>
StatusCategory: '' as Ref<Class<StatusCategory>>,
MigrationState: '' as Ref<Class<MigrationState>>
},
mixin: {
FullTextSearchContext: '' as Ref<Mixin<FullTextSearchContext>>,

View File

@ -1,6 +1,7 @@
import {
import core, {
Client,
Doc,
DOMAIN_MIGRATION,
DocumentQuery,
Domain,
FindOptions,
@ -10,7 +11,11 @@ import {
ObjQueryType,
PushOptions,
Ref,
UnsetOptions
UnsetOptions,
MigrationState,
generateId,
TxOperations,
Data
} from '@hcengineering/core'
/**
@ -94,3 +99,65 @@ export interface MigrateOperation {
// Perform high level upgrade operations.
upgrade: (client: MigrationUpgradeClient) => Promise<void>
}
/**
* @public
*/
export interface Migrations {
state: string
func: (client: MigrationClient) => Promise<void>
}
/**
* @public
*/
export interface UpgradeOperations {
state: string
func: (client: MigrationUpgradeClient) => Promise<void>
}
/**
* @public
*/
export async function tryMigrate (client: MigrationClient, plugin: string, migrations: Migrations[]): Promise<void> {
const states = new Set(
(await client.find<MigrationState>(DOMAIN_MIGRATION, { _class: core.class.MigrationState, plugin })).map(
(p) => p.state
)
)
for (const migration of migrations) {
if (states.has(migration.state)) continue
await migration.func(client)
const st: MigrationState = {
plugin,
state: migration.state,
space: core.space.Configuration,
modifiedBy: core.account.System,
modifiedOn: Date.now(),
_class: core.class.MigrationState,
_id: generateId()
}
await client.create(DOMAIN_MIGRATION, st)
}
}
/**
* @public
*/
export async function tryUpgrade (
client: MigrationUpgradeClient,
plugin: string,
migrations: UpgradeOperations[]
): Promise<void> {
const states = new Set((await client.findAll(core.class.MigrationState, { plugin })).map((p) => p.state))
for (const migration of migrations) {
if (states.has(migration.state)) continue
await migration.func(client)
const st: Data<MigrationState> = {
plugin,
state: migration.state
}
const tx = new TxOperations(client, core.account.System)
await tx.createDoc(core.class.MigrationState, core.space.Configuration, st)
}
}

View File

@ -29,15 +29,15 @@
on:click
use:tooltip={{
component: Label,
props: { label: tracker.string.TimeSpendHours, params: { value: floorFractionDigits(value * 8, 3) } }
props: { label: tracker.string.TimeSpendHours, params: { value: floorFractionDigits(value, 1) } }
}}
>
{#if noSymbol}
{floorFractionDigits(value, 1)}
{:else if value > 0 && value < 1}
<Label label={tracker.string.TimeSpendHours} params={{ value: floorFractionDigits(value * 8, 1) }} />
{:else if value > 0 && value < 8}
<Label label={tracker.string.TimeSpendHours} params={{ value: floorFractionDigits(value, 1) }} />
{:else}
<Label label={tracker.string.TimeSpendValue} params={{ value: floorFractionDigits(value, 1) }} />
<Label label={tracker.string.TimeSpendValue} params={{ value: floorFractionDigits(value / 8, 3) }} />
{/if}
</span>

View File

@ -109,22 +109,22 @@
maxDigitsAfterPoint={3}
kind={'editbox'}
/>
<Button kind={'link-bordered'} on:click={() => (data.value = 0.125)}
<Button kind={'link-bordered'} on:click={() => (data.value = 1)}
><span slot="content">1<Label label={tracker.string.HourLabel} /></span></Button
>
<Button kind={'link-bordered'} on:click={() => (data.value = 0.25)}
<Button kind={'link-bordered'} on:click={() => (data.value = 2)}
><span slot="content">2<Label label={tracker.string.HourLabel} /></span></Button
>
<Button kind={'link-bordered'} on:click={() => (data.value = 0.5)}
<Button kind={'link-bordered'} on:click={() => (data.value = 4)}
><span slot="content">4<Label label={tracker.string.HourLabel} /></span></Button
>
<Button kind={'link-bordered'} on:click={() => (data.value = 0.75)}
<Button kind={'link-bordered'} on:click={() => (data.value = 6)}
><span slot="content">6<Label label={tracker.string.HourLabel} /></span></Button
>
<Button kind={'link-bordered'} on:click={() => (data.value = 0.875)}
<Button kind={'link-bordered'} on:click={() => (data.value = 7)}
><span slot="content">7<Label label={tracker.string.HourLabel} /></span></Button
>
<Button kind={'link-bordered'} on:click={() => (data.value = 1)}
<Button kind={'link-bordered'} on:click={() => (data.value = 8)}
><span slot="content">8<Label label={tracker.string.HourLabel} /></span></Button
>
</div>

View File

@ -47,7 +47,7 @@
<Label label={tracker.string.ReportedTime} />:
<span class="caption-color"><TimePresenter value={reportedTime} /></span>.
<Label label={tracker.string.TimeSpendReports} />:
<span class="caption-color"><TimePresenter value={total} /></span>
<span class="caption-color"><TimePresenter value={floorFractionDigits(total / 8, 3)} /></span>
</span>
</svelte:fragment>
<TimeSpendReportsList {reports} {projects} />

View File

@ -281,7 +281,7 @@ export interface TimeSpendReport extends AttachedDoc {
employee: Ref<Employee> | null
date: Timestamp | null
// Value in man days
// Value in man hours
value: number
description: string

View File

@ -304,6 +304,7 @@
}
if (app === undefined && !navigateDone) {
const appShort = getMetadata(workbench.metadata.DefaultApplication) as Ref<Application>
if (appShort == null) return
const spaceRef = getMetadata(workbench.metadata.DefaultSpace) as Ref<Space>
const specialRef = getMetadata(workbench.metadata.DefaultSpecial) as Ref<Space>
const loc = getCurrentLocation()

View File

@ -534,7 +534,7 @@ export class FullTextIndexPipeline implements FullTextPipeline {
async checkIndexConsistency (dbStorage: ServerStorage): Promise<void> {
if (process.env.MODEL_VERSION !== undefined) {
const modelVersion = (await this.model.findAll(core.class.Version, {})).shift()
const modelVersion = (await this.model.findAll(core.class.Version, {}))[0]
if (modelVersion !== undefined) {
const modelVersionString = versionToString(modelVersion)
if (modelVersionString !== process.env.MODEL_VERSION) {

View File

@ -132,10 +132,10 @@ function floorFractionDigits (n: number | string, amount: number): number {
}
function toTime (value: number): string {
if (value > 0 && value < 1) {
return `${floorFractionDigits(value * 8, 1)}h`
if (value > 0 && value < 8) {
return `${floorFractionDigits(value, 1)}h`
} else {
return `${floorFractionDigits(value, 1)}d`
return `${floorFractionDigits(value / 8, 3)}d`
}
}
@ -143,7 +143,7 @@ test('report-time-from-issue-card', async ({ page }) => {
await navigate(page)
const assignee = 'Chen Rosamund'
const status = 'In Progress'
const values = [0.25, 0.5, 0.75, 1]
const values = [2, 4, 6, 8]
for (let i = 0; i < 5; i++) {
const random = Math.floor(Math.random() * values.length)
const time = values[random]
@ -214,7 +214,7 @@ test('report-time-from-main-view', async ({ page }) => {
await page.click('text="Modified date"')
await page.keyboard.press('Escape')
const values = [0.25, 0.5, 0.75, 1]
const values = [2, 4, 6, 8]
const assignee = 'Chen Rosamund'
const status = 'In Progress'
const name = getIssueName()