Skip to content

Commit

Permalink
Merge branch 'develop' into wip/gmt/11411-download-cache
Browse files Browse the repository at this point in the history
  • Loading branch information
GregoryTravis committed Jan 22, 2025
2 parents acfca95 + aa45212 commit 4ed8d35
Show file tree
Hide file tree
Showing 113 changed files with 3,284 additions and 1,083 deletions.
170 changes: 165 additions & 5 deletions app/common/src/services/Backend.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
/** @file Type definitions common between all backends. */

import type { TextId } from '../text'
import * as array from '../utilities/data/array'
import * as dateTime from '../utilities/data/dateTime'
import * as newtype from '../utilities/data/newtype'
Expand Down Expand Up @@ -79,6 +80,10 @@ export const SecretId = newtype.newtypeConstructor<SecretId>()
export type ProjectSessionId = newtype.Newtype<string, 'ProjectSessionId'>
export const ProjectSessionId = newtype.newtypeConstructor<ProjectSessionId>()

/** Unique identifier for a project execution. */
export type ProjectExecutionId = newtype.Newtype<string, 'ProjectExecutionId'>
export const ProjectExecutionId = newtype.newtypeConstructor<ProjectExecutionId>()

/** Unique identifier for a Datalink. */
export type DatalinkId = newtype.Newtype<string, 'DatalinkId'>
export const DatalinkId = newtype.newtypeConstructor<DatalinkId>()
Expand Down Expand Up @@ -208,6 +213,8 @@ export interface User extends UserInfo {
* Has enriched metadata, like the name of the group and the home directory ID.
*/
readonly groups?: readonly UserGroup[]
/** Whether the user is a member of the Enso team. */
readonly isEnsoTeamMember: boolean
}

/** A user related to the current user. */
Expand Down Expand Up @@ -345,6 +352,127 @@ export interface ProjectSession {
readonly userEmail: EmailAddress
}

export const PROJECT_PARALLEL_MODES = ['ignore', 'restart', 'parallel'] as const

export const PARALLEL_MODE_TO_TEXT_ID = {
ignore: 'ignoreParallelMode',
restart: 'restartParallelMode',
parallel: 'parallelParallelMode',
} satisfies {
[K in ProjectParallelMode]: TextId & `${K}ParallelMode`
}

export const PARALLEL_MODE_TO_DESCRIPTION_ID = {
ignore: 'ignoreParallelModeDescription',
restart: 'restartParallelModeDescription',
parallel: 'parallelParallelModeDescription',
} satisfies {
[K in ProjectParallelMode]: TextId & `${K}ParallelModeDescription`
}

/**
* The behavior when manually starting a new execution when the previous one is not yet complete.
* One of the following:
* - `ignore` - do not start the new execution.
* - `restart` - stop the old execution and start the new execution.
* - `parallel` - keep the old execution running but also run the new execution.
*/
export type ProjectParallelMode = (typeof PROJECT_PARALLEL_MODES)[number]

export const PROJECT_EXECUTION_REPEAT_TYPES = [
'none',
'hourly',
'daily',
'monthly-date',
'monthly-weekday',
'monthly-last-weekday',
] as const

export const PROJECT_EXECUTION_REPEAT_TYPE_TO_TEXT_ID = {
none: 'noneProjectExecutionRepeatType',
hourly: 'hourlyProjectExecutionRepeatType',
daily: 'dailyProjectExecutionRepeatType',
'monthly-date': 'monthlyProjectExecutionRepeatType',
'monthly-weekday': 'monthlyProjectExecutionRepeatType',
'monthly-last-weekday': 'monthlyProjectExecutionRepeatType',
} satisfies {
readonly [K in ProjectExecutionRepeatType]: TextId & `${string}ProjectExecutionRepeatType`
}

/** The interval at which a project schedule repeats. */
export type ProjectExecutionRepeatType = ProjectExecutionRepeatInfo['type']

/** Details for a project execution that repeats hourly. */
export interface ProjectExecutionNoneRepeatInfo {
readonly type: 'none'
}

/** Details for a project execution that repeats hourly. */
export interface ProjectExecutionHourlyRepeatInfo {
readonly type: 'hourly'
readonly startHour: number
readonly endHour: number
}

/** Details for a project execution that repeats daily. */
export interface ProjectExecutionDailyRepeatInfo {
readonly type: 'daily'
readonly daysOfWeek: readonly number[]
}

