Skip to content

Commit

Permalink
Merge branch 'develop' into wip/akirathan/strip-native-libs
Browse files Browse the repository at this point in the history
  • Loading branch information
Akirathan committed Jan 9, 2025
2 parents c9003a0 + 0970a2a commit 62c2818
Show file tree
Hide file tree
Showing 259 changed files with 8,401 additions and 6,511 deletions.
6 changes: 6 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -191,3 +191,9 @@ test-results/
test-traces/
playwright-report/
playwright/.cache/

#########
## Git ##
#########

/.mailmap
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,13 @@
[11902]: https://github.com/enso-org/enso/pull/11902
[11908]: https://github.com/enso-org/enso/pull/11908

#### Enso Standard Library

- [Allow using `/` to access files inside a directory reached through a data
link.][11926]

[11926]: https://github.com/enso-org/enso/pull/11926

#### Enso Language & Runtime

- [Promote broken values instead of ignoring them][11777].
Expand Down
10 changes: 6 additions & 4 deletions app/common/src/backendQuery.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,9 @@ export type BackendMethods = object.ExtractKeys<Backend, object.MethodOf<Backend

/** For each backend method, an optional function defining how to create a query key from its arguments. */
type BackendQueryNormalizers = {
[Method in BackendMethods]?: (...args: Parameters<Backend[Method]>) => queryCore.QueryKey
[Method in BackendMethods]?: (
...args: Readonly<Parameters<Backend[Method]>>
) => queryCore.QueryKey
}

