mirror of
https://github.com/hcengineering/platform.git
synced 2025-04-18 22:38:33 +00:00
UBER-972 (#3782)
This commit is contained in:
parent
f1c4726749
commit
9a4fa752de
@ -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
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
*/
|
||||
|
@ -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>>,
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
@ -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>
|
||||
|
||||
|
@ -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>
|
||||
|
@ -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} />
|
||||
|
@ -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
|
||||
|
@ -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()
|
||||
|
@ -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) {
|
||||
|
@ -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()
|
||||
|
Loading…
Reference in New Issue
Block a user