UBER-1083: Use hours and minutes to present less than a day durations (#4111)

Signed-off-by: Petr Vyazovetskiy <develop.pit@gmail.com>
This commit is contained in:
Pete Anøther 2023-12-01 04:27:56 -03:00 committed by GitHub
parent 32e39a3723
commit e361325a73
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 122 additions and 22 deletions

View File

@ -11,7 +11,7 @@ export interface KeyedAttribute {
export { updateAttribute } from '@hcengineering/core' export { updateAttribute } from '@hcengineering/core'
export function getAttribute (client: Client, object: any, key: KeyedAttribute): any { export function getAttribute (client: Client, object: any, key: KeyedAttribute): any {
// Check if attr is mixin and return it's value // Check if attr is mixin and return its value
if (client.getHierarchy().isMixin(key.attr.attributeOf)) { if (client.getHierarchy().isMixin(key.attr.attributeOf)) {
return (client.getHierarchy().as(object, key.attr.attributeOf) as any)[key.key] return (client.getHierarchy().as(object, key.attr.attributeOf) as any)[key.key]
} else { } else {

View File

@ -73,6 +73,7 @@ export function checkAdaptiveMatching (size: WidthType | null, limit: WidthType)
return size !== null ? range.has(size) : false return size !== null ? range.has(size) : false
} }
// TODO: Fix naming, since it doesn't floor (floorFractionDigits(2.5) === 3.0)
export function floorFractionDigits (n: number | string, amount: number): number { export function floorFractionDigits (n: number | string, amount: number): number {
return Number(Number(n).toFixed(amount)) return Number(Number(n).toFixed(amount))
} }

View File

@ -237,7 +237,9 @@
"TimeSpendReportValueTooltip": "Spent time in hours", "TimeSpendReportValueTooltip": "Spent time in hours",
"TimeSpendReportDescription": "Description", "TimeSpendReportDescription": "Description",
"TimeSpendValue": "{value}d", "TimeSpendValue": "{value}d",
"TimeSpendDays": "{value}d",
"TimeSpendHours": "{value}h", "TimeSpendHours": "{value}h",
"TimeSpendMinutes": "{value}m",
"ChildEstimation": "Subissues Estimation", "ChildEstimation": "Subissues Estimation",
"ChildReportedTime": "Subissues Time", "ChildReportedTime": "Subissues Time",
"CapacityValue": "of {value}d", "CapacityValue": "of {value}d",

View File

@ -237,7 +237,9 @@
"TimeSpendReportValueTooltip": "Затраченное время в человеко днях", "TimeSpendReportValueTooltip": "Затраченное время в человеко днях",
"TimeSpendReportDescription": "Описание", "TimeSpendReportDescription": "Описание",
"TimeSpendValue": "{value}d", "TimeSpendValue": "{value}d",
"TimeSpendDays": "{value}d",
"TimeSpendHours": "{value}h", "TimeSpendHours": "{value}h",
"TimeSpendMinutes": "{value}m",
"ChildEstimation": "Оценка подзадач", "ChildEstimation": "Оценка подзадач",
"ChildReportedTime": "Время подзадач", "ChildReportedTime": "Время подзадач",
"CapacityValue": "из {value}d", "CapacityValue": "из {value}d",

View File

@ -13,13 +13,46 @@
// limitations under the License. // limitations under the License.
--> -->
<script lang="ts"> <script lang="ts">
import { floorFractionDigits, Label, tooltip } from '@hcengineering/ui' import { Label, tooltip, themeStore } from '@hcengineering/ui'
import tracker from '../../../plugin' import tracker from '../../../plugin'
import { translate } from '@hcengineering/platform'
export let id: string | undefined = undefined export let id: string | undefined = undefined
export let kind: 'link' | undefined = undefined export let kind: 'link' | undefined = undefined
export let value: number export let value: number
export let noSymbol: boolean = false
// TODO: Make configurable?
const hoursInWorkingDay = 8
let label = ''
$: days = Math.floor(value / hoursInWorkingDay)
$: hours = Math.floor(value % hoursInWorkingDay)
$: minutes = Math.floor((value % 1) * 60)
$: Promise.all([
days > 0 ? translate(tracker.string.TimeSpendDays, { value: days }, $themeStore.language) : Promise.resolve(false),
hours > 0
? translate(tracker.string.TimeSpendHours, { value: hours }, $themeStore.language)
: Promise.resolve(false),
minutes > 0
? translate(tracker.string.TimeSpendMinutes, { value: minutes }, $themeStore.language)
: Promise.resolve(false)
])
.then(([days, hours, minutes]) =>
[
...(days === false ? [] : [days]),
...(hours === false ? [] : [hours]),
...(minutes === false ? [] : [minutes])
].join(' ')
)
.then((l) => (l === '' ? translate(tracker.string.TimeSpendHours, { value: 0 }, $themeStore.language) : l))
.then((l) => {
label = l
})
.catch((err) => {
console.error(err)
})
</script> </script>
<!-- svelte-ignore a11y-click-events-have-key-events --> <!-- svelte-ignore a11y-click-events-have-key-events -->
@ -30,16 +63,13 @@
on:click on:click
use:tooltip={{ use:tooltip={{
component: Label, component: Label,
props: { label: tracker.string.TimeSpendHours, params: { value: floorFractionDigits(value, 2) } } props: {
label: tracker.string.TimeSpendValue,
params: { value }
}
}} }}
> >
{#if noSymbol} {label}
{floorFractionDigits(value, 2)}
{:else if value > 0 && value < 8}
<Label label={tracker.string.TimeSpendHours} params={{ value: floorFractionDigits(value, 2) }} />
{:else}
<Label label={tracker.string.TimeSpendValue} params={{ value: floorFractionDigits(value / 8, 3) }} />
{/if}
</span> </span>
<style lang="scss"> <style lang="scss">

View File

@ -269,7 +269,9 @@ export default mergeIds(trackerId, tracker, {
TimeSpendReportValue: '' as IntlString, TimeSpendReportValue: '' as IntlString,
TimeSpendReportDescription: '' as IntlString, TimeSpendReportDescription: '' as IntlString,
TimeSpendValue: '' as IntlString, TimeSpendValue: '' as IntlString,
TimeSpendDays: '' as IntlString,
TimeSpendHours: '' as IntlString, TimeSpendHours: '' as IntlString,
TimeSpendMinutes: '' as IntlString,
ChildEstimation: '' as IntlString, ChildEstimation: '' as IntlString,
ChildReportedTime: '' as IntlString, ChildReportedTime: '' as IntlString,

View File

@ -4,7 +4,7 @@
./tool-local.sh create-account user1 -f John -l Appleseed -p 1234 ./tool-local.sh create-account user1 -f John -l Appleseed -p 1234
./tool-local.sh confirm-email user1 ./tool-local.sh confirm-email user1
# Create second user record in accounts # Create second user record in accounts
./tool-local.sh create-account user2 -f Kainin -l Numoin -p 1234 ./tool-local.sh create-account user2 -f Kainin -l Dirak -p 1234
./tool-local.sh confirm-email user2 ./tool-local.sh confirm-email user2

View File

@ -44,6 +44,7 @@ export class TemplateDetailsPage extends CommonTrackerPage {
if (data.labels != null) { if (data.labels != null) {
await this.buttonAddLabel.click() await this.buttonAddLabel.click()
await expect(this.page.locator('div.menu-group span', { hasText: data.labels })).toBeVisible() await expect(this.page.locator('div.menu-group span', { hasText: data.labels })).toBeVisible()
await this.inputTitle.click({ force: true })
} }
if (data.component != null) { if (data.component != null) {
await expect(this.buttonComponent).toHaveText(data.component) await expect(this.buttonComponent).toHaveText(data.component)

View File

@ -83,5 +83,32 @@ test.describe('Tracker issue tests', () => {
...editIssue, ...editIssue,
estimation: '1d' estimation: '1d'
}) })
const estimations = new Map([
['0', '0h'],
['1', '1h'],
['1.25', '1h 15m'],
['1.259', '1h 15m'],
['1.26', '1h 15m'],
['1.27', '1h 16m'],
['1.5', '1h 30m'],
['1.75', '1h 45m'],
['2', '2h'],
['7', '7h'],
['8', '1d'],
['9', '1d 1h'],
['9.5', '1d 1h 30m']
])
for (const [input, expected] of estimations.entries()) {
await issuesDetailsPage.editIssue({
estimation: input
})
await issuesDetailsPage.checkIssue({
...newIssue,
...editIssue,
estimation: expected
})
}
}) })
}) })

