Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update dashboard to design v122 (Part 1) #9896

Merged
merged 68 commits into from
Jun 12, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
68 commits
Select commit Hold shift + click to select a range
edb050c
Refactor start page into a dialog; update Drive Bar styles
somebody1234 May 8, 2024
3c83f6d
Remove obsolete `onSpinnerStateChange` event
somebody1234 May 8, 2024
63301a6
Add new UI for dropzone
somebody1234 May 8, 2024
36babfb
Convert Cloud and Local from backend types to categories
somebody1234 May 8, 2024
74d3499
WIP: Split useBackend into useRemoteBackend and useLocalBackend
somebody1234 May 8, 2024
80bbaab
Fix type errors
somebody1234 May 9, 2024
b44d7b1
Remove `useRemoteBackendStrict` from Settings pages
somebody1234 May 9, 2024
b4b191a
Minor UI fixes and improvements
somebody1234 May 9, 2024
1952ee1
Fixes for offline mode
somebody1234 May 9, 2024
55ab258
Add variant to `UnstyledButton` for new button style
somebody1234 May 9, 2024
c70f6a0
Prettier
somebody1234 May 9, 2024
275fcbc
Merge branch 'develop' into wip/sb/dashboard-122
somebody1234 May 10, 2024
d4fdeea
Fix type errors
somebody1234 May 10, 2024
48a24d7
Fix lints
somebody1234 May 10, 2024
93ed518
Merge branch 'develop' into wip/sb/dashboard-122
somebody1234 May 10, 2024
dad5b03
Prettier
somebody1234 May 10, 2024
347b1a8
Fix E2E tests
somebody1234 May 13, 2024
bcff2e5
Fix ESLint config
somebody1234 May 13, 2024
380f7e2
Lighten extra columns for assets table
somebody1234 May 13, 2024
c3d3a61
Adjust ESLint ignores
somebody1234 May 14, 2024
cf9b513
Merge branch 'develop' into wip/sb/dashboard-122
somebody1234 May 14, 2024
392dcda
Merge branch 'develop' into wip/sb/dashboard-122
somebody1234 May 15, 2024
f6f33ac
Fix lint errors
somebody1234 May 15, 2024
2835653
Fix error when `build.json` is missing
somebody1234 May 15, 2024
37eb101
Fix import error
somebody1234 May 15, 2024
6939e00
Merge branch 'develop' into wip/sb/dashboard-122
somebody1234 May 15, 2024
aa5ccbf
Merge branch 'develop' into wip/sb/dashboard-122
somebody1234 May 20, 2024
d44b8c9
Prettier
somebody1234 May 20, 2024
fc4793b
Fix broken spinner animations
somebody1234 May 20, 2024
3f6f483
Fix projects not continuing to load on reload
somebody1234 May 21, 2024
0db5c72
Merge branch 'develop' into wip/sb/dashboard-122
somebody1234 May 24, 2024
56cfef9
Address code review (part 1)
somebody1234 May 27, 2024
866bbe5
Address code review (part 2)
somebody1234 May 27, 2024
f554be9
WIP: Replace template literal `className`s with `twMerge` or `tv`
somebody1234 May 27, 2024
7ad226e
Finishing replacing template literal `className`s with `twMerge` or `tv`
somebody1234 May 28, 2024
7ccbf1b
Fix docs
somebody1234 May 28, 2024
396a6d5
Remove `clsx`
somebody1234 May 28, 2024
c7e90e3
Continue addressing code review
somebody1234 May 28, 2024
8e45f40
Merge branch 'develop' into wip/sb/dashboard-122
somebody1234 May 28, 2024
bcab4f9
WIP: Switch to mutations for interacting with the backend
somebody1234 May 28, 2024
721ed0b
Initial implementation of proper React Queries for backend
somebody1234 May 28, 2024
b73c9d5
Finish switching to React Query mutations
somebody1234 May 28, 2024
bb0a94a
Continue migration to React Query mutations
somebody1234 May 29, 2024
b128b74
Replace `UnstyledButton` with `ariaComponents.Button`
somebody1234 May 29, 2024
4cb5004
Minor style fixes
somebody1234 May 29, 2024
613680c
Key backend queries and mutations on the actual backend object itself
somebody1234 May 29, 2024
1e0c595
Replace `setOrganization` in settings with React Query mutations
somebody1234 May 29, 2024
6d242ed
Fix bugs related to updating organizations and users
somebody1234 May 29, 2024
874d2ad
Merge branch 'develop' into wip/sb/dashboard-122
somebody1234 May 29, 2024
63280d5
Fix type errors and lint errors
somebody1234 May 29, 2024
12c1548
Fix dropzone styling
somebody1234 May 31, 2024
f311561
Merge branch 'develop' into wip/sb/dashboard-122
somebody1234 May 31, 2024
23990b5
Add dropzone
somebody1234 May 31, 2024
fdb5ed9
Fix chat CSS
somebody1234 May 31, 2024
56c2531
Switch context menu away from Button
somebody1234 May 31, 2024
64e5267
Fix drag-n-drop bug
somebody1234 May 31, 2024
00ac3dd
Fix E2E tests
somebody1234 May 31, 2024
384eabc
WIP: Fix opening projects
somebody1234 May 31, 2024
9b864a6
Fix React Query mutation effect dependencies
somebody1234 May 31, 2024
0f0a9c7
Fix `ProjectIcon` appearance
somebody1234 May 31, 2024
d778232
Add mutation listeners for labels
somebody1234 Jun 3, 2024
cc28a7d
Merge branch 'develop' into wip/sb/dashboard-122
somebody1234 Jun 4, 2024
d8f4b0f
Merge branch 'develop' into wip/sb/dashboard-122
somebody1234 Jun 10, 2024
78be553
Fix E2E tests
somebody1234 Jun 10, 2024
af31300
Fix lint errors
somebody1234 Jun 10, 2024
3626d6e
Address QA
somebody1234 Jun 10, 2024
1ffaf98
Prettier
somebody1234 Jun 10, 2024
530dd07
Merge branch 'develop' into wip/sb/dashboard-122
somebody1234 Jun 11, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
8 changes: 6 additions & 2 deletions app/ide-desktop/eslint.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -50,8 +50,7 @@ const NOT_CONSTANT_CASE = `/^(?!${WHITELISTED_CONSTANTS}$|_?[A-Z][A-Z0-9]*(_[A-Z
/** @type {{ selector: string; message: string; }[]} */
const RESTRICTED_SYNTAXES = [
{
selector:
':matches(ImportDeclaration:has(ImportSpecifier), ExportDeclaration, ExportSpecifier)',
selector: ':matches(ImportDeclaration:has(ImportSpecifier))',
message: 'No {} imports and exports',
},
{
Expand Down Expand Up @@ -214,6 +213,11 @@ const RESTRICTED_SYNTAXES = [
)`,
message: 'Use a `getText()` from `useText` instead of a literal string',
},
{
selector: `JSXAttribute[name.name=/^(?:className)$/] TemplateLiteral`,
message:
'Use `tv` from `tailwind-variants` or `twMerge` from `tailwind-merge` instead of template strings for classes',
},
{
selector: 'JSXOpeningElement[name.name=button] > JSXIdentifier',
message: 'Use `Button` or `UnstyledButton` instead of `button`',
Expand Down
12 changes: 12 additions & 0 deletions app/ide-desktop/lib/assets/drop_files.svg
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 0 additions & 4 deletions app/ide-desktop/lib/assets/home.svg

This file was deleted.

8 changes: 0 additions & 8 deletions app/ide-desktop/lib/assets/home2.svg

This file was deleted.

1 change: 0 additions & 1 deletion app/ide-desktop/lib/dashboard/404.html
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,6 @@
<script type="module" src="./src/entrypoint.ts" defer></script>
</head>
<body>
<div id="app"></div>
<div id="enso-dashboard" class="enso-dashboard"></div>
<div id="enso-chat" class="enso-chat"></div>
<div id="enso-portal-root" class="enso-portal-root"></div>
Expand Down
49 changes: 16 additions & 33 deletions app/ide-desktop/lib/dashboard/e2e/actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -167,9 +167,14 @@ export function locateLabelsPanelLabels(page: test.Page) {
)
}