/** Details for a project execution that repeats monthly on a specific date. */
export interface ProjectExecutionMonthlyDateRepeatInfo {
readonly type: 'monthly-date'
readonly date: number
readonly months: readonly number[]
}

/**
* Details for a project execution that repeats monthly on a specific weekday of a specific week
* of a specific month.
*/
export interface ProjectExecutionMonthlyWeekdayRepeatInfo {
readonly type: 'monthly-weekday'
readonly weekNumber: number
readonly dayOfWeek: number
readonly months: readonly number[]
}

/**
* Details for a project execution that repeats monthly on a specific weekday of the last week
* of a specific month.
*/
export interface ProjectExecutionMonthlyLastWeekdayRepeatInfo {
readonly type: 'monthly-last-weekday'
readonly dayOfWeek: number
readonly months: readonly number[]
}

export type ProjectExecutionRepeatInfo =
| ProjectExecutionHourlyRepeatInfo
| ProjectExecutionDailyRepeatInfo
| ProjectExecutionMonthlyDateRepeatInfo
| ProjectExecutionMonthlyWeekdayRepeatInfo
| ProjectExecutionMonthlyLastWeekdayRepeatInfo
| ProjectExecutionNoneRepeatInfo

/** Metadata for a {@link ProjectExecution}. */
export interface ProjectExecutionInfo {
readonly projectId: ProjectId
readonly timeZone: string
readonly repeat: ProjectExecutionRepeatInfo
readonly parallelMode: ProjectParallelMode
readonly maxDurationMinutes: number
readonly startDate: dateTime.Rfc3339DateTime
}

/** A specific execution schedule of a project. */
export interface ProjectExecution extends ProjectExecutionInfo {
readonly enabled: boolean
readonly executionId: ProjectExecutionId
readonly versionId: S3ObjectVersionId
}