const NORMALIZE_METHOD_QUERY: BackendQueryNormalizers = {
Expand All @@ -22,7 +24,7 @@ const NORMALIZE_METHOD_QUERY: BackendQueryNormalizers = {
/** Creates a partial query key representing the given method and arguments. */
function normalizeMethodQuery<Method extends BackendMethods>(
method: Method,
args: Parameters<Backend[Method]>,
args: Readonly<Parameters<Backend[Method]>>,
) {
return NORMALIZE_METHOD_QUERY[method]?.(...args) ?? args
}
Expand All @@ -31,7 +33,7 @@ function normalizeMethodQuery<Method extends BackendMethods>(
export function backendQueryOptions<Method extends BackendMethods>(
backend: Backend | null,
method: Method,
args: Parameters<Backend[Method]>,
args: Readonly<Parameters<Backend[Method]>>,
keyExtra?: queryCore.QueryKey | undefined,
): {
queryKey: queryCore.QueryKey
Expand All @@ -47,7 +49,7 @@ export function backendQueryOptions<Method extends BackendMethods>(
export function backendQueryKey<Method extends BackendMethods>(
backend: Backend | null,
method: Method,
args: Parameters<Backend[Method]>,
args: Readonly<Parameters<Backend[Method]>>,
keyExtra?: queryCore.QueryKey | undefined,
): queryCore.QueryKey {
return [backend?.type, method, ...normalizeMethodQuery(method, args), ...(keyExtra ?? [])]
Expand Down
4 changes: 4 additions & 0 deletions app/common/src/queryClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ declare module '@tanstack/query-core' {
* @default false
*/
readonly awaitInvalidates?: queryCore.QueryKey[] | boolean
readonly refetchType?: queryCore.InvalidateQueryFilters['refetchType']
}

readonly queryMeta: {
Expand Down Expand Up @@ -98,6 +99,7 @@ export function createQueryClient<TStorageValue = string>(
mutationCache: new queryCore.MutationCache({
onSuccess: (_data, _variables, _context, mutation) => {
const shouldAwaitInvalidates = mutation.meta?.awaitInvalidates ?? false
const refetchType = mutation.meta?.refetchType ?? 'active'
const invalidates = mutation.meta?.invalidates ?? []
const invalidatesToAwait = (() => {
if (Array.isArray(shouldAwaitInvalidates)) {
Expand All @@ -113,6 +115,7 @@ export function createQueryClient<TStorageValue = string>(
for (const queryKey of invalidatesToIgnore) {
void queryClient.invalidateQueries({
predicate: query => queryCore.matchQuery({ queryKey }, query),
refetchType,
})
}

Expand All @@ -121,6 +124,7 @@ export function createQueryClient<TStorageValue = string>(
invalidatesToAwait.map(queryKey =>
queryClient.invalidateQueries({
predicate: query => queryCore.matchQuery({ queryKey }, query),
refetchType,
}),
),
)
Expand Down
22 changes: 15 additions & 7 deletions app/common/src/services/Backend.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1070,6 +1070,18 @@ export function assetIsType<Type extends AssetType>(type: Type) {
return (asset: AnyAsset): asset is Extract<AnyAsset, Asset<Type>> => asset.type === type
}

/** Extract the type of an id and return a discriminated union containing both id and type. */
export function extractTypeFromId(id: AssetId): AnyAsset extends infer T ?
T extends T ?
Pick<T, ('id' | 'type') & keyof T>
: never
: never {
return {
type: id.match(/^(.+?)-/)?.[1],
id,
} as never
}

/** Creates a new placeholder asset id for the given asset type. */
export function createPlaceholderAssetId<Type extends AssetType>(
type: Type,
Expand Down Expand Up @@ -1674,11 +1686,7 @@ export default abstract class Backend {
title: string,
): Promise<CreatedProject>
/** Return project details. */
abstract getProjectDetails(
projectId: ProjectId,
directoryId: DirectoryId | null,
getPresignedUrl?: boolean,
): Promise<Project>
abstract getProjectDetails(projectId: ProjectId, getPresignedUrl?: boolean): Promise<Project>
/** Return Language Server logs for a project session. */
abstract getProjectSessionLogs(
projectSessionId: ProjectSessionId,
Expand Down Expand Up @@ -1767,8 +1775,8 @@ export default abstract class Backend {
projectId?: string | null,
metadata?: object | null,
): Promise<void>
/** Download from an arbitrary URL that is assumed to originate from this backend. */
abstract download(url: string, name?: string): Promise<void>
/** Download an asset. */
abstract download(assetId: AssetId, title: string): Promise<void>

/**
* Get the URL for the customer portal.
Expand Down
4 changes: 0 additions & 4 deletions app/common/src/text/english.json
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,6 @@
"editDescriptionError": "Could not edit description",
"canOnlyDownloadFilesError": "You currently can only download files.",
"noProjectSelectedError": "First select a project to download.",
"downloadInvalidTypeError": "You can only download files, projects, and Datalinks",
"downloadProjectError": "Could not download project '$0'",
"downloadFileError": "Could not download file '$0'",
"downloadDatalinkError": "Could not download Datalink '$0'",
Expand All @@ -64,9 +63,6 @@
"nameShouldNotContainInvalidCharacters": "Name should not contain invalid characters",
"invalidEmailValidationError": "Please enter a valid email address",

"projectHasNoSourceFilesPhrase": "project has no source files",
"fileNotFoundPhrase": "file not found",

"noNewProfilePictureError": "Could not upload a new profile picture because no image was found",

"registrationError": "Something went wrong! Please try again or contact the administrators.",
Expand Down
2 changes: 1 addition & 1 deletion app/common/src/utilities/data/array.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/** @file Utilities for manipulating arrays. */

export const EMPTY_ARRAY: readonly never[] = []
export const EMPTY_ARRAY: readonly [] = []

// ====================
// === shallowEqual ===
Expand Down
9 changes: 9 additions & 0 deletions app/common/src/utilities/data/object.ts
Original file line number Diff line number Diff line change
Expand Up @@ -219,3 +219,12 @@ export function useObjectId() {
* can be used to splice together objects without the risk of collisions.
*/
export type DisjointKeysUnion<A, B> = keyof A & keyof B extends never ? A & B : never

/**
* Merge types of values of an object union. Useful to return an object that UNSAFELY
* (at runtime) conforms to the shape of a discriminated union.
* Especially useful for things like Tanstack Query results.
*/
export type MergeValuesOfObjectUnion<T> = {
[K in `${keyof T & string}`]: T[K & keyof T]
}
2 changes: 1 addition & 1 deletion app/gui/.storybook/preview.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ const reactPreview: ReactPreview = {

(Story, context) => (
<>
<div className="enso-dashboard">
<div className="enso-app">
<Story {...context} />
</div>
<div id="enso-portal-root" className="enso-portal-root" />
Expand Down
2 changes: 1 addition & 1 deletion app/gui/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@
</head>
<body>
<div id="enso-spotlight" class="enso-spotlight"></div>
<div id="enso-dashboard" class="enso-dashboard"></div>
<div id="enso-app" class="enso-app"></div>
<div id="enso-chat" class="enso-chat"></div>
<div id="enso-portal-root" class="enso-portal-root"></div>
<script type="module" src="/src/entrypoint.ts"></script>
Expand Down
9 changes: 1 addition & 8 deletions app/gui/integration-test/dashboard/createAsset.spec.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/** @file Test copying, moving, cutting and pasting. */
import { expect, test, type Page } from '@playwright/test'
import { expect, test } from '@playwright/test'

import { mockAllAndLogin } from './actions'

Expand All @@ -12,13 +12,6 @@ const SECRET_NAME = 'a secret name'
/** The value of the created secret. */
const SECRET_VALUE = 'a secret value'

/** Find an editor container. */
// eslint-disable-next-line @typescript-eslint/no-unused-vars
function locateEditor(page: Page) {
// Test ID of a placeholder editor component used during testing.
return page.locator('.App')
}

test('create folder', ({ page }) =>
mockAllAndLogin({ page })
.createFolder()
Expand Down
7 changes: 0 additions & 7 deletions app/gui/integration-test/dashboard/delete.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,6 @@ test('delete and restore', ({ page }) =>
.contextMenu.restoreFromTrash()
.driveTable.expectTrashPlaceholderRow()
.goToCategory.cloud()
.expectStartModal()
.withStartModal(async (startModal) => {
await expect(startModal).toBeVisible()
})
.close()
.driveTable.withRows(async (rows) => {
await expect(rows).toHaveCount(1)
}))
Expand All @@ -50,8 +45,6 @@ test('delete and restore (keyboard)', ({ page }) =>
.press('Mod+R')
.driveTable.expectTrashPlaceholderRow()
.goToCategory.cloud()
.expectStartModal()
.close()
.driveTable.withRows(async (rows) => {
await expect(rows).toHaveCount(1)
}))
Expand Down
9 changes: 1 addition & 8 deletions app/gui/integration-test/dashboard/driveView.spec.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,8 @@
/** @file Test the drive view. */
import { expect, test, type Locator, type Page } from '@playwright/test'
import { expect, test, type Locator } from '@playwright/test'

import { TEXT, mockAllAndLogin } from './actions'

/** Find an editor container. */
// eslint-disable-next-line @typescript-eslint/no-unused-vars
function locateEditor(page: Page) {
// Test ID of a placeholder editor component used during testing.
return page.locator('.App')
}

/** Find a button to close the project. */
// eslint-disable-next-line @typescript-eslint/no-unused-vars
function locateStopProjectButton(page: Locator) {
Expand Down
3 changes: 2 additions & 1 deletion app/gui/integration-test/dashboard/labelsPanel.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -84,12 +84,13 @@ test('labels', ({ page }) =>
// Labels panel with one entry
await locateCreateButton(locateNewLabelModal(page)).click()
await expect(locateLabelsPanel(page)).toBeVisible()
expect(await locateLabelsPanelLabels(page).count()).toBe(1)

// Empty labels panel again, after deleting the only entry
await locateLabelsPanelLabels(page).first().hover()

const labelsPanel = locateLabelsPanel(page)
await labelsPanel.getByRole('button').and(labelsPanel.getByLabel(TEXT.delete)).click()
await page.getByRole('button', { name: TEXT.delete }).getByText(TEXT.delete).click()
expect(await locateLabelsPanelLabels(page).count()).toBeGreaterThanOrEqual(1)
expect(await locateLabelsPanelLabels(page).count()).toBe(0)
}))
2 changes: 1 addition & 1 deletion app/gui/integration-test/dashboard/pageSwitcher.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { mockAllAndLogin } from './actions'
/** Find an editor container. */
function locateEditor(page: Page) {
// Test ID of a placeholder editor component used during testing.
return page.locator('.App')
return page.locator('.ProjectView')
}

/** Find a drive view. */
Expand Down
2 changes: 1 addition & 1 deletion app/gui/integration-test/dashboard/startModal.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { mockAllAndLogin } from './actions'
/** Find an editor container. */
function locateEditor(page: Page) {
// Test ID of a placeholder editor component used during testing.
return page.locator('.App')
return page.locator('.ProjectView')
}

/** Find a samples list. */
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import assert from 'assert'
import * as actions from './actions'
import { computedContent } from './css'
import { expect } from './customExpect'
import { CONTROL_KEY } from './keyboard'
import * as locate from './locate'

test('Node can open and load visualization', async ({ page }) => {
Expand Down Expand Up @@ -50,10 +51,12 @@ test('Previewing visualization', async ({ page }) => {

test('Warnings visualization', async ({ page }) => {
await actions.goToGraph(page)

// Without centering the graph, menu sometimes goes out of the view.
await page.keyboard.press(`${CONTROL_KEY}+Shift+A`)
// Create a node, attach a warning, open the warnings-visualization.
await locate.addNewNodeButton(page).click()
const input = locate.componentBrowserInput(page).locator('input')

await input.fill('Warning.attach "Uh oh" 42')
await page.keyboard.press('Enter')
await expect(locate.componentBrowser(page)).toBeHidden()
Expand Down
78 changes: 78 additions & 0 deletions app/gui/src/App.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
<script setup lang="ts">
import '@/assets/base.css'
import TooltipDisplayer from '@/components/TooltipDisplayer.vue'
import ProjectView from '@/ProjectView.vue'
import { provideAppClassSet } from '@/providers/appClass'
import { provideGuiConfig } from '@/providers/guiConfig'
import { provideTooltipRegistry } from '@/providers/tooltipRegistry'
import { registerAutoBlurHandler } from '@/util/autoBlur'
import { baseConfig, configValue, mergeConfig, type ApplicationConfigValue } from '@/util/config'
import { urlParams } from '@/util/urlParams'
import { useQueryClient } from '@tanstack/vue-query'
import { applyPureReactInVue } from 'veaury'
import { computed, onMounted } from 'vue'
import { ComponentProps } from 'vue-component-type-helpers'
import ReactRoot from './ReactRoot'
const _props = defineProps<{
// Used in Project View integration tests. Once both test projects will be merged, this should be
// removed
projectViewOnly?: { options: ComponentProps<typeof ProjectView> } | null
onAuthenticated?: (accessToken: string | null) => void
}>()
const classSet = provideAppClassSet()
const appTooltips = provideTooltipRegistry()
const appConfig = computed(() => {
const config = mergeConfig(baseConfig, urlParams(), {
onUnrecognizedOption: (p) => console.warn('Unrecognized option:', p),
})
return config
})
const appConfigValue = computed((): ApplicationConfigValue => configValue(appConfig.value))
const ReactRootWrapper = applyPureReactInVue(ReactRoot)
const queryClient = useQueryClient()
provideGuiConfig(appConfigValue)
registerAutoBlurHandler()
onMounted(() => {
if (appConfigValue.value.window.vibrancy) {
document.body.classList.add('vibrancy')
}
})
</script>

<template>
<div :class="['App', ...classSet.keys()]">
<ProjectView v-if="projectViewOnly" v-bind="projectViewOnly.options" />
<ReactRootWrapper
v-else
:config="appConfigValue"
:queryClient="queryClient"
@authenticated="onAuthenticated ?? (() => {})"
/>
</div>
<TooltipDisplayer :registry="appTooltips" />
</template>

<style>
.App {
width: 100%;
height: 100%;
display: flex;
flex-direction: column;
}
/*
TODO [ao]: Veaury adds a wrapping elements which have `style="all: unset"`, which in turn breaks our layout.
See https://github.com/gloriasoft/veaury/issues/158
*/
[__use_react_component_wrap],
[data-use-vue-component-wrap] {
display: contents !important;
}
</style>
Loading

0 comments on commit 62c2818

Please sign in to comment.