/** Find a "home" button (if any) on the current page. */
export function locateHomeButton(page: test.Locator | test.Page) {
return page.getByRole('button', { name: 'Home' }).getByText('Home')
/** Find a "cloud" category button (if any) on the current page. */
export function locateCloudButton(page: test.Locator | test.Page) {
return page.getByRole('button', { name: 'Cloud' }).getByText('Cloud')
}

/** Find a "local" category button (if any) on the current page. */
export function locateLocalButton(page: test.Locator | test.Page) {
return page.getByRole('button', { name: 'Local' }).getByText('Local')
}

/** Find a "trash" button (if any) on the current page. */
Expand Down Expand Up @@ -331,9 +336,16 @@ export function locateUploadFilesButton(page: test.Locator | test.Page) {
return page.getByRole('button', { name: 'Upload Files' }).getByText('Upload Files')
}

/** Find a "start modal" button (if any) on the current page. */
export function locateStartModalButton(page: test.Locator | test.Page) {
return page
.getByRole('button', { name: 'Start with a template' })
.getByText('Start with a template')
}

/** Find a "new project" button (if any) on the current page. */
export function locateNewProjectButton(page: test.Locator | test.Page) {
return page.getByRole('button', { name: 'New Project' }).getByText('New Project')
return page.getByRole('button', { name: 'New Empty Project' }).getByText('New Empty Project')
}

/** Find a "new folder" button (if any) on the current page. */
Expand Down Expand Up @@ -425,11 +437,6 @@ export function locateSortDescendingIcon(page: test.Locator | test.Page) {

// === Page locators ===

/** Find a "home page" icon (if any) on the current page. */
export function locateHomePageIcon(page: test.Locator | test.Page) {
return page.getByRole('button').filter({ has: page.getByAltText('Home') })
}

/** Find a "drive page" icon (if any) on the current page. */
export function locateDrivePageIcon(page: test.Locator | test.Page) {
return page.getByRole('button').filter({ has: page.getByAltText('Catalog') })
Expand Down Expand Up @@ -803,23 +810,6 @@ export async function passTermsAndConditionsDialog({ page }: MockParams) {
}
}

// ========================
// === mockIDEContainer ===
// ========================

/** Make the IDE container have a non-zero size. */
// This syntax is required for Playwright to work properly.
// eslint-disable-next-line no-restricted-syntax
export async function mockIDEContainer({ page }: MockParams) {
await page.evaluate(() => {
const ideContainer = document.getElementById('app')
if (ideContainer) {
ideContainer.style.height = '100vh'
ideContainer.style.width = '100vw'
}
})
}

// ===============
// === mockApi ===
// ===============
Expand All @@ -838,7 +828,6 @@ export const mockApi = apiModule.mockApi
export async function mockAll({ page }: MockParams) {
const api = await mockApi({ page })
await mockDate({ page })
await mockIDEContainer({ page })
return { api }
}

Expand All @@ -852,12 +841,6 @@ export async function mockAll({ page }: MockParams) {
export async function mockAllAndLogin({ page }: MockParams) {
const mocks = await mockAll({ page })
await login({ page })

await passTermsAndConditionsDialog({ page })

// This MUST run after login, otherwise the element's styles are reset when the browser
// is navigated to another page.
await mockIDEContainer({ page })

return mocks
}
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ test.test('can drop onto root directory dropzone', async ({ page }) => {
const parentLeft = await actions.getAssetRowLeftPx(assetRows.nth(0))
const childLeft = await actions.getAssetRowLeftPx(assetRows.nth(1))
test.expect(childLeft, 'child is indented further than parent').toBeGreaterThan(parentLeft)
await assetRows.nth(1).dragTo(actions.locateRootDirectoryDropzone(page))
await assetRows.nth(1).dragTo(actions.locateRootDirectoryDropzone(page), { force: true })
const firstLeft = await actions.getAssetRowLeftPx(assetRows.nth(0))
const secondLeft = await actions.getAssetRowLeftPx(assetRows.nth(1))
test.expect(firstLeft, 'siblings have same indentation').toEqual(secondLeft)
Expand Down
4 changes: 2 additions & 2 deletions app/ide-desktop/lib/dashboard/e2e/delete.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ test.test('delete and restore', async ({ page }) => {
await actions.locateRestoreFromTrashButton(contextMenu).click()
await actions.expectTrashPlaceholderRow(page)

await actions.locateHomeButton(page).click()
await actions.locateCloudButton(page).click()
await test.expect(assetRows).toHaveCount(1)
})

Expand All @@ -45,6 +45,6 @@ test.test('delete and restore (keyboard)', async ({ page }) => {
await actions.press(page, 'Mod+R')
await actions.expectTrashPlaceholderRow(page)

await actions.locateHomeButton(page).click()
await actions.locateCloudButton(page).click()
await test.expect(assetRows).toHaveCount(1)
})
1 change: 0 additions & 1 deletion app/ide-desktop/lib/dashboard/e2e/driveView.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ test.test('drive view', async ({ page }) => {
await test.expect(assetRows).toHaveCount(1)
await test.expect(actions.locateAssetsTable(page)).toBeVisible()
await actions.locateNewProjectButton(page).click()
await test.expect(assetRows).toHaveCount(2)
await test.expect(actions.locateEditor(page)).toBeVisible()
await actions.locateDrivePageIcon(page).click()
await test.expect(assetRows).toHaveCount(2)
Expand Down
7 changes: 0 additions & 7 deletions app/ide-desktop/lib/dashboard/e2e/pageSwitcher.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,16 +12,9 @@ test.test('page switcher', async ({ page }) => {

await actions.locateDrivePageIcon(page).click()
await test.expect(actions.locateDriveView(page)).toBeVisible()
await test.expect(actions.locateSamplesList(page)).not.toBeVisible()
await test.expect(actions.locateEditor(page)).not.toBeVisible()

await actions.locateHomePageIcon(page).click()
await test.expect(actions.locateDriveView(page)).not.toBeVisible()
await test.expect(actions.locateSamplesList(page)).toBeVisible()
await test.expect(actions.locateEditor(page)).not.toBeVisible()

await actions.locateEditorPageIcon(page).click()
await test.expect(actions.locateDriveView(page)).not.toBeVisible()
await test.expect(actions.locateSamplesList(page)).not.toBeVisible()
await test.expect(actions.locateEditor(page)).toBeVisible()
})
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,10 @@ import * as actions from './actions'

test.test.beforeEach(actions.mockAllAndLogin)

test.test('create empty project', async ({ page }) => {
await actions.locateHomePageIcon(page).click()
// The first "sample" is a button to create a new empty project.
await actions.locateSamples(page).nth(0).click()
await test.expect(actions.locateEditor(page)).toBeVisible()
})

test.test('create project from template', async ({ page }) => {
await actions.locateHomePageIcon(page).click()
await actions.locateStartModalButton(page).click()
// The second "sample" is the first template.
await actions.locateSamples(page).nth(1).click()
await test.expect(actions.locateEditor(page)).toBeVisible()
await test.expect(actions.locateSamples(page).first()).not.toBeVisible()
})
1 change: 0 additions & 1 deletion app/ide-desktop/lib/dashboard/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,6 @@
<script type="module" src="./src/entrypoint.ts" defer></script>
</head>
<body>
<div id="app"></div>
<div id="enso-dashboard" class="enso-dashboard"></div>
<div id="enso-chat" class="enso-chat"></div>
<div id="enso-portal-root" class="enso-portal-root"></div>
Expand Down
6 changes: 3 additions & 3 deletions app/ide-desktop/lib/dashboard/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
"build": "vite build",
"dev": "vite",
"dev:e2e": "vite -c vite.test.config.ts",
"dev:e2e:ci": "vite -c vite.test.config.ts build && vite preview --port 8080 --strictPort",
"test": "npm run test:unit && npm run test:e2e",
"test:unit": "vitest run",
"test:unit:debug": "vitest",
Expand All @@ -39,7 +40,6 @@
"@sentry/react": "^7.74.0",
"@tanstack/react-query": "5.37.1",
"ajv": "^8.12.0",
"clsx": "^1.1.1",
"enso-common": "^1.0.0",
"is-network-error": "^1.0.1",
"monaco-editor": "0.48.0",
Expand All @@ -53,11 +53,11 @@
"react-stately": "^3.31.0",
"react-toastify": "^9.1.3",
"tailwind-merge": "^2.3.0",
"tailwind-variants": "0.2.1",
"tiny-invariant": "^1.3.3",
"ts-results": "^3.3.0",
"validator": "^13.12.0",
"zod": "^3.23.8",
"tailwind-variants": "0.2.1"
"zod": "^3.23.8"
},
"devDependencies": {
"@babel/plugin-syntax-import-assertions": "^7.23.3",
Expand Down
4 changes: 2 additions & 2 deletions app/ide-desktop/lib/dashboard/playwright.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ export default test.defineConfig({
testDir: './e2e',
fullyParallel: true,
forbidOnly: true,
workers: 1,
workers: process.env.PROD ? 8 : 1,
repeatEach: process.env.CI ? 3 : 1,
expect: {
toHaveScreenshot: { threshold: 0 },
Expand Down Expand Up @@ -50,7 +50,7 @@ export default test.defineConfig({
},
},
webServer: {
command: 'npm run dev:e2e',
command: process.env.CI || process.env.PROD ? 'npm run dev:e2e:ci' : 'npm run dev:e2e',
port: 8080,
reuseExistingServer: false,
},
Expand Down
47 changes: 29 additions & 18 deletions app/ide-desktop/lib/dashboard/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,8 @@ import * as appUtils from '#/appUtils'

import * as inputBindingsModule from '#/configurations/inputBindings'

import * as backendHooks from '#/hooks/backendHooks'

import AuthProvider, * as authProvider from '#/providers/AuthProvider'
import BackendProvider from '#/providers/BackendProvider'
import InputBindingsProvider from '#/providers/InputBindingsProvider'
Expand All @@ -53,9 +55,7 @@ import LoggerProvider from '#/providers/LoggerProvider'
import type * as loggerProvider from '#/providers/LoggerProvider'
import ModalProvider, * as modalProvider from '#/providers/ModalProvider'
import * as navigator2DProvider from '#/providers/Navigator2DProvider'
import RemoteBackendProvider from '#/providers/RemoteBackendProvider'
import SessionProvider from '#/providers/SessionProvider'
import SupportsLocalBackendProvider from '#/providers/SupportsLocalBackendProvider'

import ConfirmRegistration from '#/pages/authentication/ConfirmRegistration'
import ForgotPassword from '#/pages/authentication/ForgotPassword'
Expand Down Expand Up @@ -212,7 +212,7 @@ export interface AppRouterProps extends AppProps {
* because the {@link AppRouter} relies on React hooks, which can't be used in the same React
* component as the component that defines the provider. */
function AppRouter(props: AppRouterProps) {
const { logger, supportsLocalBackend, isAuthenticationDisabled, shouldShowDashboard } = props
const { logger, isAuthenticationDisabled, shouldShowDashboard } = props
const { onAuthenticated, projectManagerUrl, projectManagerRootDirectory } = props
const { portalRoot } = props
// `navigateHooks.useNavigate` cannot be used here as it relies on `AuthProvider`, which has not
Expand All @@ -222,6 +222,14 @@ function AppRouter(props: AppRouterProps) {
const { localStorage } = localStorageProvider.useLocalStorage()
const { setModal } = modalProvider.useSetModal()
const navigator2D = navigator2DProvider.useNavigator2D()
const [remoteBackend, setRemoteBackend] = React.useState<Backend | null>(null)
const [localBackend] = React.useState(() =>
projectManagerUrl != null && projectManagerRootDirectory != null
? new LocalBackend(projectManagerUrl, projectManagerRootDirectory)
: null
)
backendHooks.useObserveBackend(remoteBackend)
backendHooks.useObserveBackend(localBackend)
if (detect.IS_DEV_MODE) {
// @ts-expect-error This is used exclusively for debugging.
window.navigate = navigate
Expand All @@ -246,6 +254,20 @@ function AppRouter(props: AppRouterProps) {
}
}, [/* should never change */ localStorage, /* should never change */ inputBindingsRaw])

React.useEffect(() => {
if (remoteBackend) {
void remoteBackend.logEvent('open_app')
const logCloseEvent = () => void remoteBackend.logEvent('close_app')
window.addEventListener('beforeunload', logCloseEvent)
return () => {
window.removeEventListener('beforeunload', logCloseEvent)
logCloseEvent()
}
} else {
return
}
}, [remoteBackend])

const inputBindings = React.useMemo(() => {
const updateLocalStorage = () => {
localStorage.set(
Expand Down Expand Up @@ -305,12 +327,6 @@ function AppRouter(props: AppRouterProps) {
const refreshUserSession =
authService?.cognito.refreshUserSession.bind(authService.cognito) ?? null
const registerAuthEventListener = authService?.registerAuthEventListener ?? null
const initialBackend: Backend =
isAuthenticationDisabled && projectManagerUrl != null && projectManagerRootDirectory != null
? new LocalBackend(projectManagerUrl, projectManagerRootDirectory)
: // This is SAFE, because the backend is always set by the authentication flow.
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
null!

React.useEffect(() => {
if ('menuApi' in window) {
Expand Down Expand Up @@ -439,27 +455,22 @@ function AppRouter(props: AppRouterProps) {
)

let result = routes

result = <InputBindingsProvider inputBindings={inputBindings}>{result}</InputBindingsProvider>
result = (
<SupportsLocalBackendProvider supportsLocalBackend={supportsLocalBackend}>
<BackendProvider remoteBackend={remoteBackend} localBackend={localBackend}>
{result}
</SupportsLocalBackendProvider>
</BackendProvider>
)
result = <InputBindingsProvider inputBindings={inputBindings}>{result}</InputBindingsProvider>
result = <RemoteBackendProvider>{result}</RemoteBackendProvider>
result = (
<AuthProvider
shouldStartInOfflineMode={isAuthenticationDisabled}
supportsLocalBackend={supportsLocalBackend}
setRemoteBackend={setRemoteBackend}
authService={authService}
onAuthenticated={onAuthenticated}
projectManagerUrl={projectManagerUrl}
projectManagerRootDirectory={projectManagerRootDirectory}
>
{result}
</AuthProvider>
)
result = <BackendProvider initialBackend={initialBackend}>{result}</BackendProvider>
result = (
<SessionProvider
mainPageUrl={mainPageUrl}
Expand Down