diff --git a/.github/workflows/deploy-base.yml b/.github/workflows/deploy-base.yml index 902806b85..24b0f328e 100644 --- a/.github/workflows/deploy-base.yml +++ b/.github/workflows/deploy-base.yml @@ -18,37 +18,24 @@ on: SLACK_WEBHOOK: required: true + jobs: - deploy: - name: Deploy + build: + name: Build/push Docker Image runs-on: ubuntu-latest permissions: id-token: write contents: read environment: ${{ inputs.env }} - env: - ECS_CLUSTER: skate - ECS_SERVICE: skate-${{ inputs.env }} + outputs: + docker-tag-suffix: ${{ steps.build-push.outputs.docker-tag-suffix }} + sentry-release: ${{ steps.version-ids.outputs.sentry-release }} steps: - - name: Configure AWS credentials - uses: aws-actions/configure-aws-credentials@v4 - with: - role-to-assume: ${{ secrets.AWS_ROLE_ARN }} - aws-region: us-east-1 - uses: actions/checkout@v4 - name: Get version ids id: version-ids run: | echo "sentry-release=${{github.ref}}_${{github.sha}}" | tr / - >> "$GITHUB_OUTPUT" - - uses: getsentry/action-release@v1 - env: - SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }} - SENTRY_ORG: ${{ secrets.SENTRY_ORG }} - SENTRY_PROJECT: ${{ secrets.SENTRY_PROJECT }} - with: - environment: ${{ inputs.env }} - version: ${{steps.version-ids.outputs.sentry-release}} - ignore_missing: true - uses: mbta/actions/build-push-ecr@v2 id: build-push with: @@ -61,13 +48,38 @@ jobs: SENTRY_ORG: ${{ secrets.SENTRY_ORG }} SENTRY_PROJECT: ${{ secrets.SENTRY_PROJECT }} SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }} + + + deploy_ecs: + name: Deploy (ECS) + runs-on: ubuntu-latest + needs: build + permissions: + id-token: write + environment: ${{ inputs.env }} + env: + ECS_CLUSTER: skate + ECS_SERVICE: skate-${{ inputs.env }} + steps: - uses: mbta/actions/deploy-ecs@v2 with: role-to-assume: ${{ secrets.AWS_ROLE_ARN }} ecs-cluster: ${{ env.ECS_CLUSTER }} ecs-service: ${{ env.ECS_SERVICE }} - docker-tag: ${{ steps.build-push.outputs.docker-tag }} - - uses: mbta/actions/notify-slack-deploy@v1 + docker-tag: ${{ secrets.DOCKER_REPO }}:${{ needs.build.outputs.docker-tag-suffix }} + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + - uses: getsentry/action-release@v1 + env: + SENTRY_ORG: ${{ secrets.SENTRY_ORG }} + SENTRY_PROJECT: ${{ secrets.SENTRY_PROJECT }} + SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }} + with: + environment: ${{ inputs.env }} + version: ${{needs.build.outputs.sentry-release}} + ignore_missing: true + - uses: mbta/actions/notify-slack-deploy@v2 if: ${{ !cancelled() }} with: webhook-url: ${{ secrets.SLACK_WEBHOOK }} diff --git a/assets/src/components/detours/diversionPage.tsx b/assets/src/components/detours/diversionPage.tsx index 7e2839f31..718509c29 100644 --- a/assets/src/components/detours/diversionPage.tsx +++ b/assets/src/components/detours/diversionPage.tsx @@ -177,6 +177,223 @@ export const DiversionPage = ({ } } + const detourPanel = () => { + if (snapshot.matches({ "Detour Drawing": "Pick Route Pattern" })) { + return ( + { + if (route) { + send({ + type: "detour.route-pattern.select-route", + route, + }) + } else { + send({ + type: "detour.route-pattern.delete-route", + }) + } + }} + onSelectRoutePattern={(routePattern) => { + routePattern && + send({ + type: "detour.route-pattern.select-pattern", + routePattern, + }) + }} + onConfirm={() => send({ type: "detour.route-pattern.done" })} + /> + ) + } else if ( + snapshot.matches({ "Detour Drawing": "Editing" }) && + routePattern + ) { + return ( + send({ type: "detour.route-pattern.open" })} + /> + ) + } else if ( + snapshot.matches({ "Detour Drawing": "Share Detour" }) && + editDetour + ) { + return ( + { + send({ type: "detour.share.open-activate-modal" }) + } + : undefined + } + > + {snapshot.matches({ + "Detour Drawing": { + "Share Detour": "Activating", + }, + }) ? ( + { + send({ type: "detour.share.activate-modal.cancel" }) + }} + onBack={ + snapshot.can({ type: "detour.share.activate-modal.back" }) + ? () => { + send({ type: "detour.share.activate-modal.back" }) + } + : undefined + } + onNext={ + snapshot.can({ type: "detour.share.activate-modal.next" }) + ? () => { + send({ type: "detour.share.activate-modal.next" }) + } + : undefined + } + onActivate={ + snapshot.can({ + type: "detour.share.activate-modal.activate", + }) + ? () => { + send({ type: "detour.share.activate-modal.activate" }) + } + : undefined + } + > + {snapshot.matches({ + "Detour Drawing": { + "Share Detour": { Activating: "Selecting Duration" }, + }, + }) ? ( + { + send({ + type: "detour.share.activate-modal.select-duration", + duration: selectedDuration, + }) + }} + selectedDuration={selectedDuration} + /> + ) : snapshot.matches({ + "Detour Drawing": { + "Share Detour": { Activating: "Selecting Reason" }, + }, + }) ? ( + { + send({ + type: "detour.share.activate-modal.select-reason", + reason: selectedReason, + }) + }} + selectedReason={selectedReason} + /> + ) : snapshot.matches({ + "Detour Drawing": { + "Share Detour": { Activating: "Confirming" }, + }, + }) ? ( + + ) : null} + + ) : null} + + ) + } else if (snapshot.matches({ "Detour Drawing": "Active" })) { + return ( + { + send({ type: "detour.active.open-deactivate-modal" }) + } + : undefined + } + > + {snapshot.matches({ + "Detour Drawing": { Active: "Deactivating" }, + }) ? ( + + send({ type: "detour.active.deactivate-modal.deactivate" }) + } + onCancel={() => + send({ type: "detour.active.deactivate-modal.cancel" }) + } + routeName={routeName || "??"} + routeDescription={routeDescription || "??"} + routeOrigin={routeOrigin || "??"} + routeDirection={routeDirection || "??"} + /> + ) : null} + + ) + } else if (snapshot.matches({ "Detour Drawing": "Past" })) { + return ( + + ) + } else { + return <> + } + } + return ( <>
@@ -190,7 +407,7 @@ export const DiversionPage = ({ : "text-bg-light", ])} > - {"snapshot" in useDetourProps ? ( + {"snapshot" in useDetourProps && ( <> @@ -203,7 +420,7 @@ export const DiversionPage = ({ {useDetourProps.author} - ) : null} + )} @@ -216,204 +433,7 @@ export const DiversionPage = ({ : "text-bg-light", ])} > - {snapshot.matches({ "Detour Drawing": "Pick Route Pattern" }) ? ( - { - if (route) { - send({ - type: "detour.route-pattern.select-route", - route, - }) - } else { - send({ - type: "detour.route-pattern.delete-route", - }) - } - }} - onSelectRoutePattern={(routePattern) => { - routePattern && - send({ - type: "detour.route-pattern.select-pattern", - routePattern, - }) - }} - onConfirm={() => send({ type: "detour.route-pattern.done" })} - /> - ) : snapshot.matches({ "Detour Drawing": "Editing" }) && - routePattern ? ( - send({ type: "detour.route-pattern.open" })} - /> - ) : snapshot.matches({ "Detour Drawing": "Share Detour" }) && - editDetour ? ( - { - send({ type: "detour.share.open-activate-modal" }) - } - : undefined - } - > - {snapshot.matches({ - "Detour Drawing": { - "Share Detour": "Activating", - }, - }) ? ( - { - send({ type: "detour.share.activate-modal.cancel" }) - }} - onBack={ - snapshot.can({ type: "detour.share.activate-modal.back" }) - ? () => { - send({ type: "detour.share.activate-modal.back" }) - } - : undefined - } - onNext={ - snapshot.can({ type: "detour.share.activate-modal.next" }) - ? () => { - send({ type: "detour.share.activate-modal.next" }) - } - : undefined - } - onActivate={ - snapshot.can({ - type: "detour.share.activate-modal.activate", - }) - ? () => { - send({ type: "detour.share.activate-modal.activate" }) - } - : undefined - } - > - {snapshot.matches({ - "Detour Drawing": { - "Share Detour": { Activating: "Selecting Duration" }, - }, - }) ? ( - { - send({ - type: "detour.share.activate-modal.select-duration", - duration: selectedDuration, - }) - }} - selectedDuration={selectedDuration} - /> - ) : snapshot.matches({ - "Detour Drawing": { - "Share Detour": { Activating: "Selecting Reason" }, - }, - }) ? ( - { - send({ - type: "detour.share.activate-modal.select-reason", - reason: selectedReason, - }) - }} - selectedReason={selectedReason} - /> - ) : snapshot.matches({ - "Detour Drawing": { - "Share Detour": { Activating: "Confirming" }, - }, - }) ? ( - - ) : null} - - ) : null} - - ) : snapshot.matches({ "Detour Drawing": "Active" }) ? ( - { - send({ type: "detour.active.open-deactivate-modal" }) - } - : undefined - } - > - {snapshot.matches({ - "Detour Drawing": { Active: "Deactivating" }, - }) ? ( - - send({ type: "detour.active.deactivate-modal.deactivate" }) - } - onCancel={() => - send({ type: "detour.active.deactivate-modal.cancel" }) - } - routeName={routeName || "??"} - routeDescription={routeDescription || "??"} - routeOrigin={routeOrigin || "??"} - routeDirection={routeDirection || "??"} - /> - ) : null} - - ) : snapshot.matches({ "Detour Drawing": "Past" }) ? ( - - ) : null} + {detourPanel()}
{snapshot.matches({ "Detour Drawing": "Share Detour" }) && ( diff --git a/assets/src/components/notificationCard.tsx b/assets/src/components/notificationCard.tsx index ce4022f8a..a01a2a683 100644 --- a/assets/src/components/notificationCard.tsx +++ b/assets/src/components/notificationCard.tsx @@ -6,6 +6,7 @@ import { BlockWaiverReason, NotificationType, isBlockWaiverNotification, + DetourNotificationStatus, } from "../realtime" import { Route } from "../schedule" import { formattedTime } from "../util/dateTime" @@ -99,7 +100,17 @@ export const title = (notification: Notification) => { } case NotificationType.Detour: { - return "Detour - Active" + switch (notification.content.status) { + case DetourNotificationStatus.Activated: { + return "Detour - Active" + } + case DetourNotificationStatus.Deactivated: { + return "Detour - Closed" + } + } + // Typescript says this is unreachable, + // but eslint doesn't seem to get the memo + break } case NotificationType.BridgeMovement: { diff --git a/assets/src/models/notificationData.ts b/assets/src/models/notificationData.ts index 602a80c83..e496bf564 100644 --- a/assets/src/models/notificationData.ts +++ b/assets/src/models/notificationData.ts @@ -15,6 +15,7 @@ import { import { BlockWaiverNotification, BridgeNotification, + DetourNotificationStatus, Notification, NotificationContentTypes, NotificationType, @@ -68,6 +69,10 @@ export const BridgeNotificationData = union([ export const DetourNotificationData = type({ __struct__: literal(NotificationType.Detour), + status: enums([ + DetourNotificationStatus.Activated, + DetourNotificationStatus.Deactivated, + ]), detour_id: detourId, headsign: string(), route: string(), @@ -136,6 +141,7 @@ export const notificationFromData = ( case NotificationType.Detour: { content = { $type: NotificationType.Detour, + status: notificationData.content.status, detourId: notificationData.content.detour_id, direction: notificationData.content.direction, headsign: notificationData.content.headsign, diff --git a/assets/src/realtime.ts b/assets/src/realtime.ts index e8a817e44..afefafeab 100644 --- a/assets/src/realtime.ts +++ b/assets/src/realtime.ts @@ -92,8 +92,14 @@ export type BlockWaiverNotification = { endTime: Date | null } +export enum DetourNotificationStatus { + Activated = "activated", + Deactivated = "deactivated", +} + export type DetourNotification = { $type: NotificationType.Detour + status: DetourNotificationStatus detourId: DetourId headsign: string route: string diff --git a/assets/tests/components/__snapshots__/notificationBellIcon.test.tsx.snap b/assets/tests/components/__snapshots__/notificationBellIcon.test.tsx.snap index 658587ae6..645193cd5 100644 --- a/assets/tests/components/__snapshots__/notificationBellIcon.test.tsx.snap +++ b/assets/tests/components/__snapshots__/notificationBellIcon.test.tsx.snap @@ -1,5 +1,53 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP +exports[`NotificationBellIcon activated detour notification renders when there are new detour notifications and user is not part of DetoursList 1`] = ` + +
+ + + +
+ +`; + +exports[`NotificationBellIcon activated detour notification renders when there are new detour notifications and user is part of DetoursList group 1`] = ` + +
+ + + +
+ +`; + +exports[`NotificationBellIcon deactivated detour notification renders when there are new detour notifications and user is not part of DetoursList 1`] = ` + +
+ + + +
+ +`; + +exports[`NotificationBellIcon deactivated detour notification renders when there are new detour notifications and user is part of DetoursList group 1`] = ` + +
+ + + +
+ +`; + exports[`NotificationBellIcon renders when the drawer is closed and there are new notifications 1`] = ` `; - -exports[`NotificationBellIcon renders when there are new detour notifications and user is not part of DetoursList 1`] = ` - -
- - - -
- -`; - -exports[`NotificationBellIcon renders when there are new detour notifications and user is part of DetoursList group 1`] = ` - -
- - - -
- -`; diff --git a/assets/tests/components/__snapshots__/notificationCard.test.tsx.snap b/assets/tests/components/__snapshots__/notificationCard.test.tsx.snap index 0f3a9360c..8da2f49bd 100644 --- a/assets/tests/components/__snapshots__/notificationCard.test.tsx.snap +++ b/assets/tests/components/__snapshots__/notificationCard.test.tsx.snap @@ -1,6 +1,6 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`NotificationCard renders detour notification if user is in DetoursList group 1`] = ` +exports[`NotificationCard renders activated detour notification if user is in DetoursList group 1`] = `
- 2 + 4
- Headsign 2 + Headsign 4
From - Origin station 2 + Origin station 4 +
+
+ Outbound +
+
+
+
+ + + + + + +`; + +exports[`NotificationCard renders detour deactivated notification if user is in DetoursList group 1`] = ` + +
+
+