Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 2 additions & 7 deletions apps/www/components/Hero/Hero.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,9 @@ const Hero = () => {
<div className="mx-auto max-w-2xl lg:col-span-6 lg:flex lg:items-center justify-center text-center">
<div className="relative z-10 lg:h-auto pt-[90px] lg:pt-[90px] lg:min-h-[300px] flex flex-col items-center justify-center sm:mx-auto md:w-3/4 lg:mx-0 lg:w-full gap-4 lg:gap-8">
<AnnouncementBadge
url="/launch-week#main-stage"
url="/launch-week"
badge="LW15"
announcement={
<>
<span className="hidden md:inline">Day 5: </span>
{announcement.launch}
</>
}
announcement={announcement.launch}
className="lg:-mt-8 mb-4 lg:mb-0"
hasArrow
/>
Expand Down
49 changes: 24 additions & 25 deletions apps/www/components/LaunchWeek/15/LW15Hackathon.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,35 +11,34 @@ const MotionImage = motion(Image)

const LW15Hackathon: FC = () => {
return (
<>
<SectionContainer
className="!max-w-none lg:!container lw-nav-anchor flex flex-col lg:grid lg:grid-cols-5 gap-4 lg:gap-2"
id="build-stage"
>
<LW15HackathonImage className="hidden lg:flex" />
<div className="w-full h-full flex flex-col justify-between gap-4 lg:col-span-3">
<div className="flex flex-col h-full gap-4">
<div className="flex flex-col text-6xl">
<div className="flex justify-between">
<span className="text-foreground-lighter">Hackathon</span>
</div>
<div>Starts this Friday.</div>
<SectionContainer
className="!max-w-none lg:!container lw-nav-anchor flex flex-col lg:grid lg:grid-cols-5 gap-4 lg:gap-2"
id="build-stage"
>
<LW15HackathonImage className="hidden lg:flex" />
<div className="w-full h-full flex flex-col justify-between gap-4 lg:col-span-3">
<div className="flex flex-col h-full gap-4">
<div className="flex flex-col text-6xl">
<div className="flex justify-between">
<span className="text-foreground-lighter">Hackathon</span>
</div>
<div>Starts this Friday.</div>
</div>
<LW15HackathonImage className="lg:hidden" />
<div className="flex flex-col justify-between gap-4">
<div className="text-2xl max-w-[600px] text-foreground-lighter">
<p>Build something amazing in 10 days using Supabase.</p>
<p>
Compete solo or in a team,
<br />
submit a quick demo and win.
</p>
</div>
</div>
<LW15HackathonImage className="lg:hidden" />
<div className="flex flex-col justify-between items-start gap-4">
<div className="text-2xl max-w-[350px] text-foreground-lighter">
<p>
Build something amazing <br /> in 10 days using Supabase.
</p>
<p>Compete solo or in a team, submit a quick demo and win.</p>
</div>
<Button type="secondary" size="medium" asChild>
<Link href="/blog/lw15-hackathon">Join Hackathon</Link>
</Button>
</div>
</SectionContainer>
</>
</div>
</SectionContainer>
)
}

Expand Down
2 changes: 1 addition & 1 deletion apps/www/components/LaunchWeek/15/LW15MainStage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -198,7 +198,7 @@ const CardsSlider: React.FC<Props> = ({
ref={swiperRef}
onSwiper={setControlledSwiper}
modules={[Controller, Navigation, A11y]}
initialSlide={4}
initialSlide={0}
spaceBetween={8}
slidesPerView={1.5}
breakpoints={{
Expand Down
2 changes: 1 addition & 1 deletion apps/www/components/LaunchWeek/15/data/lw15_data.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down
28 changes: 14 additions & 14 deletions apps/www/public/rss.xml
Original file line number Diff line number Diff line change
Expand Up @@ -280,20 +280,6 @@
<description>Technical deep dive into the new DBOS integration for Supabase</description>
<pubDate>Tue, 10 Dec 2024 00:00:00 -0700</pubDate>
</item>
<item>
<guid>https://supabase.com/blog/database-build-v2</guid>
<title>database.build v2: Bring-your-own-LLM</title>
<link>https://supabase.com/blog/database-build-v2</link>
<description>Use any OpenAI API compatible LLMs in database.build</description>
<pubDate>Fri, 06 Dec 2024 00:00:00 -0700</pubDate>
</item>
<item>
<guid>https://supabase.com/blog/restore-to-a-new-project</guid>
<title>Restore to a New Project</title>
<link>https://supabase.com/blog/restore-to-a-new-project</link>
<description>Effortlessly Clone Data into a New Supabase Project</description>
<pubDate>Fri, 06 Dec 2024 00:00:00 -0700</pubDate>
</item>
<item>
<guid>https://supabase.com/blog/hack-the-base</guid>
<title>Hack the Base! with Supabase</title>
Expand All @@ -308,6 +294,20 @@
<description>Highlights from Launch Week 13</description>
<pubDate>Fri, 06 Dec 2024 00:00:00 -0700</pubDate>
</item>
<item>
<guid>https://supabase.com/blog/database-build-v2</guid>
<title>database.build v2: Bring-your-own-LLM</title>
<link>https://supabase.com/blog/database-build-v2</link>
<description>Use any OpenAI API compatible LLMs in database.build</description>
<pubDate>Fri, 06 Dec 2024 00:00:00 -0700</pubDate>
</item>
<item>
<guid>https://supabase.com/blog/restore-to-a-new-project</guid>
<title>Restore to a New Project</title>
<link>https://supabase.com/blog/restore-to-a-new-project</link>
<description>Effortlessly Clone Data into a New Supabase Project</description>
<pubDate>Fri, 06 Dec 2024 00:00:00 -0700</pubDate>
</item>
<item>
<guid>https://supabase.com/blog/supabase-queues</guid>
<title>Supabase Queues</title>
Expand Down
2 changes: 2 additions & 0 deletions e2e/studio/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
```
Expand Down
197 changes: 164 additions & 33 deletions e2e/studio/features/sql-editor.spec.ts
Original file line number Diff line number Diff line change
@@ -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()
Expand Down Expand Up @@ -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,
})
}
})
})
Loading
Loading