diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 2bc5e0661f8..3f47f43c86b 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -310,7 +310,7 @@ jobs: flyctl launch --no-deploy --copy-config --name "$APP_NAME" --image-label latest -o personal popd fi - flyctl deploy . --config ./apps/wing-console/console/app/preview/fly.toml --app "$APP_NAME" --image-label latest --vm-memory 512 --strategy immediate + flyctl deploy . --config ./apps/wing-console/console/app/preview/fly.toml --app "$APP_NAME" --image-label latest --vm-memory 1024 --strategy immediate flyctl scale count 1 --yes --app "$APP_NAME" echo "deploytime=$(TZ=UTC date +'%Y-%m-%d %H:%M')" >> $GITHUB_OUTPUT diff --git a/.github/workflows/periodic-azure-clean.yml b/.github/workflows/periodic-azure-clean.yml new file mode 100644 index 00000000000..3ac3e90dbaf --- /dev/null +++ b/.github/workflows/periodic-azure-clean.yml @@ -0,0 +1,37 @@ +name: Periodic Azure cleanup + +on: + schedule: + - cron: "0 0 * * 1" # Every Saturday at midnight UTC, I assume the main build won't be triggered right before this one + workflow_dispatch: {} + +env: + MANUAL: ${{ github.event_name == 'workflow_dispatch' }} + +jobs: + azure-cleanup: + runs-on: ubuntu-latest + steps: + - name: test if is maintainer + uses: tspascoal/get-user-teams-membership@v3 + id: testUserGroup + if: ${{ env.MANUAL == 'true' }} + with: + username: ${{ github.actor }} + team: "maintainers" + GITHUB_TOKEN: ${{ secrets.GH_GROUPS_READ_TOKEN }} + - name: cancel run if not allowed + if: ${{ env.MANUAL == 'true' && steps.testUserGroup.outputs.isTeamMember == 'false' }} + run: | + echo "User ${{github.actor}} is not allowed to dispatch this action." + exit 1 + + - name: Configure azure credentials + uses: azure/login@v2 + with: + creds: ${{ secrets.AZURE_CREDENTIALS }} + - name: Remove all resources + run: | + for rg in $(az group list --query "[].name" -o tsv); do + az group delete --name $rg --yes --no-wait + done diff --git a/Cargo.lock b/Cargo.lock index fe1a15e1e2e..72b67505c2b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1657,7 +1657,7 @@ dependencies = [ [[package]] name = "wingcli" -version = "0.59.24" +version = "0.74.53" dependencies = [ "anstyle", "camino", diff --git a/apps/wing-console/console/app/demo/main.w b/apps/wing-console/console/app/demo/main.w index 035874567ca..b0209cc3ddc 100644 --- a/apps/wing-console/console/app/demo/main.w +++ b/apps/wing-console/console/app/demo/main.w @@ -43,6 +43,7 @@ class myBucket { } let myB = new myBucket() as "MyUIComponentBucket"; + let putfucn = new cloud.Function(inflight () => { myB.put("test", "Test"); }) as "PutFileInCustomBucket"; @@ -106,6 +107,7 @@ let table = new ex.Table( let rateSchedule = new cloud.Schedule(cloud.ScheduleProps{ rate: 5m }) as "Rate Schedule"; +nodeof(rateSchedule).expanded = true; rateSchedule.onTick(inflight () => { log("Rate schedule ticked!"); @@ -165,10 +167,12 @@ test "Add fixtures" { class WidgetService { data: cloud.Bucket; counter: cloud.Counter; + bucket: myBucket; new() { this.data = new cloud.Bucket(); this.counter = new cloud.Counter(); + this.bucket = new myBucket() as "MyInternalBucket"; // a field displays a labeled value, with optional refreshing new ui.Field( diff --git a/apps/wing-console/console/app/test/describe.ts b/apps/wing-console/console/app/test/describe.ts index 5d1e7b70cea..a9e258b04ca 100644 --- a/apps/wing-console/console/app/test/describe.ts +++ b/apps/wing-console/console/app/test/describe.ts @@ -26,13 +26,19 @@ import { createConsoleApp } from "../dist/index.js"; * `describe(wingfile, callback)`. Any tests added in * this callback will belong to the group. */ -export const describe = (wingfile: string, callback: () => void) => { +export const describe = ( + wingfile: string, + callback: () => void, + options?: { + requireSignIn?: boolean; + }, +) => { let server: { port: number; close: () => void } | undefined; test.beforeEach(async ({ page }) => { server = await createConsoleApp({ wingfile: path.resolve(__dirname, wingfile), - requireSignIn: false, + requireSignIn: options?.requireSignIn ?? false, }); await page.goto(`http://localhost:${server.port}/`); diff --git a/apps/wing-console/console/app/test/login/login.test.ts b/apps/wing-console/console/app/test/login/login.test.ts new file mode 100644 index 00000000000..e0ccde0af0e --- /dev/null +++ b/apps/wing-console/console/app/test/login/login.test.ts @@ -0,0 +1,40 @@ +import { expect, test } from "@playwright/test"; + +import { describe } from "../describe.js"; + +describe( + `${__dirname}/main.w`, + () => { + test("Sign in modal is visible when required", async ({ page }) => { + const signinModal = page.getByTestId("signin-modal"); + await expect(signinModal).toBeVisible(); + }); + }, + { requireSignIn: true }, +); + +describe( + `${__dirname}/main.w`, + () => { + test("GitHub button is clickable", async ({ page }) => { + const githubLoginButton = page.getByTestId("signin-github-button"); + await expect(githubLoginButton).toBeVisible(); + await expect(githubLoginButton).toBeEnabled(); + await githubLoginButton.click(); + }); + }, + { requireSignIn: true }, +); + +describe( + `${__dirname}/main.w`, + () => { + test("Google button is clickable", async ({ page }) => { + const googleSignInButton = page.getByTestId("signin-google-button"); + await expect(googleSignInButton).toBeVisible(); + await expect(googleSignInButton).toBeEnabled(); + await googleSignInButton.click(); + }); + }, + { requireSignIn: true }, +); diff --git a/apps/wing-console/console/app/test/login/main.w b/apps/wing-console/console/app/test/login/main.w new file mode 100644 index 00000000000..6758e443e08 --- /dev/null +++ b/apps/wing-console/console/app/test/login/main.w @@ -0,0 +1,2 @@ +bring cloud; + diff --git a/apps/wing-console/console/design-system/src/headless/tree-item.tsx b/apps/wing-console/console/design-system/src/headless/tree-item.tsx index e89df902931..e30b4891ead 100644 --- a/apps/wing-console/console/design-system/src/headless/tree-item.tsx +++ b/apps/wing-console/console/design-system/src/headless/tree-item.tsx @@ -121,6 +121,12 @@ export const TreeItem = ({ }); const canBeExpanded = !!children; + useEffect(() => { + if (selected) { + ref.current?.scrollIntoView(); + } + }, [selected, ref]); + return (
+ Are you sure you want to reset all state and restart the + application? +
+