View File

@ -49,7 +49,7 @@ test.describe('Tracker template tests', () => {
test('Edit a Template', async ({ page }) => { test('Edit a Template', async ({ page }) => {
const newTemplate: NewIssue = { const newTemplate: NewIssue = {
title: 'Template for edit', title: `Template for edit-${generateId()}`,
description: 'Created template for edit' description: 'Created template for edit'
} }
@ -70,6 +70,7 @@ test.describe('Tracker template tests', () => {
await trackerNavigationMenuPage.buttonTemplates.click() await trackerNavigationMenuPage.buttonTemplates.click()
const templatePage = new TemplatePage(page) const templatePage = new TemplatePage(page)
await templatePage.createNewTemplate(newTemplate)
await templatePage.openTemplate(newTemplate.title) await templatePage.openTemplate(newTemplate.title)
const templateDetailsPage = new TemplateDetailsPage(page) const templateDetailsPage = new TemplateDetailsPage(page)
@ -82,9 +83,32 @@ test.describe('Tracker template tests', () => {
}) })
await templateDetailsPage.checkCommentExist('Appleseed John created template') await templateDetailsPage.checkCommentExist('Appleseed John created template')
await templateDetailsPage.checkCommentExist('Appleseed John changed priority to High')
await templateDetailsPage.checkCommentExist('Appleseed John changed assignee to Dirak Kainin') const estimations = new Map([
await templateDetailsPage.checkCommentExist('Appleseed John changed estimation to 1d') ['0', '0h'],
await templateDetailsPage.checkCommentExist('Appleseed John changed due date') ['1', '1h'],
['1.25', '1h 15m'],
['1.259', '1h 15m'],
['1.26', '1h 15m'],
['1.27', '1h 16m'],
['1.5', '1h 30m'],
['1.75', '1h 45m'],
['2', '2h'],
['7', '7h'],
['8', '1d'],
['9', '1d 1h'],
['9.5', '1d 1h 30m']
])
for (const [input, expected] of estimations.entries()) {
await templateDetailsPage.editTemplate({
estimation: input
})
await templateDetailsPage.checkTemplate({
...newTemplate,
...editTemplate,
estimation: expected
})
}
}) })
}) })

View File

@ -213,9 +213,20 @@ export async function floorFractionDigits (n: number | string, amount: number):
} }
export async function toTime (value: number): Promise<string> { export async function toTime (value: number): Promise<string> {
if (value > 0 && value < 8) { if (value <= 0) {
return `${await floorFractionDigits(value, 2)}h` return '0h'
} else {
return `${await floorFractionDigits(value / 8, 3)}d`
} }
// TODO: Make configurable?
const hoursInWorkingDay = 8
const days = Math.floor(value / hoursInWorkingDay)
const hours = Math.floor(value % hoursInWorkingDay)
const minutes = Math.floor((value % 1) * 60)
return [
...(days > 0 ? [`${days}d`] : []),
...(hours > 0 ? [`${hours}h`] : []),
...(minutes > 0 ? [`${minutes}m`] : [])
].join(' ')
} }