diff --git a/.github/workflows/previews.yml b/.github/workflows/previews.yml new file mode 100644 index 00000000..d8fff4a5 --- /dev/null +++ b/.github/workflows/previews.yml @@ -0,0 +1,249 @@ +name: Previews + +on: + pull_request: + types: [opened, reopened, synchronize, closed] + +env: + TOKEN: ${{ secrets.DIGITAL_OCEAN_PROXY_TOKEN }} + URL: ${{ vars.DIGITAL_OCEAN_PROXY_URL }} + APP_NAME: pdc-ui-admin-preview-${{ github.event.pull_request.number }} + PROJECT_ID: ${{ secrets.DIGITAL_OCEAN_PROJECT_ID }} + BRANCH: ${{ github.event.pull_request.head.ref }} + +jobs: + post-apps: + runs-on: ubuntu-latest + permissions: + pull-requests: write + if: | + github.actor != 'dependabot[bot]' && + !cancelled() && + github.event.pull_request.head.repo.full_name == github.repository && + contains(fromJSON('["opened", "reopened", "synchronize"]'), github.event.action) + needs: delete-app + concurrency: + group: preview-admin-app-${{ github.event.pull_request.number }} + cancel-in-progress: false + steps: + - name: Create or recreate a preview admin app for this PR + id: create-app-get-url + run: | + set -eo pipefail + app_spec=$(cat <> "${GITHUB_OUTPUT}" + subdomain=$(echo "${live_url}" | + sed 's/^https:\/\/\([0-9a-z-]\+\)\.ondigitalocean.app/\1/') + echo "subdomain=${subdomain}" >> "${GITHUB_OUTPUT}" + else + echo "The deployment of a deploy preview succeeded but couldn't find the URL." + exit 155 + fi + - name: Add deploy preview URL to the PR comments + uses: thollander/actions-comment-pull-request@v3 + with: + message: | + 🔎 A preview deployment of this pull request is available at https://utilities.philanthropydatacommons.org/${{ steps.create-app-get-url.outputs.subdomain }}/ (and upstream ${{ steps.create-app-get-url.outputs.live_url }}) assuming this PR is still open. 🔍 + comment-tag: deploy-preview-url + + delete-app: + runs-on: ubuntu-latest + if: | + github.actor != 'dependabot[bot]' && + github.event.pull_request.head.repo.full_name == github.repository && + contains(fromJSON('["closed", "synchronize"]'), github.event.action) + concurrency: + group: preview-admin-app-${{ github.event.pull_request.number }} + cancel-in-progress: false + steps: + - name: Delete a preview app for this PR if it exists + run: | + set -eo pipefail + # Get the list of applications (there is no filter by name call) + if ! app_response=$(curl --fail-with-body -X GET \ + -H "Accept: application/json" \ + -H "Authorization: Bearer ${TOKEN}" \ + --url "${URL}/v2/apps") + then + echo "App list could not be found. Response:" + echo "${app_response}" + # Failure to GET a list of apps should fail the task. + exit 153 + fi + app_id=$(echo "${app_response}" | + jq -r -c '.apps[] | select (.spec.name == "${{ env.APP_NAME }}") | .id') + if [ "${app_id}" != "" ] && [ "${app_id}" != "null" ]; then + # Found the app, delete it. Failure on DELETE should fail the task. + curl --fail-with-body -X DELETE \ + -H "Accept: application/json" \ + -H "Authorization: Bearer ${TOKEN}" \ + --url "${URL}/v2/apps/${app_id}" + else + # Failure to find the app in a successfully-gotten list should not + # fail the task. And we cannot print the list because secrets show. + echo "WARNING: ${{ env.APP_NAME }} was not found in the list." + fi diff --git a/apps/admin-interface/index.html b/apps/admin-interface/index.html index 20380584..f00d3439 100644 --- a/apps/admin-interface/index.html +++ b/apps/admin-interface/index.html @@ -13,6 +13,10 @@ Philanthropy Data Commons + diff --git a/apps/admin-interface/vite.config.ts b/apps/admin-interface/vite.config.ts index 71a6eaee..1f5d6fc0 100644 --- a/apps/admin-interface/vite.config.ts +++ b/apps/admin-interface/vite.config.ts @@ -15,4 +15,5 @@ export default defineConfig({ port: Number.parseInt(process.env.PORT ?? '3000'), host: process.env.HOST ?? 'localhost', }, + base: '', }); diff --git a/packages/utilities/src/router.ts b/packages/utilities/src/router.ts index 0c9d7404..ef8b64cc 100644 --- a/packages/utilities/src/router.ts +++ b/packages/utilities/src/router.ts @@ -1,6 +1,12 @@ import { createWebHistory, createRouter } from 'vue-router'; import type { Router, RouteRecordRaw } from 'vue-router'; +declare global { + interface Window { + __APP_BASE__: string | undefined; + } +} + declare module 'vue-router' { interface RouteMeta { requiresAuth?: boolean; @@ -9,7 +15,7 @@ declare module 'vue-router' { const createAppRouter = (routes: RouteRecordRaw[]): Router => createRouter({ - history: createWebHistory(), + history: createWebHistory(window.__APP_BASE__ ?? '/'), routes, });