Skip to content

Commit

Permalink
Merge remote-tracking branch 'origin/develop' into wip/jtulach/EnsoIn…
Browse files Browse the repository at this point in the history
…sightDev11159
  • Loading branch information
JaroslavTulach committed Oct 24, 2024
2 parents 954e628 + 3390491 commit a3938bf
Show file tree
Hide file tree
Showing 33 changed files with 717 additions and 310 deletions.
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,13 @@
Widget][11271]
- [Copying and pasting in Table Editor Widget now works properly][11332]
- [Fix invisible selection in Table Input Widget][11358]
- [Enable cloud file browser in local projects][11383]

[11151]: https://github.com/enso-org/enso/pull/11151
[11271]: https://github.com/enso-org/enso/pull/11271
[11332]: https://github.com/enso-org/enso/pull/11332
[11358]: https://github.com/enso-org/enso/pull/11358
[11383]: https://github.com/enso-org/enso/pull/11383

#### Enso Standard Library

Expand All @@ -24,6 +26,12 @@
[11235]: https://github.com/enso-org/enso/pull/11235
[11255]: https://github.com/enso-org/enso/pull/11255

#### Enso Language & Runtime

- [Arguments in constructor definitions may now be on their own lines][11374]

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

# Enso 2024.4

#### Enso IDE
Expand Down
9 changes: 8 additions & 1 deletion app/gui/e2e/dashboard/assetSearchBar.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import * as backend from '#/services/Backend'

import * as actions from './actions'

