diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 242f22944911..321d47627bd4 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -617,8 +617,11 @@ jobs: ENSO_IDE_GOOGLE_ANALYTICS_TAG: ${{ vars.ENSO_CLOUD_GOOGLE_ANALYTICS_TAG }} ENSO_IDE_MAPBOX_API_TOKEN: ${{ vars.ENSO_MAPBOX_API_TOKEN }} ENSO_IDE_SENTRY_DSN: ${{ vars.ENSO_CLOUD_SENTRY_DSN }} + ENSO_IDE_SENTRY_ORGANIZATION: ${{ vars.ENSO_CLOUD_SENTRY_ORGANIZATION }} + ENSO_IDE_SENTRY_PROJECT: ${{ vars.ENSO_CLOUD_SENTRY_PROJECT }} ENSO_IDE_STRIPE_KEY: ${{ vars.ENSO_CLOUD_STRIPE_KEY }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }} - if: failure() && runner.os == 'Windows' name: List files if failed (Windows) run: Get-ChildItem -Force -Recurse @@ -698,8 +701,11 @@ jobs: ENSO_IDE_GOOGLE_ANALYTICS_TAG: ${{ vars.ENSO_CLOUD_GOOGLE_ANALYTICS_TAG }} ENSO_IDE_MAPBOX_API_TOKEN: ${{ vars.ENSO_MAPBOX_API_TOKEN }} ENSO_IDE_SENTRY_DSN: ${{ vars.ENSO_CLOUD_SENTRY_DSN }} + ENSO_IDE_SENTRY_ORGANIZATION: ${{ vars.ENSO_CLOUD_SENTRY_ORGANIZATION }} + ENSO_IDE_SENTRY_PROJECT: ${{ vars.ENSO_CLOUD_SENTRY_PROJECT }} ENSO_IDE_STRIPE_KEY: ${{ vars.ENSO_CLOUD_STRIPE_KEY }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }} - if: failure() && runner.os == 'Windows' name: List files if failed (Windows) run: Get-ChildItem -Force -Recurse @@ -777,8 +783,11 @@ jobs: ENSO_IDE_GOOGLE_ANALYTICS_TAG: ${{ vars.ENSO_CLOUD_GOOGLE_ANALYTICS_TAG }} ENSO_IDE_MAPBOX_API_TOKEN: ${{ vars.ENSO_MAPBOX_API_TOKEN }} ENSO_IDE_SENTRY_DSN: ${{ vars.ENSO_CLOUD_SENTRY_DSN }} + ENSO_IDE_SENTRY_ORGANIZATION: ${{ vars.ENSO_CLOUD_SENTRY_ORGANIZATION }} + ENSO_IDE_SENTRY_PROJECT: ${{ vars.ENSO_CLOUD_SENTRY_PROJECT }} ENSO_IDE_STRIPE_KEY: ${{ vars.ENSO_CLOUD_STRIPE_KEY }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }} - if: failure() && runner.os == 'Windows' name: List files if failed (Windows) run: Get-ChildItem -Force -Recurse diff --git a/.gitignore b/.gitignore index f550280970b0..8ee7fcea6dcc 100644 --- a/.gitignore +++ b/.gitignore @@ -110,6 +110,7 @@ bench-report*.xml /enso.lib *.dll +*.dylib *.exe *.pdb *.so diff --git a/CHANGELOG.md b/CHANGELOG.md index 5c83b1cfb59d..3c054bb201f5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,11 +21,15 @@ - A constructor or type definition with a single inline argument definition was previously allowed to use spaces in the argument definition without parentheses. [This is now a syntax error.][11856] +- [Native libraries of projects can be added to `polyglot/lib` directory][11874] +- [Redo stack is no longer lost when interacting with text literals][11908]. - Symetric, transitive and reflexive [equality for intersection types][11897] [11777]: https://github.com/enso-org/enso/pull/11777 [11600]: https://github.com/enso-org/enso/pull/11600 [11856]: https://github.com/enso-org/enso/pull/11856 +[11874]: https://github.com/enso-org/enso/pull/11874 +[11908]: https://github.com/enso-org/enso/pull/11908 [11897]: https://github.com/enso-org/enso/pull/11897 # Enso 2024.5 diff --git a/app/common/src/queryClient.ts b/app/common/src/queryClient.ts index 4ad6802a177a..140caf24287e 100644 --- a/app/common/src/queryClient.ts +++ b/app/common/src/queryClient.ts @@ -59,7 +59,7 @@ export type QueryClient = vueQuery.QueryClient const DEFAULT_QUERY_STALE_TIME_MS = Infinity const DEFAULT_QUERY_PERSIST_TIME_MS = 30 * 24 * 60 * 60 * 1000 // 30 days -const DEFAULT_BUSTER = 'v1.1' +const DEFAULT_BUSTER = 'v1.2' export interface QueryClientOptions { readonly persisterStorage?: AsyncStorage & { diff --git a/app/gui/src/dashboard/components/MarkdownViewer/MarkdownViewer.tsx b/app/gui/src/dashboard/components/MarkdownViewer/MarkdownViewer.tsx index 1531ff5e6974..db66e404554e 100644 --- a/app/gui/src/dashboard/components/MarkdownViewer/MarkdownViewer.tsx +++ b/app/gui/src/dashboard/components/MarkdownViewer/MarkdownViewer.tsx @@ -30,6 +30,9 @@ export function MarkdownViewer(props: MarkdownViewerProps) { const { data: markdownToHtml } = useSuspenseQuery({ queryKey: ['markdownToHtml', { text, imgUrlResolver, markedInstance }] as const, + meta: { persist: false }, + gcTime: 0, + staleTime: 0, queryFn: ({ queryKey: [, args] }) => args.markedInstance.parse(args.text, { async: true, @@ -38,15 +41,10 @@ export function MarkdownViewer(props: MarkdownViewerProps) { const href = token.href token.raw = href - token.href = await args - .imgUrlResolver(href) - .then((url) => { - return url - }) - .catch((error) => { - logger.error(error) - return null - }) + token.href = await args.imgUrlResolver(href).catch((error) => { + logger.error(error) + return null + }) token.text = getText('arbitraryFetchImageError') } }, diff --git a/app/gui/src/dashboard/hooks/backendHooks.tsx b/app/gui/src/dashboard/hooks/backendHooks.tsx index 19679729b066..8208a0428a5a 100644 --- a/app/gui/src/dashboard/hooks/backendHooks.tsx +++ b/app/gui/src/dashboard/hooks/backendHooks.tsx @@ -56,9 +56,11 @@ import { tryCreateOwnerPermission } from '#/utilities/permissions' import { usePreventNavigation } from '#/utilities/preventNavigation' import { toRfc3339 } from 'enso-common/src/utilities/data/dateTime' -// The number of bytes in 1 megabyte. +/** The number of bytes in 1 megabyte. */ const MB_BYTES = 1_000_000 const S3_CHUNK_SIZE_MB = Math.round(backendModule.S3_CHUNK_SIZE_BYTES / MB_BYTES) +/** The maximum number of file chunks to upload at the same time. */ +const FILE_UPLOAD_CONCURRENCY = 5 // ============================ // === DefineBackendMethods === @@ -1198,20 +1200,31 @@ export function useUploadFileMutation(backend: Backend, options: UploadFileMutat body, file, ]) + let i = 0 + let completedChunkCount = 0 const parts: backendModule.S3MultipartPart[] = [] - for (const [url, i] of Array.from( - presignedUrls, - (presignedUrl, index) => [presignedUrl, index] as const, - )) { - parts.push(await uploadFileChunkMutation.mutateAsync([url, file, i])) - const newSentMb = Math.min((i + 1) * S3_CHUNK_SIZE_MB, fileSizeMb) + const uploadNextChunk = async (): Promise => { + const currentI = i + const url = presignedUrls[i] + if (url == null) { + return + } + i += 1 + const promise = uploadFileChunkMutation.mutateAsync([url, file, currentI]) + // Queue the next chunk to be uploaded after this one. + const fullPromise = promise.then(uploadNextChunk) + parts[currentI] = await promise + completedChunkCount += 1 + const newSentMb = Math.min(completedChunkCount * S3_CHUNK_SIZE_MB, fileSizeMb) setSentMb(newSentMb) options.onChunkSuccess?.({ event: 'chunk', sentMb: newSentMb, totalMb: fileSizeMb, }) + return fullPromise } + await Promise.all(Array.from({ length: FILE_UPLOAD_CONCURRENCY }).map(uploadNextChunk)) const result = await uploadFileEndMutation.mutateAsync([ { parentDirectoryId: body.parentDirectoryId, diff --git a/app/gui/src/project-view/components/visualizations/ScatterplotVisualization.vue b/app/gui/src/project-view/components/visualizations/ScatterplotVisualization.vue index 632c3ae42a05..b1ade0c8e3e1 100644 --- a/app/gui/src/project-view/components/visualizations/ScatterplotVisualization.vue +++ b/app/gui/src/project-view/components/visualizations/ScatterplotVisualization.vue @@ -7,6 +7,7 @@ import { Pattern } from '@/util/ast/match' import { getTextWidthBySizeAndFamily } from '@/util/measurement' import { defineKeybinds } from '@/util/visualizationBuiltins' import { computed, ref, watch, watchEffect, watchPostEffect } from 'vue' +import { ToolbarItem } from './toolbar' export const name = 'Scatter Plot' export const icon = 'points' @@ -707,12 +708,12 @@ watchPostEffect(() => { const yScale_ = yScale.value const plotData = getPlotData(data.value) as Point[] const series = Object.keys(data.value.axis).filter((s) => s != 'x') - const colorScale = (d: string) => { + const colorScale = (d: Point) => { const color = d3.scaleOrdinal(d3.schemeCategory10).domain(series) if (data.value.is_multi_series) { - return color(d) + return color(d.series ?? '') } - return DEFAULT_FILL_COLOR + return d.color ?? DEFAULT_FILL_COLOR } d3Points.value .selectAll('path') @@ -730,7 +731,7 @@ watchPostEffect(() => { 'd', symbol.type(matchShape).size((d) => (d.size ?? 0.15) * SIZE_SCALE_MULTIPLER), ) - .style('fill', (d) => colorScale(d.series || '')) + .style('fill', (d) => colorScale(d)) .attr('transform', (d) => `translate(${xScale_(Number(d.x))}, ${yScale_(d.y)})`) if (data.value.points.labels === VISIBLE_POINTS) { d3Points.value @@ -865,42 +866,49 @@ const makeSeriesLabelOptions = () => { return seriesOptions } -config.setToolbar([ - { - icon: 'select', - title: 'Enable Selection', - toggle: selectionEnabled, - }, - { - icon: 'show_all', - title: 'Fit All', - onClick: () => zoomToSelected(false), - }, - { - icon: 'zoom', - title: 'Zoom to Selected', - disabled: () => brushExtent.value == null, - onClick: zoomToSelected, - }, - { - icon: 'add_to_graph_editor', - title: 'Create component of selected points', - disabled: () => !createNewFilterNodeEnabled.value, - onClick: createNewFilterNode, - }, - { - type: 'textSelectionMenu', - selectedTextOption: yAxisSelected, - title: 'Choose Y Axis Label', - heading: 'Y Axis Label: ', - options: { - none: { - label: 'No Label', - }, - ...makeSeriesLabelOptions(), +const createTextSelectionButton = (): ToolbarItem => ({ + type: 'textSelectionMenu', + selectedTextOption: yAxisSelected, + title: 'Choose Y Axis Label', + heading: 'Y Axis Label: ', + options: { + none: { + label: 'No Label', }, + ...makeSeriesLabelOptions(), }, -]) +}) + +function useScatterplotVizToolbar() { + const textSelectionButton = createTextSelectionButton() + return computed(() => [ + { + icon: 'select', + title: 'Enable Selection', + toggle: selectionEnabled, + }, + { + icon: 'show_all', + title: 'Fit All', + onClick: () => zoomToSelected(false), + }, + { + icon: 'zoom', + title: 'Zoom to Selected', + disabled: () => brushExtent.value == null, + onClick: zoomToSelected, + }, + { + icon: 'add_to_graph_editor', + title: 'Create component of selected points', + disabled: () => !createNewFilterNodeEnabled.value, + onClick: createNewFilterNode, + }, + ...(data.value.is_multi_series ? [textSelectionButton] : []), + ]) +} + +config.setToolbar(useScatterplotVizToolbar())