/** Metadata describing the location of an uploaded file. */
export interface FileLocator {
readonly fileId: FileId
Expand Down Expand Up @@ -668,19 +796,19 @@ export const COLORS = [
{ lightness: 50, chroma: 66, hue: 34 },
// Yellow
{ lightness: 50, chroma: 66, hue: 80 },
// Turquoise
// Green
{ lightness: 50, chroma: 66, hue: 139 },
// Teal
{ lightness: 50, chroma: 66, hue: 172 },
// Blue
{ lightness: 50, chroma: 66, hue: 271 },
// Lavender
// Purple
{ lightness: 50, chroma: 66, hue: 295 },
// Pink
{ lightness: 50, chroma: 66, hue: 332 },
// Light blue
// Light blueish grey
{ lightness: 50, chroma: 22, hue: 252 },
// Dark blue
// Dark blueish grey
{ lightness: 22, chroma: 13, hue: 252 },
] as const satisfies LChColor[]

Expand Down Expand Up @@ -1309,6 +1437,14 @@ export interface OpenProjectRequestBody {
readonly parentId: DirectoryId
}

/** HTTP request body for the "create project execution" endpoint. */
export interface CreateProjectExecutionRequestBody extends ProjectExecutionInfo {}

/** HTTP request body for the "update project execution" endpoint. */
export interface UpdateProjectExecutionRequestBody {
readonly enabled?: boolean | undefined
}

/** HTTP request body for the "create secret" endpoint. */
export interface CreateSecretRequestBody {
readonly name: string
Expand Down Expand Up @@ -1684,11 +1820,35 @@ export default abstract class Backend {
abstract createProject(body: CreateProjectRequestBody): Promise<CreatedProject>
/** Close a project. */
abstract closeProject(projectId: ProjectId, title: string): Promise<void>
/** Return a list of sessions for the current project. */
/** Return a list of sessions for a project. */
abstract listProjectSessions(
projectId: ProjectId,
title: string,
): Promise<readonly ProjectSession[]>
/** Create a project execution. */
abstract createProjectExecution(
body: CreateProjectExecutionRequestBody,
title: string,
): Promise<ProjectExecution>
abstract updateProjectExecution(
executionId: ProjectExecutionId,
body: UpdateProjectExecutionRequestBody,
projectTitle: string,
): Promise<ProjectExecution>
/** Delete a project execution. */
abstract deleteProjectExecution(
executionId: ProjectExecutionId,
projectTitle: string,
): Promise<void>
/** Return a list of executions for a project. */
abstract listProjectExecutions(
projectId: ProjectId,
title: string,
): Promise<readonly ProjectExecution[]>
abstract syncProjectExecution(
executionId: ProjectExecutionId,
projectTitle: string,
): Promise<ProjectExecution>
/** Restore a project from a different version. */
abstract restoreProject(
projectId: ProjectId,
Expand Down
100 changes: 100 additions & 0 deletions app/common/src/services/Backend/__test__/projectExecution.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
import * as v from 'vitest'
import { toRfc3339 } from '../../../utilities/data/dateTime'
import { ProjectExecutionInfo, ProjectId } from '../../Backend'
import { firstProjectExecutionOnOrAfter, nextProjectExecutionDate } from '../projectExecution'

const HOURLY_EXECUTION_1: ProjectExecutionInfo = {
projectId: ProjectId('project-aaaaaaaa'),
repeat: {
type: 'hourly',
startHour: 7,
endHour: 15,
},
startDate: toRfc3339(new Date(2020, 0, 1, 10, 59)),
timeZone: 'UTC',
maxDurationMinutes: 60,
parallelMode: 'ignore',
}

const HOURLY_EXECUTION_2: ProjectExecutionInfo = {
projectId: ProjectId('project-aaaaaaaa'),
repeat: {
type: 'hourly',
startHour: 20,
endHour: 4,
},
startDate: toRfc3339(new Date(2015, 2, 8, 22, 33)),
timeZone: 'UTC',
maxDurationMinutes: 60,
parallelMode: 'ignore',
}

const DAILY_EXECUTION: ProjectExecutionInfo = {
projectId: ProjectId('project-aaaaaaaa'),
repeat: {
type: 'daily',
daysOfWeek: [0, 5],
},
startDate: toRfc3339(new Date(2000, 0, 1, 7, 3)),
timeZone: 'UTC',
maxDurationMinutes: 60,
parallelMode: 'ignore',
}

v.test.each([
{
info: DAILY_EXECUTION,
current: new Date(2000, 5, 4, 7, 3),
next1: new Date(2000, 5, 9, 7, 3),
next2: new Date(2000, 5, 11, 7, 3),
next3: new Date(2000, 5, 16, 7, 3),
},
{
info: HOURLY_EXECUTION_1,
current: new Date(2022, 10, 21, 14, 59),
next1: new Date(2022, 10, 21, 15, 59),
next2: new Date(2022, 10, 22, 7, 59),
next3: new Date(2022, 10, 22, 8, 59),
},
{
info: HOURLY_EXECUTION_2,
current: new Date(2018, 4, 11, 3, 33),
next1: new Date(2018, 4, 11, 4, 33),
next2: new Date(2018, 4, 11, 20, 33),
next3: new Date(2018, 4, 11, 21, 33),
},
{
info: HOURLY_EXECUTION_2,
current: new Date(2018, 4, 11, 23, 33),
next1: new Date(2018, 4, 12, 0, 33),
next2: new Date(2018, 4, 12, 1, 33),
next3: new Date(2018, 4, 12, 2, 33),
},
] satisfies readonly {
info: ProjectExecutionInfo
current: Date
next1: Date
next2: Date
next3: Date
}[])(
'Get next project execution date (current: $current)',
({ info, current, next1, next2, next3 }) => {
v.expect(nextProjectExecutionDate(info, current)).toStrictEqual(next1)
v.expect(nextProjectExecutionDate(info, next1)).toStrictEqual(next2)
v.expect(nextProjectExecutionDate(info, next2)).toStrictEqual(next3)
},
)

v.test.each([
{ info: DAILY_EXECUTION, current: new Date(1999, 1, 1), next: new Date(2000, 0, 2, 7, 3) },
{ info: DAILY_EXECUTION, current: new Date(2000, 10, 16), next: new Date(2000, 10, 17, 7, 3) },
] satisfies readonly {
info: ProjectExecutionInfo
current: Date
next: Date
}[])(
'Get first project execution date on or after (current: $current)',
({ info, current, next }) => {
v.expect(firstProjectExecutionOnOrAfter(info, current)).toStrictEqual(next)
},
)
Loading

0 comments on commit 4ed8d35

Please sign in to comment.