diff --git a/apps/www/components/Hero/Hero.tsx b/apps/www/components/Hero/Hero.tsx index 470e4e20f2d04..072185fd20690 100644 --- a/apps/www/components/Hero/Hero.tsx +++ b/apps/www/components/Hero/Hero.tsx @@ -17,14 +17,9 @@ const Hero = () => {
- Day 5: - {announcement.launch} - - } + announcement={announcement.launch} className="lg:-mt-8 mb-4 lg:mb-0" hasArrow /> diff --git a/apps/www/components/LaunchWeek/15/LW15Hackathon.tsx b/apps/www/components/LaunchWeek/15/LW15Hackathon.tsx index 2560dd53874a0..335bb58c7caab 100644 --- a/apps/www/components/LaunchWeek/15/LW15Hackathon.tsx +++ b/apps/www/components/LaunchWeek/15/LW15Hackathon.tsx @@ -11,35 +11,34 @@ const MotionImage = motion(Image) const LW15Hackathon: FC = () => { return ( - <> - - -
-
-
-
- Hackathon -
-
Starts this Friday.
+ + +
+
+
+
+ Hackathon
+
Starts this Friday.
- -
-
-

Build something amazing in 10 days using Supabase.

-

- Compete solo or in a team, -
- submit a quick demo and win. -

-
+
+ +
+
+

+ Build something amazing
in 10 days using Supabase. +

+

Compete solo or in a team, submit a quick demo and win.

+
- - +
+ ) } diff --git a/apps/www/components/LaunchWeek/15/LW15MainStage.tsx b/apps/www/components/LaunchWeek/15/LW15MainStage.tsx index c366ef1eb82e3..37cc3ed75c2bc 100644 --- a/apps/www/components/LaunchWeek/15/LW15MainStage.tsx +++ b/apps/www/components/LaunchWeek/15/LW15MainStage.tsx @@ -198,7 +198,7 @@ const CardsSlider: React.FC = ({ ref={swiperRef} onSwiper={setControlledSwiper} modules={[Controller, Navigation, A11y]} - initialSlide={4} + initialSlide={0} spaceBetween={8} slidesPerView={1.5} breakpoints={{ diff --git a/apps/www/components/LaunchWeek/15/data/lw15_data.tsx b/apps/www/components/LaunchWeek/15/data/lw15_data.tsx index 977ae334fafdd..d7c40e3e7dc55 100644 --- a/apps/www/components/LaunchWeek/15/data/lw15_data.tsx +++ b/apps/www/components/LaunchWeek/15/data/lw15_data.tsx @@ -186,7 +186,7 @@ const days: (isDark?: boolean) => WeekDayProps[] = (isDark = true) => [ d: 5, dd: 'Fri', shipped: true, - isToday: true, + isToday: false, hasCountdown: false, blog: '/blog/persistent-storage-for-faster-edge-functions', date: 'Friday', diff --git a/apps/www/public/rss.xml b/apps/www/public/rss.xml index d486e2c8e6588..8ec0f09ffee83 100644 --- a/apps/www/public/rss.xml +++ b/apps/www/public/rss.xml @@ -280,20 +280,6 @@ Technical deep dive into the new DBOS integration for Supabase Tue, 10 Dec 2024 00:00:00 -0700 - - https://supabase.com/blog/database-build-v2 - database.build v2: Bring-your-own-LLM - https://supabase.com/blog/database-build-v2 - Use any OpenAI API compatible LLMs in database.build - Fri, 06 Dec 2024 00:00:00 -0700 - - - https://supabase.com/blog/restore-to-a-new-project - Restore to a New Project - https://supabase.com/blog/restore-to-a-new-project - Effortlessly Clone Data into a New Supabase Project - Fri, 06 Dec 2024 00:00:00 -0700 - https://supabase.com/blog/hack-the-base Hack the Base! with Supabase @@ -308,6 +294,20 @@ Highlights from Launch Week 13 Fri, 06 Dec 2024 00:00:00 -0700 + + https://supabase.com/blog/database-build-v2 + database.build v2: Bring-your-own-LLM + https://supabase.com/blog/database-build-v2 + Use any OpenAI API compatible LLMs in database.build + Fri, 06 Dec 2024 00:00:00 -0700 + + + https://supabase.com/blog/restore-to-a-new-project + Restore to a New Project + https://supabase.com/blog/restore-to-a-new-project + Effortlessly Clone Data into a New Supabase Project + Fri, 06 Dec 2024 00:00:00 -0700 + https://supabase.com/blog/supabase-queues Supabase Queues diff --git a/e2e/studio/README.md b/e2e/studio/README.md index 21e66a97f75c9..6ddfe554a10bc 100644 --- a/e2e/studio/README.md +++ b/e2e/studio/README.md @@ -10,6 +10,8 @@ Edit the `.env.local` file with your credentials and environment. ### Install the playwright browser +⚠️ This should be done in the `e2e/studio` directory + ```bash pnpm exec playwright install ``` diff --git a/e2e/studio/features/sql-editor.spec.ts b/e2e/studio/features/sql-editor.spec.ts index cf6428ab6c751..32f30dcd8cde5 100644 --- a/e2e/studio/features/sql-editor.spec.ts +++ b/e2e/studio/features/sql-editor.spec.ts @@ -1,55 +1,159 @@ -import { expect } from '@playwright/test' -import { env } from '../env.config' +import { expect, Page } from '@playwright/test' +import { isCLI } from '../utils/is-cli' import { test } from '../utils/test' import { toUrl } from '../utils/to-url' +const deleteQuery = async (page: Page, queryName: string) => { + const privateSnippet = page.getByLabel('private-snippets') + await privateSnippet.getByText(queryName).first().click({ button: 'right' }) + await page.getByRole('menuitem', { name: 'Delete query' }).click() + await expect(page.getByRole('heading', { name: 'Confirm to delete query' })).toBeVisible() + await page.getByRole('button', { name: 'Delete 1 query' }).click() +} + test.describe('SQL Editor', () => { - test('should check if SQL editor can run simple commands', async ({ page }) => { - await page.goto(toUrl(`/project/${env.PROJECT_REF}/sql/new?skip=true`)) + let page: Page + const pwTestQueryName = 'pw-test-query' - const editor = page.getByRole('code').nth(0) + test.beforeAll(async ({ browser, ref }) => { + test.setTimeout(60000) + + // Create a new table for the tests + page = await browser.newPage() + await page.goto(toUrl(`/project/${ref}/sql/new?skip=true`)) + + await page.evaluate((ref) => { + localStorage.removeItem('dashboard-history-default') + localStorage.removeItem(`dashboard-history-${ref}`) + }, ref) + + // intercept AI title generation to prevent flaky tests + await page.route('**/dashboard/api/ai/sql/title-v2', async (route) => { + await route.abort() + }) + }) + + test.beforeEach(async ({ ref }) => { + if ((await page.getByLabel('private-snippets').count()) === 0) { + return + } + + // since in local, we don't have access to the supabase platform, reloading would reload all the sql snippets. + if (isCLI()) { + await page.reload() + } + + // remove sql snippets for - "Untitled query" and "pw test query" + const privateSnippet = page.getByLabel('private-snippets') + let privateSnippetText = await privateSnippet.textContent() + while (privateSnippetText.includes('Untitled query')) { + deleteQuery(page, 'Untitled query') + + await page.waitForResponse( + (response) => + (response.url().includes(`projects/${ref}/content`) || + response.url().includes('projects/default/content')) && + response.request().method() === 'DELETE' + ) + await expect( + page.getByText('Successfully deleted 1 query'), + 'Delete confirmation toast should be visible' + ).toBeVisible({ + timeout: 50000, + }) + await page.waitForTimeout(1000) + privateSnippetText = + (await page.getByLabel('private-snippets').count()) > 0 + ? await privateSnippet.textContent() + : '' + } + + while (privateSnippetText.includes(pwTestQueryName)) { + deleteQuery(page, pwTestQueryName) + await page.waitForResponse( + (response) => + (response.url().includes(`projects/${ref}/content`) || + response.url().includes('projects/default/content')) && + response.request().method() === 'DELETE' + ) + await expect( + page.getByText('Successfully deleted 1 query'), + 'Delete confirmation toast should be visible' + ).toBeVisible({ + timeout: 50000, + }) + await page.waitForTimeout(1000) + privateSnippetText = + (await page.getByLabel('private-snippets').count()) > 0 + ? await privateSnippet.textContent() + : '' + } + }) + + test('should check if SQL editor can run simple commands', async () => { + await page.getByTestId('sql-editor-new-query-button').click() + await page.getByRole('menuitem', { name: 'Create a new snippet' }).click() // write some sql in the editor // This has to be done since the editor is not editable (input, textarea, etc.) - await editor.click() + await page.waitForTimeout(1000) + const editor = page.getByRole('code').nth(0) await editor.click() await page.keyboard.press('ControlOrMeta+KeyA') await page.keyboard.type(`select 'hello world';`) + await page.getByTestId('sql-run-button').click() - await page.getByRole('button', { name: /^Run( CTRL)?$/, exact: false }).click() + // verify the result + await expect(page.getByRole('gridcell', { name: 'hello world' })).toBeVisible({ + timeout: 5000, + }) - // Should say "Running..." - await expect(page.getByText('Running...')).toBeVisible() + // SQL written in the editor should not be the previous query. + await page.waitForTimeout(1000) + await editor.click() + await page.keyboard.press('ControlOrMeta+KeyA') + await page.keyboard.type(`select length('hello');`) + await page.getByTestId('sql-run-button').click() - // Wait until Running... is not visible - await expect(page.getByText('Running...')).not.toBeVisible() + // verify the result is updated. + await expect(page.getByRole('gridcell', { name: '5' })).toBeVisible({ + timeout: 5000, + }) + }) + + test('destructive query would tripper a warning modal', async () => { + await page.getByTestId('sql-editor-new-query-button').click() + await page.getByRole('menuitem', { name: 'Create a new snippet' }).click() - // clear the editor + // write some sql in the editor + // This has to be done since the editor is not editable (input, textarea, etc.) + await page.waitForTimeout(1000) + const editor = page.getByRole('code').nth(0) await editor.click() await page.keyboard.press('ControlOrMeta+KeyA') - await page.keyboard.press('Backspace') + await page.keyboard.type(`delete table 'test';`) + await page.getByTestId('sql-run-button').click() - // verify the result - const result = page.getByRole('gridcell', { name: 'hello world' }) - await expect(result).toBeVisible() - }) -}) + // verify warning modal is visible + expect(page.getByRole('heading', { name: 'Potential issue detected with' })).toBeVisible() + expect(page.getByText('Query has destructive')).toBeVisible() -test.describe('SQL Snippets', () => { - test('should create and load a new snippet', async ({ page }) => { - await page.goto(toUrl(`/project/${env.PROJECT_REF}/sql`)) + // reset test + await page.getByRole('button', { name: 'Cancel' }).click() + await page.waitForTimeout(500) + await editor.click() + await page.keyboard.press('ControlOrMeta+KeyA') + await page.keyboard.press('Backspace') + }) - const addButton = page.getByTestId('sql-editor-new-query-button') + test('should create and load a new snippet', async ({ ref }) => { const runButton = page.getByTestId('sql-run-button') await page.getByRole('button', { name: 'Favorites' }).click() await page.getByRole('button', { name: 'Shared' }).click() - await expect(page.getByText('No shared queries')).toBeVisible() - await expect(page.getByText('No favorite queries')).toBeVisible() // write some sql in the editor - await addButton.click() + await page.getByTestId('sql-editor-new-query-button').click() await page.getByRole('menuitem', { name: 'Create a new snippet' }).click() - const editor = page.getByRole('code').nth(0) await page.waitForTimeout(1000) await editor.click() @@ -77,25 +181,52 @@ test.describe('SQL Snippets', () => { await privateSnippet.getByText('Untitled query').click({ button: 'right' }) await page.getByRole('menuitem', { name: 'Rename query', exact: true }).click() await expect(page.getByRole('heading', { name: 'Rename' })).toBeVisible() - await page.getByRole('textbox', { name: 'Name' }).fill('test snippet') + await page.getByRole('textbox', { name: 'Name' }).fill(pwTestQueryName) await page.getByRole('button', { name: 'Rename query', exact: true }).click() - - const privateSnippet2 = privateSnippet.getByText('test snippet', { exact: true }) - await expect(privateSnippet2).toBeVisible() + await page.waitForResponse( + (response) => + (response.url().includes(`projects/${ref}/content`) || + response.url().includes('projects/default/content')) && + response.request().method() === 'PUT' && + response.status().toString().startsWith('2') + ) + await expect(privateSnippet.getByText(pwTestQueryName, { exact: true })).toBeVisible({ + timeout: 50000, + }) + const privateSnippet2 = await privateSnippet.getByText(pwTestQueryName, { exact: true }) // share with a team await privateSnippet2.click({ button: 'right' }) await page.getByRole('menuitem', { name: 'Share query with team' }).click() - await expect(page.getByRole('heading', { name: 'Confirm to share query: test' })).toBeVisible() + await expect(page.getByRole('heading', { name: 'Confirm to share query' })).toBeVisible() await page.getByRole('button', { name: 'Share query', exact: true }).click() + await page.waitForResponse( + (response) => + (response.url().includes(`projects/${ref}/content`) || + response.url().includes('projects/default/content')) && + response.request().method() === 'PUT' && + response.status().toString().startsWith('2') + ) const sharedSnippet = await page.getByLabel('project-level-snippets') - await expect(sharedSnippet).toContainText('test snippet') + await expect(sharedSnippet).toContainText(pwTestQueryName) // unshare a snippet - await sharedSnippet.getByText('test snippet').click({ button: 'right' }) + await sharedSnippet.getByText(pwTestQueryName).click({ button: 'right' }) await page.getByRole('menuitem', { name: 'Unshare query with team' }).click() await expect(page.getByRole('heading', { name: 'Confirm to unshare query:' })).toBeVisible() await page.getByRole('button', { name: 'Unshare query', exact: true }).click() await expect(sharedSnippet).not.toBeVisible() + + // delete snippet (for non-local environment) + if (!isCLI()) { + deleteQuery(page, pwTestQueryName) + + await expect( + page.getByText('Successfully deleted 1 query'), + 'Delete confirmation toast should be visible' + ).toBeVisible({ + timeout: 50000, + }) + } }) }) diff --git a/e2e/studio/features/table-editor.spec.ts b/e2e/studio/features/table-editor.spec.ts index 281c3f220d272..da74fe95ce3e3 100644 --- a/e2e/studio/features/table-editor.spec.ts +++ b/e2e/studio/features/table-editor.spec.ts @@ -1,4 +1,5 @@ import { expect, Page } from '@playwright/test' +import fs from 'fs' import { test } from '../utils/test' import { toUrl } from '../utils/to-url' @@ -15,8 +16,9 @@ const getSelectors = (tableName: string) => ({ saveBtn: (page) => page.getByRole('button', { name: 'Save' }), definitionTab: (page) => page.getByText('definition', { exact: true }), viewLines: (page) => page.locator('div.view-lines'), - insertRowBtn: (page) => page.getByTestId('table-editor-insert-new-row'), - insertModal: (page) => page.getByText('Insert a new row into'), + insertBtn: (page) => page.getByTestId('table-editor-insert-new-row'), + insertRow: (page) => page.getByText('Insert a new row into'), + insertColumn: (page) => page.getByText('Insert a new column into'), defaultValueInput: (page) => page.getByTestId('defaultValueColumn-input'), actionBarSaveRow: (page) => page.getByTestId('action-bar-save-row'), grid: (page) => page.getByRole('grid'), @@ -61,9 +63,6 @@ const createTable = async (page: Page, tableName: string) => { await s.saveBtn(page).click() - // wait till we see the success toast - // Text: Table tableName is good to go! - await expect( page.getByText(`Table ${tableName} is good to go!`), 'Success toast should be visible after table creation' @@ -77,7 +76,7 @@ const createTable = async (page: Page, tableName: string) => { ).toBeVisible() } -const deleteTables = async (page: Page, tableName: string) => { +const deleteTable = async (page: Page, tableName: string) => { const s = getSelectors(tableName) await page.waitForTimeout(500) @@ -94,11 +93,35 @@ const deleteTables = async (page: Page, tableName: string) => { ).toBeVisible() } +const deleteEnum = async (page: Page, enumName: string, ref: string) => { + // give it a second for interactions to load + await page.waitForResponse( + (response) => + response.url().includes(`pg-meta/${ref}/types`) || + response.url().includes('pg-meta/default/types') + ) + + // if enum (test) exists, delete it. + const exists = (await page.getByRole('cell', { name: enumName, exact: true }).count()) > 0 + if (!exists) return + + await page + .getByRole('row', { name: `public ${enumName}` }) + .getByRole('button') + .click() + await page.getByRole('menuitem', { name: 'Delete type' }).click() + await page.getByRole('heading', { name: 'Confirm to delete enumerated' }).click() + await page.getByRole('button', { name: 'Confirm delete' }).click() + await expect(page.getByText(`Successfully deleted "${enumName}"`)).toBeVisible() +} + test.describe('Table Editor', () => { let page: Page const testTableName = `pw-test-table-editor` const tableNameRlsEnabled = `pw-test-rls-enabled` const tableNameRlsDisabled = `pw-test-rls-disabled` + const tableNameEnum = `pw-test-enum` + const tableNameCsv = `pw-test-csv` test.beforeAll(async ({ browser, ref }) => { test.setTimeout(60000) @@ -111,18 +134,22 @@ test.describe('Table Editor', () => { await page.waitForTimeout(2000) // delete table name if it exists - await deleteTables(page, testTableName) - await deleteTables(page, tableNameRlsEnabled) - await deleteTables(page, tableNameRlsDisabled) + await deleteTable(page, testTableName) + await deleteTable(page, tableNameRlsEnabled) + await deleteTable(page, tableNameRlsDisabled) + await deleteTable(page, tableNameEnum) + await deleteTable(page, tableNameCsv) }) test.afterAll(async () => { test.setTimeout(60000) // delete all tables related to this test - await deleteTables(page, testTableName) - await deleteTables(page, tableNameRlsEnabled) - await deleteTables(page, tableNameRlsDisabled) + await deleteTable(page, testTableName) + await deleteTable(page, tableNameRlsEnabled) + await deleteTable(page, tableNameRlsDisabled) + await deleteTable(page, tableNameEnum) + await deleteTable(page, tableNameCsv) }) test('should perform all table operations sequentially', async ({ ref }) => { @@ -143,14 +170,14 @@ test.describe('Table Editor', () => { // 2. Insert test data await page.getByRole('button', { name: `View ${testTableName}` }).click() - await s.insertRowBtn(page).click() - await s.insertModal(page).click() + await s.insertBtn(page).click() + await s.insertRow(page).click() await s.defaultValueInput(page).fill('100') await s.actionBarSaveRow(page).click() await page.getByRole('button', { name: `View ${testTableName}` }).click() - await s.insertRowBtn(page).click() - await s.insertModal(page).click() + await s.insertBtn(page).click() + await s.insertRow(page).click() await s.defaultValueInput(page).fill('4') await s.actionBarSaveRow(page).click() @@ -225,7 +252,7 @@ test.describe('Table Editor', () => { 'Tables list should be visible in public schema' ).toBeVisible() - await deleteTables(page, testTableName) + await deleteTable(page, testTableName) }) test('should show rls accordingly', async () => { @@ -253,7 +280,140 @@ test.describe('Table Editor', () => { await page.getByRole('button', { name: `View ${tableNameRlsDisabled}` }).click() await expect(page.getByRole('button', { name: 'RLS disabled' })).toBeVisible() - await deleteTables(page, tableNameRlsEnabled) - await deleteTables(page, tableNameRlsDisabled) + await deleteTable(page, tableNameRlsEnabled) + await deleteTable(page, tableNameRlsDisabled) + }) + + test('add enums and show enums on table', async ({ ref }) => { + const ENUM_NAME = 'test_enum' + const ENUM_COLUMN_NAME = 'test_column' + + // clear local storage, as it might result in some flakiness + await page.evaluate((ref) => { + localStorage.removeItem('dashboard-history-default') + localStorage.removeItem(`dashboard-history-${ref}`) + }, ref) + await page.goto(toUrl(`/project/${ref}/database/types?schema=public`)) + + // delete enum if it exists + await deleteEnum(page, ENUM_NAME, ref) + + // create a new enum + await page.getByRole('button', { name: 'Create type' }).click() + await page.getByRole('textbox', { name: 'Name' }).fill(ENUM_NAME) + await page.locator('input[name="values.0.value"]').fill('value1') + await page.getByRole('button', { name: 'Add value' }).click() + await page.locator('input[name="values.1.value"]').fill('value2') + await page.getByRole('button', { name: 'Create type' }).click() + + // Wait for enum response to be completed + await page.waitForResponse( + (response) => + response.url().includes(`pg-meta/${ref}/types`) || + response.url().includes('pg-meta/default/types') + ) + + // verify enum is created + await expect(page.getByRole('cell', { name: ENUM_NAME, exact: true })).toBeVisible() + await expect(page.getByRole('cell', { name: 'value1, value2', exact: true })).toBeVisible() + + // create a new table with new column for enums + await page.goto(toUrl(`/project/${ref}/editor`)) + + const s = getSelectors(tableNameEnum) + await s.newTableBtn(page).click() + await s.tableNameInput(page).fill(tableNameEnum) + await s.createdAtExtraOptions(page).click() + await page.getByText('Is Nullable').click() + await s.createdAtExtraOptions(page).click() + await s.addColumnBtn(page).click() + await s.columnNameInput(page).fill(ENUM_COLUMN_NAME) + await page.getByRole('combobox').filter({ hasText: 'Choose a column type...' }).click() + await page.getByPlaceholder('Search types...').fill(ENUM_NAME) + await page.getByRole('option', { name: ENUM_NAME }).click() + await s.saveBtn(page).click() + + await expect( + page.getByText(`Table ${tableNameEnum} is good to go!`), + 'Success toast should be visible after table creation' + ).toBeVisible({ + timeout: 50000, + }) + + // Wait for the grid to be visible and data to be loaded + await expect(s.grid(page), 'Grid should be visible after inserting data').toBeVisible() + await expect(page.getByRole('columnheader', { name: ENUM_NAME })).toBeVisible() + + // insert row with enum value + await s.insertBtn(page).click() + await s.insertRow(page).click() + await page.getByRole('combobox').selectOption('value1') + await s.actionBarSaveRow(page).click() + await expect(page.getByRole('gridcell', { name: 'value1' })).toBeVisible() + + // insert row with another enum value + await s.insertBtn(page).click() + await s.insertRow(page).click() + await page.getByRole('combobox').selectOption('value2') + await s.actionBarSaveRow(page).click() + await expect(page.getByRole('gridcell', { name: 'value2' })).toBeVisible() + + // delete enum and enum table + await deleteTable(page, tableNameEnum) + await page.goto(toUrl(`/project/${ref}/database/types?schema=public`)) + await deleteEnum(page, ENUM_NAME, ref) + + // should end at the init link + // clear local storage, as it might result in some flakiness + await page.evaluate((ref) => { + localStorage.removeItem('dashboard-history-default') + localStorage.removeItem(`dashboard-history-${ref}`) + }, ref) + await page.goto(toUrl(`/project/${ref}/editor`)) + }) + + test('csv import works properly', async () => { + // create a new table and insert some data + await createTable(page, tableNameCsv) + const s = getSelectors(tableNameCsv) + await page.getByRole('button', { name: `View ${tableNameCsv}` }).click() + await s.insertBtn(page).click() + await s.insertRow(page).click() + await s.defaultValueInput(page).fill('123') + await s.actionBarSaveRow(page).click() + await s.insertBtn(page).click() + await s.insertRow(page).click() + await s.defaultValueInput(page).fill('456') + await s.actionBarSaveRow(page).click() + await s.insertBtn(page).click() + await s.insertRow(page).click() + await s.defaultValueInput(page).fill('789') + await s.actionBarSaveRow(page).click() + + // download csv + const tableBtn = await page.getByRole('button', { name: 'View pw-test-csv' }) + await tableBtn.getByRole('button').last().click() + await page.getByRole('menuitem', { name: 'Export data' }).click() + const downloadPromise = page.waitForEvent('download') + await page.getByRole('menuitem', { name: 'Export table as CSV' }).click() + const download = await downloadPromise + expect(download.suggestedFilename()).toContain('.csv') + const downloadPath = await download.path() + + // verify file contents + const csvContent = fs.readFileSync(downloadPath, 'utf-8').replace(/\r?\n/g, '\n') + const rows = csvContent.trim().split('\n') + const defaultColumnValues = rows.map((row) => { + const columns = row.split(',') + return columns[2].trim() + }) + const expectedDefaultColumnValues = ['defaultValueColumn', '123', '456', '789'] + defaultColumnValues.forEach((expectedValue) => { + expect(expectedDefaultColumnValues).toContain(expectedValue) + }) + + // remove the downloaded file + clean up tables + fs.unlinkSync(downloadPath) + await deleteTable(page, tableNameCsv) }) }) diff --git a/e2e/studio/utils/is-cli.ts b/e2e/studio/utils/is-cli.ts new file mode 100644 index 0000000000000..6387f4c3a9b12 --- /dev/null +++ b/e2e/studio/utils/is-cli.ts @@ -0,0 +1,11 @@ +import { env } from '../env.config' + +/** + * Returns true if running in CLI/self-hosted mode (IS_PLATFORM=false), + * false if running in hosted mode (IS_PLATFORM=true). + */ +export function isCLI(): boolean { + // IS_PLATFORM=true = hosted mode + // IS_PLATFORM=false = CLI/self-hosted mode + return env.IS_PLATFORM === 'false' +} diff --git a/packages/ui-patterns/src/Banners/data.json b/packages/ui-patterns/src/Banners/data.json index ab01eb3abafe6..ebbb6e84c6030 100644 --- a/packages/ui-patterns/src/Banners/data.json +++ b/packages/ui-patterns/src/Banners/data.json @@ -1,7 +1,7 @@ { - "text": "LW15: Day 5", - "launch": "Persistent Storage for Edge Functions", + "text": "Launch Week 15", + "launch": "View all the announcements", "launchDate": "2025-07-17T08:00:00.000-07:00", - "link": "/launch-week#main-stage", + "link": "/launch-week", "cta": "Learn more" }