test.test('tags', async ({ page }) => {
test.test('tags (positive)', async ({ page }) => {
await actions.mockAllAndLogin({ page })
const searchBarInput = actions.locateSearchBarInput(page)
const tags = actions.locateSearchBarTags(page)
Expand All @@ -19,7 +19,14 @@ test.test('tags', async ({ page }) => {
await positiveTag.click()
await test.expect(searchBarInput).toHaveValue(text)
}
})

test.test('tags (negative)', async ({ page }) => {
await actions.mockAllAndLogin({ page })
const searchBarInput = actions.locateSearchBarInput(page)
const tags = actions.locateSearchBarTags(page)

await searchBarInput.click()
await page.keyboard.down('Shift')
for (const negativeTag of await tags.all()) {
await searchBarInput.selectText()
Expand Down
1 change: 1 addition & 0 deletions app/gui/e2e/dashboard/assetsTableFeatures.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,7 @@ test.test('can drop onto root directory dropzone', ({ page }) =>
.driveTable.doubleClickRow(0)
.driveTable.withRows(async (rows, nonAssetRows) => {
const parentLeft = await actions.getAssetRowLeftPx(rows.nth(0))
await test.expect(nonAssetRows.nth(0)).toHaveText(actions.TEXT.thisFolderIsEmpty)
const childLeft = await actions.getAssetRowLeftPx(nonAssetRows.nth(0))
test.expect(childLeft, 'Child is indented further than parent').toBeGreaterThan(parentLeft)
})
Expand Down
20 changes: 12 additions & 8 deletions app/gui/e2e/project-view/widgets.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,10 @@ class DropDownLocator {
}
}

const CHOOSE_CLOUD_FILE = 'Choose file from cloud...'
const CHOOSE_LOCAL_FILE = 'Choose file…'
const CHOOSE_FILE_OPTIONS = [CHOOSE_CLOUD_FILE, CHOOSE_LOCAL_FILE]

test('Widget in plain AST', async ({ page }) => {
await actions.goToGraph(page)
const numberNode = locate.graphNodeByBinding(page, 'five')
Expand Down Expand Up @@ -249,7 +253,7 @@ test('Selection widgets in Data.read node', async ({ page }) => {
const pathArg = topLevelArgs.filter({ has: page.getByText('path') })
await pathArg.click()
const pathDropdown = new DropDownLocator(pathArg)
await pathDropdown.expectVisibleWithOptions(['Choose file…', 'File 1', 'File 2'])
await pathDropdown.expectVisibleWithOptions([...CHOOSE_FILE_OPTIONS, 'File 1', 'File 2'])
await pathDropdown.clickOption('File 2')
await expect(pathArg.locator('.WidgetText > input')).toHaveValue('File 2')

Expand All @@ -263,7 +267,7 @@ test('Selection widgets in Data.read node', async ({ page }) => {
notAppliedArguments: [1],
})
await page.getByText('path').click()
await pathDropdown.expectVisibleWithOptions(['Choose file…', 'File 1', 'File 2'])
await pathDropdown.expectVisibleWithOptions([...CHOOSE_FILE_OPTIONS, 'File 1', 'File 2'])
await pathDropdown.clickOption('File 1')
await expect(pathArg.locator('.WidgetText > input')).toHaveValue('File 1')
})
Expand All @@ -283,7 +287,7 @@ test('Selection widget with text widget as input', async ({ page }) => {

// Editing text input shows and filters drop down
await pathArgInput.click()
await pathDropdown.expectVisibleWithOptions(['Choose file…', 'File 1', 'File 2'])
await pathDropdown.expectVisibleWithOptions([...CHOOSE_FILE_OPTIONS, 'File 1', 'File 2'])
await page.keyboard.insertText('File 1')
await pathDropdown.expectVisibleWithOptions(['File 1'])
// Clearing input should show all text literal options
Expand All @@ -298,7 +302,7 @@ test('Selection widget with text widget as input', async ({ page }) => {

// Choosing entry should finish editing
await pathArgInput.click()
await pathDropdown.expectVisibleWithOptions(['Choose file…', 'File 1', 'File 2'])
await pathDropdown.expectVisibleWithOptions([...CHOOSE_FILE_OPTIONS, 'File 1', 'File 2'])
await page.keyboard.insertText('File')
await pathDropdown.expectVisibleWithOptions(['File 1', 'File 2'])
await pathDropdown.clickOption('File 1')
Expand All @@ -308,15 +312,15 @@ test('Selection widget with text widget as input', async ({ page }) => {

// Clicking-off and pressing enter should accept text as-is
await pathArgInput.click()
await pathDropdown.expectVisibleWithOptions(['Choose file…', 'File 1', 'File 2'])
await pathDropdown.expectVisibleWithOptions([...CHOOSE_FILE_OPTIONS, 'File 1', 'File 2'])
await page.keyboard.insertText('File')
await page.keyboard.press('Enter')
await expect(pathArgInput).not.toBeFocused()
await expect(pathArgInput).toHaveValue('File')
await expect(pathDropdown.dropDown).not.toBeVisible()

await pathArgInput.click()
await pathDropdown.expectVisibleWithOptions(['Choose file…', 'File 1', 'File 2'])
await pathDropdown.expectVisibleWithOptions([...CHOOSE_FILE_OPTIONS, 'File 1', 'File 2'])
await page.keyboard.insertText('Foo')
await expect(pathArgInput).toHaveValue('Foo')
await actions.clickAtBackground(page)
Expand All @@ -342,8 +346,8 @@ test('File Browser widget', async ({ page }) => {
const pathArg = topLevelArgs.filter({ has: page.getByText('path') })
const pathDropdown = new DropDownLocator(pathArg)
await pathArg.click()
await pathDropdown.expectVisibleWithOptions(['Choose file…', 'File 1', 'File 2'])
await pathDropdown.clickOption('Choose file…')
await pathDropdown.expectVisibleWithOptions([...CHOOSE_FILE_OPTIONS, 'File 1', 'File 2'])
await pathDropdown.clickOption(CHOOSE_LOCAL_FILE)
await expect(pathArg.locator('.WidgetText > input')).toHaveValue('/path/to/some/mock/file')
})

Expand Down
2 changes: 1 addition & 1 deletion app/gui/playwright.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import { defineConfig } from '@playwright/test'
import net from 'net'

const DEBUG = process.env.DEBUG_E2E === 'true'
const TIMEOUT_MS = DEBUG ? 100_000_000 : 30_000
const TIMEOUT_MS = DEBUG ? 100_000_000 : 60_000

async function findFreePortInRange(min: number, max: number) {
for (let i = 0; i < 50; i++) {
Expand Down
9 changes: 6 additions & 3 deletions app/gui/src/dashboard/layouts/Editor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,8 @@ export interface GraphEditorProps {
readonly ignoreParamsRegex?: RegExp
readonly logEvent: (message: string, projectId?: string | null, metadata?: object | null) => void
readonly renameProject: (newName: string) => void
readonly backend: Backend | null
readonly projectBackend: Backend | null
readonly remoteBackend: Backend | null
}

// =========================
Expand Down Expand Up @@ -187,7 +188,8 @@ function EditorInternal(props: EditorInternalProps) {
const jsonAddress = openedProject.jsonAddress
const binaryAddress = openedProject.binaryAddress
const ydocAddress = openedProject.ydocAddress ?? ydocUrl ?? ''
const backend = backendType === backendModule.BackendType.remote ? remoteBackend : localBackend
const projectBackend =
backendType === backendModule.BackendType.remote ? remoteBackend : localBackend

if (jsonAddress == null) {
throw new Error(getText('noJSONEndpointError'))
Expand All @@ -205,7 +207,8 @@ function EditorInternal(props: EditorInternalProps) {
ignoreParamsRegex: IGNORE_PARAMS_REGEX,
logEvent,
renameProject,
backend,
projectBackend,
remoteBackend,
}
}
}, [
Expand Down
14 changes: 12 additions & 2 deletions app/gui/src/project-view/App.vue
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,20 @@ const props = defineProps<{
hidden: boolean
ignoreParamsRegex?: RegExp
renameProject: (newName: string) => void
backend: Backend
/** The current project's backend, which may be remote or local. */
projectBackend: Backend | null
/**
* The remote backend.
*
* This is used regardless of whether the project is local for e.g. the cloud file browser.
*/
remoteBackend: Backend | null
}>()
provideBackend(() => markRaw(toRaw(props.backend)))
provideBackend({
project: () => props.projectBackend && markRaw(toRaw(props.projectBackend)),
remote: () => props.remoteBackend && markRaw(toRaw(props.remoteBackend)),
})
const classSet = provideAppClassSet()
const appTooltips = provideTooltipRegistry()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,9 @@ import {
type CustomDropdownItem,
} from '@/components/GraphEditor/widgets/WidgetSelection.vue'
import FileBrowserWidget from '@/components/widgets/FileBrowserWidget.vue'
import { injectBackend } from '@/providers/backend'
import { Score, WidgetInput, defineWidget, widgetProps } from '@/providers/widgetRegistry'
import { Ast } from '@/util/ast'
import { ArgumentInfoKey } from '@/util/callTree'
import { BackendType } from 'enso-common/src/services/Backend'
import { computed, h } from 'vue'
const props = defineProps(widgetProps(widgetDefinition))
Expand Down Expand Up @@ -50,8 +48,6 @@ export const widgetDefinition = defineWidget(
{
priority: 49,
score: (props) => {
const backend = injectBackend(true)?.backend
if (backend?.type !== BackendType.remote) return Score.Mismatch
if (props.input.dynamicConfig?.kind === 'File_Browse') return Score.Perfect
const reprType = props.input[ArgumentInfoKey]?.info?.reprType
if (reprType?.includes(FILE_TYPE) || reprType?.includes(WRITABLE_FILE_TYPE))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,22 +2,23 @@
import LoadingSpinner from '@/components/shared/LoadingSpinner.vue'
import SvgButton from '@/components/SvgButton.vue'
import SvgIcon from '@/components/SvgIcon.vue'
import { useBackendQuery, useBackendQueryPrefetching } from '@/composables/backend'
import type { ToValue } from '@/util/reactivity'
import type {
import { useBackend } from '@/composables/backend'
import { ToValue } from '@/util/reactivity'
import Backend, {
assetIsDirectory,
assetIsFile,
DirectoryAsset,
DirectoryId,
FileAsset,
FileId,
} from 'enso-common/src/services/Backend'
import Backend, { assetIsDirectory, assetIsFile } from 'enso-common/src/services/Backend'
import { computed, ref, toValue, watch } from 'vue'
const emit = defineEmits<{
pathSelected: [path: string]
}>()
const { ensureQueryData } = useBackendQueryPrefetching()
const { query, ensureQueryData } = useBackend('remote')
// === Current Directory ===
Expand All @@ -33,7 +34,7 @@ const directoryStack = ref<Directory[]>([
},
])
const currentDirectory = computed(() => directoryStack.value[directoryStack.value.length - 1]!)
const currentUser = useBackendQuery('usersMe', [])
const currentUser = query('usersMe', [])
const currentPath = computed(
() =>
currentUser.data.value &&
Expand All @@ -59,7 +60,7 @@ function listDirectoryArgs(params: ToValue<Directory | undefined>) {
})
}
const { isPending, isError, data, error } = useBackendQuery(
const { isPending, isError, data, error } = query(
'listDirectory',
listDirectoryArgs(currentDirectory),
)
Expand Down
47 changes: 25 additions & 22 deletions app/gui/src/project-view/composables/backend.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,6 @@
import { injectBackend } from '@/providers/backend'
import type { ToValue } from '@/util/reactivity'
import {
useQuery,
useQueryClient,
type UseQueryOptions,
type UseQueryReturnType,
} from '@tanstack/vue-query'
import { useQuery, useQueryClient, UseQueryOptions, UseQueryReturnType } from '@tanstack/vue-query'
import type { BackendMethods } from 'enso-common/src/backendQuery'
import { backendBaseOptions, backendQueryKey } from 'enso-common/src/backendQuery'
import Backend from 'enso-common/src/services/Backend'
Expand All @@ -23,7 +18,7 @@ const methodDefaultOptions: Partial<Record<BackendMethods, ExtraOptions>> = {
function backendQueryOptions<Method extends BackendMethods>(
method: Method,
args: ToValue<Parameters<Backend[Method]> | undefined>,
backend: Backend,
backend: Backend | null,
) {
return {
...backendBaseOptions(backend),
Expand All @@ -32,36 +27,44 @@ function backendQueryOptions<Method extends BackendMethods>(
const argsValue = toValue(args)
return argsValue ? backendQueryKey(backend, method, argsValue) : []
}),
queryFn: () => (backend[method] as any).apply(backend, toValue(args)!),
enabled: computed(() => !!toValue(args)),
queryFn: () => backend && (backend[method] as any).apply(backend, toValue(args)!),
enabled: computed(() => !!backend && !!toValue(args)),
}
}

/** TODO: Add docs */
export function useBackendQuery<Method extends BackendMethods>(
method: Method,
args: ToValue<Parameters<Backend[Method]> | undefined>,
): UseQueryReturnType<Awaited<ReturnType<Backend[Method]>>, Error> {
const { backend } = injectBackend()
return useQuery(backendQueryOptions(method, args, backend))
}

/** TODO: Add docs */
export function useBackendQueryPrefetching() {
/**
* Composable providing access to the backend API.
* @param which - Whether to use the remote backend, or the current project's backend (which may be the remote backend,
* or a local backend).
*/
export function useBackend(which: 'remote' | 'project') {
const queryClient = useQueryClient()
const { backend } = injectBackend()
const { project, remote } = injectBackend()
const backend = which === 'project' ? project : remote

/** Perform the specified query, and keep the result up-to-date if the provided arguments change. */
function query<Method extends BackendMethods>(
method: Method,
args: ToValue<Parameters<Backend[Method]> | undefined>,
): UseQueryReturnType<Awaited<ReturnType<Backend[Method]>>, Error> {
return useQuery(backendQueryOptions(method, args, backend))
}

/** Enable prefetching of the specified query. */
function prefetch<Method extends BackendMethods>(
method: Method,
args: ToValue<Parameters<Backend[Method]> | undefined>,
) {
return queryClient.prefetchQuery(backendQueryOptions(method, args, backend))
}

/** Return query results from the cache (even if stale), or if no cached data is available fetch the data. */
function ensureQueryData<Method extends BackendMethods>(
method: Method,
args: ToValue<Parameters<Backend[Method]> | undefined>,
): Promise<Awaited<ReturnType<Backend[Method]>>> {
return queryClient.ensureQueryData(backendQueryOptions(method, args, backend))
}
return { prefetch, ensureQueryData }

return { query, prefetch, ensureQueryData }
}
11 changes: 7 additions & 4 deletions app/gui/src/project-view/providers/backend.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,11 @@ import type Backend from 'enso-common/src/services/Backend'
import { proxyRefs, toRef } from 'vue'

export { injectFn as injectBackend, provideFn as provideBackend }
const { provideFn, injectFn } = createContextStore('backend', (backend: ToValue<Backend>) =>
proxyRefs({
backend: toRef(backend),
}),
const { provideFn, injectFn } = createContextStore(
'backend',
({ project, remote }: { project: ToValue<Backend | null>; remote: ToValue<Backend | null> }) =>
proxyRefs({
project: toRef(project),
remote: toRef(remote),
}),
)
Loading

0 comments on commit a3938bf

Please sign in to comment.