Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor: api v2 schedules #14870

Conversation

supalarry
Copy link
Contributor

@supalarry supalarry commented May 3, 2024

Problem

v2 /schedules endpoints are not intuitive to use e.g. when creating a schedule for Sunday user must pass "0" to represent Sunday, and the response is in shape that atom expects which is messy and hard to understand and contains UI specific data like "isLastSchedule":

{
    "status": "success",
    "data": {
        "id": 139,
        "name": "Default",
        "isManaged": false,
        "workingHours": [
            {
                "days": [
                    1,
                    2,
                    3,
                    4,
                    5
                ],
                "startTime": 540,
                "endTime": 1020,
                "userId": 256
            }
        ],
        "schedule": [
            {
                "id": 437,
                "userId": 256,
                "eventTypeId": null,
                "days": [
                    1,
                    2,
                    3,
                    4,
                    5
                ],
                "startTime": "1970-01-01T09:00:00.000Z",
                "endTime": "1970-01-01T17:00:00.000Z",
                "date": null,
                "scheduleId": 139
            }
        ],
        "availability": [
            [],
            [
                {
                    "start": "2024-05-14T09:00:00.000Z",
                    "end": "2024-05-14T17:00:00.000Z"
                }
            ],
            [
                {
                    "start": "2024-05-14T09:00:00.000Z",
                    "end": "2024-05-14T17:00:00.000Z"
                }
            ],
            [
                {
                    "start": "2024-05-14T09:00:00.000Z",
                    "end": "2024-05-14T17:00:00.000Z"
                }
            ],
            [
                {
                    "start": "2024-05-14T09:00:00.000Z",
                    "end": "2024-05-14T17:00:00.000Z"
                }
            ],
            [
                {
                    "start": "2024-05-14T09:00:00.000Z",
                    "end": "2024-05-14T17:00:00.000Z"
                }
            ],
            []
        ],
        "timeZone": "America/Cancun",
        "dateOverrides": [],
        "isDefault": true,
        "isLastSchedule": true,
        "readOnly": false
    }
}

Solution

Simplify create inputs and responses to deliver good developer experience. The API does not distinguish whether the request came from an atom or not - the response is always in the same shape.

On the frontend hooks are used to fetch api data and then transformers to transform api data into atom compatible structure, and then when updating a schedule to transform atom schedule into api compatible structure.

Endpoints

Whether creating, fetching or updating schedule the response has the same shape.

POST /schedules

Screenshot 2024-05-14 at 12 21 46

GET /schedules/default & GET /schedules/:id

Screenshot 2024-05-14 at 12 23 35

UPDATE /schedules/id

Screenshot 2024-05-14 at 12 25 24

Implementation

API inputs and outputs

For atom hooks and api to be on the same page, all of schedules inputs e.g. CreateScheduleInput and outputs e.g. GetScheduleOutput have been moved to platform-types packages/platform/types/schedules in order to centralize inputs and outputs of API.

Data transformation

We want to re-use existing functions while providing a cleaner API interface. That means that:

  1. API takes in data defined by the input types, then uses input-schedules.service.ts function "transformInputCreateSchedule" that transforms availability days from names e.g "Sunday" to numeric values e.g. 0 aka to data format that is compatible with internal functions. Same is done for overrides. API then passes transformed values to prisma that performs create, read or update operation.
  2. We no longer use trpc handlers to update schedule - we completely rely on the schedules.repository.ts for performing prisma operations.
  3. When prisma operation is performed and it returns data, then output-schedules.service.ts service is used to transform internal data into the "clean" way for response.

If you check "transformInputCreateSchedule" in point 1., then you see that to transform avaiability and overrides it uses functions imported from platform libraries. They reside there not in the class, because they are also used client side - when API data is returned by hook, that data also needs to be converted to data structure compatible with our internal functions. That brings us to client:

  1. Hooks return data in the same "clean" structure as API.
  2. transformApiScheduleForAtom.ts is used to transform API response into structure compatible with atom.
  3. transformAtomScheduleForApi.ts is used to transform atom schedule into API structure, for example, when updating schedule.

New hook - useSchedules

useSchedules.ts has been added to be used in PlatformAvailabilitySettingsWrapper.tsx - atom needs to know if its users last schedule do disable deleting it. We use useSchedules to fetch user schedules, and then pass their count to transformApiScheduleForAtom.

Tests

  1. API e2e tests - schedules.controller.e2e-spec.ts
  2. Setup jest for atoms (atoms/jest.config.json) and then add tests - Atom tests testing the transformation of API data into atom compatible data (transformApiScheduleForAtom.spec.ts) and atom data into API compatible data (transformAtomScheduleForApi.spec.ts)
  3. Built atoms and then started v2 api, and in examples app confirm that fetched default schedule is displayed correctly and that its possible to update it too.

Docs

Swagger docs been updated with examples

schedules refactor: before & after

Creating schedule

1. Before

  1. availabilities days are numbers instead of names
  2. start and end times must be dates even though api only cares about hours and minutes
  3. no way to pass overrides - dates and times when availabilities change

Request body:

{
  "name": "working hours",
  "timeZone": "Europe/Rome",
  "availabilities": [
    {
      "days": [
        1,
        2,
        3,
        4,
        5
      ],
      "startTime": "1970-01-01T09:00:00.289Z",
      "endTime": "1970-01-01T17:00:00.289Z"
    }
  ],
  "isDefault": true
}

Response:

{
  "status": "success",
  "data": {
    "id": 256,
    "name": "working hours",
    "isManaged": false,
    "workingHours": [
      {
        "days": [
          1,
          2,
          3,
          4,
          5
        ],
        "startTime": 540,
        "endTime": 1020,
        "userId": 480
      }
    ],
    "schedule": [
      {
        "id": 462,
        "userId": 480,
        "eventTypeId": null,
        "days": [
          1,
          2,
          3,
          4,
          5
        ],
        "startTime": "1970-01-01T09:00:00.000Z",
        "endTime": "1970-01-01T17:00:00.000Z",
        "date": null,
        "scheduleId": 256
      }
    ],
    "availability": [
      [],
      [
        {
          "start": "2024-05-15T09:00:00.000Z",
          "end": "2024-05-15T17:00:00.000Z"
        }
      ],
      [
        {
          "start": "2024-05-15T09:00:00.000Z",
          "end": "2024-05-15T17:00:00.000Z"
        }
      ],
      [
        {
          "start": "2024-05-15T09:00:00.000Z",
          "end": "2024-05-15T17:00:00.000Z"
        }
      ],
      [
        {
          "start": "2024-05-15T09:00:00.000Z",
          "end": "2024-05-15T17:00:00.000Z"
        }
      ],
      [
        {
          "start": "2024-05-15T09:00:00.000Z",
          "end": "2024-05-15T17:00:00.000Z"
        }
      ],
      []
    ],
    "timeZone": "Europe/Rome",
    "dateOverrides": [],
    "isDefault": true,
    "isLastSchedule": true,
    "readOnly": false
  }
}

2. After

  1. availabilities body key has been renamed to availability
  2. Days are words e.g. Sunday instead of number e.g. 0
  3. Overrides can be passed.
Screenshot 2024-05-14 at 12 21 46

Updating schedule

1. Before

  1. To update availabilities they had to be passed under body.schedule in data format different than when creating a schedule.
  2. The response did not include schedule availabilities

Request body:

{
  "schedule": [
      [],
      [{ "start": "2022-01-01T10:00:00.000Z", "end": "2022-01-02T16:00:00.000Z" }],
      [],
      [],
      [],
      [],
      []
    ]
}

Response:

{
  "status": "success",
  "data": {
    "schedule": {
      "userId": 480,
      "name": "working hours",
      "id": 256
    },
    "isDefault": true
  }
}

2. After

  1. Shape is exactly the same as when creating schedule
  2. Response includes availabilities and is in same shape as when creating a schedule
Screenshot 2024-05-14 at 12 25 24

Fetching schedule

1. Before

Response is in same shape as when creating.

2. After

Response is in same shape as wen creating and updating.
Screenshot 2024-05-14 at 12 23 35

Copy link

linear bot commented May 3, 2024

Copy link
Contributor

github-actions bot commented May 3, 2024

Thank you for following the naming conventions! 🙏 Feel free to join our discord and post your PR link.

@keithwillcode keithwillcode added core area: core, team members only platform Anything related to our platform plan labels May 3, 2024
Copy link

vercel bot commented May 3, 2024

The latest updates on your projects. Learn more about Vercel for Git ↗︎

3 Ignored Deployments
Name Status Preview Comments Updated (UTC)
ai ⬜️ Ignored (Inspect) Visit Preview May 16, 2024 8:08am
cal ⬜️ Ignored (Inspect) Visit Preview May 16, 2024 8:08am
calcom-web-canary ⬜️ Ignored (Inspect) Visit Preview May 16, 2024 8:08am

Copy link
Contributor

github-actions bot commented May 3, 2024

📦 Next.js Bundle Analysis for @calcom/web

This analysis was generated by the Next.js Bundle Analysis action. 🤖

This PR introduced no changes to the JavaScript bundle! 🙌

Copy link

deploysentinel bot commented May 3, 2024

Current Playwright Test Results Summary

✅ 321 Passing - ⚠️ 24 Flaky

Run may still be in progress, this comment will be updated as current testing workflow or job completes...

(Last updated on 05/16/2024 08:24:35am UTC)

Run Details

Running Workflow PR Update on Github Actions

Commit: 2251f3c

Started: 05/16/2024 08:20:02am UTC

⚠️ Flakes

📄   apps/web/playwright/integrations-stripe.e2e.ts • 3 Flakes

Top 1 Common Error Messages

null

3 Test Cases Affected

Test Case Results

Test Case Last 7 days Failures Last 7 days Flakes
Stripe integration Can book a paid booking
Retry 1Initial Attempt
3.43% (7) 7 / 204 runs
failed over last 7 days
15.20% (31) 31 / 204 runs
flaked over last 7 days
Stripe integration Paid booking should be able to be rescheduled
Retry 1Initial Attempt
3.47% (7) 7 / 202 runs
failed over last 7 days
16.34% (33) 33 / 202 runs
flaked over last 7 days
Stripe integration Paid booking should be able to be cancelled
Retry 1Initial Attempt
2.49% (5) 5 / 201 runs
failed over last 7 days
11.94% (24) 24 / 201 runs
flaked over last 7 days

📄   apps/web/playwright/event-types.e2e.ts • 2 Flakes

Top 1 Common Error Messages

null

2 Test Cases Affected

Test Case Results

Test Case Last 7 days Failures Last 7 days Flakes
Event Types tests -- future user can add multiple organizer address
Retry 1Initial Attempt
1.81% (4) 4 / 221 runs
failed over last 7 days
22.62% (50) 50 / 221 runs
flaked over last 7 days
Event Types tests -- future user Different Locations Tests can select 'display on booking page' option when multiple organizer input type are present
Retry 1Initial Attempt
0% (0) 0 / 219 runs
failed over last 7 days
10.96% (24) 24 / 219 runs
flaked over last 7 days

📄   packages/embeds/embed-core/playwright/tests/action-based.e2e.ts • 9 Flakes

Top 1 Common Error Messages

null

9 Test Cases Affected

Test Case Results

Test Case Last 7 days Failures Last 7 days Flakes
Popup Tests should open embed iframe on click - Configured with light theme
Retry 1Initial Attempt
-0.93% (-2) -2 / 214 runs
failed over last 7 days
61.68% (132) 132 / 214 runs
flaked over last 7 days
Popup Tests should be able to reschedule
Retry 1Initial Attempt
-157.14% (-132) -132 / 84 runs
failed over last 7 days
157.14% (132) 132 / 84 runs
flaked over last 7 days
Popup Tests should open Routing Forms embed on click
Retry 1Initial Attempt
-154.76% (-130) -130 / 84 runs
failed over last 7 days
154.76% (130) 130 / 84 runs
flaked over last 7 days
Popup Tests Floating Button Popup Pro User - Configured in App with default setting of system theme should open embed iframe according to system theme when no theme is configured through Embed API
Retry 1Initial Attempt
-148.81% (-125) -125 / 84 runs
failed over last 7 days
151.19% (127) 127 / 84 runs
flaked over last 7 days
Popup Tests Floating Button Popup Pro User - Configured in App with default setting of system theme should open embed iframe according to system theme when configured with 'auto' theme using Embed API
Retry 1Initial Attempt
-154.88% (-127) -127 / 82 runs
failed over last 7 days
154.88% (127) 127 / 82 runs
flaked over last 7 days
Popup Tests Floating Button Popup Pro User - Configured in App with default setting of system theme should open embed iframe(Booker Profile Page) with dark theme when configured with dark theme using Embed API
Retry 1Initial Attempt
-154.88% (-127) -127 / 82 runs
failed over last 7 days
154.88% (127) 127 / 82 runs
flaked over last 7 days
Popup Tests Floating Button Popup Pro User - Configured in App with default setting of system theme should open embed iframe(Event Booking Page) with dark theme when configured with dark theme using Embed API
Retry 1Initial Attempt
-154.88% (-127) -127 / 82 runs
failed over last 7 days
154.88% (127) 127 / 82 runs
flaked over last 7 days
Popup Tests prendered embed should be loaded and apply the config given to it
Retry 1Initial Attempt
-154.88% (-127) -127 / 82 runs
failed over last 7 days
154.88% (127) 127 / 82 runs
flaked over last 7 days
Popup Tests should open on clicking child element
Retry 1Initial Attempt
-151.85% (-123) -123 / 81 runs
failed over last 7 days
151.85% (123) 123 / 81 runs
flaked over last 7 days

📄   apps/web/playwright/profile.e2e.ts • 2 Flakes

Top 1 Common Error Messages

null

2 Test Cases Affected

Test Case Results

Test Case Last 7 days Failures Last 7 days Flakes
Update Profile Can update a users email (verification enabled)
Retry 1Initial Attempt
39.92% (97) 97 / 243 runs
failed over last 7 days
31.28% (76) 76 / 243 runs
flaked over last 7 days
Update Profile Can verify the newly added secondary email
Retry 1Initial Attempt
2.45% (6) 6 / 245 runs
failed over last 7 days
21.63% (53) 53 / 245 runs
flaked over last 7 days

📄   packages/app-store/routing-forms/playwright/tests/basic.e2e.ts • 1 Flake

Test Case Results

Test Case Last 7 days Failures Last 7 days Flakes
Routing Forms Seeded Routing Form Test preview should return correct route
Retry 1Initial Attempt
0.94% (2) 2 / 212 runs
failed over last 7 days
32.55% (69) 69 / 212 runs
flaked over last 7 days

📄   apps/web/playwright/teams.e2e.ts • 1 Flake

Test Case Results

Test Case Last 7 days Failures Last 7 days Flakes
Teams - NonOrg -- legacy Can create a booking for Round Robin EventType
Retry 1Initial Attempt
6.31% (14) 14 / 222 runs
failed over last 7 days
29.73% (66) 66 / 222 runs
flaked over last 7 days

📄   packages/embeds/embed-core/playwright/tests/namespacing.e2e.ts • 4 Flakes

Top 1 Common Error Messages

null

4 Test Cases Affected

Test Case Results

Test Case Last 7 days Failures Last 7 days Flakes
Namespacing Inline Embed Double install Embed Snippet with inline embed using a namespace
Retry 1Initial Attempt
0% (0) 0 / 212 runs
failed over last 7 days
58.49% (124) 124 / 212 runs
flaked over last 7 days
Namespacing Inline Embed Add inline embed using a namespace without reload
Retry 1Initial Attempt
0.47% (1) 1 / 212 run
failed over last 7 days
58.96% (125) 125 / 212 runs
flaked over last 7 days
Namespacing Different namespaces can have different init configs
Retry 1Initial Attempt
0% (0) 0 / 210 runs
failed over last 7 days
58.57% (123) 123 / 210 runs
flaked over last 7 days
Namespacing Inline Embed Double install Embed Snippet with inline embed without a namespace(i.e. default namespace)
Retry 1Initial Attempt
0% (0) 0 / 212 runs
failed over last 7 days
62.26% (132) 132 / 212 runs
flaked over last 7 days

📄   apps/web/playwright/login.e2e.ts • 1 Flake

Test Case Results

Test Case Last 7 days Failures Last 7 days Flakes
user can login & logout succesfully -- future login flow user & logout using dashboard
Retry 1Initial Attempt
4.59% (10) 10 / 218 runs
failed over last 7 days
31.65% (69) 69 / 218 runs
flaked over last 7 days

📄   apps/web/playwright/ab-tests-redirect.e2e.ts • 1 Flake

Test Case Results

Test Case Last 7 days Failures Last 7 days Flakes
apps/ A/B tests -- future should render the /getting-started
Retry 1Initial Attempt
0.46% (1) 1 / 217 run
failed over last 7 days
0.92% (2) 2 / 217 runs
flaked over last 7 days

View Detailed Build Results


@graphite-app graphite-app bot requested review from a team May 14, 2024 15:40
@dosubot dosubot bot added api area: API, enterprise API, access token, OAuth 💻 refactor labels May 14, 2024
@supalarry supalarry removed the request for review from a team May 14, 2024 15:42
Copy link

graphite-app bot commented May 14, 2024

Graphite Automations

"Add foundation team as reviewer" took an action on this PR • (05/14/24)

1 reviewer was added to this PR based on Keith Williams's automation.

"Add platform team as reviewer" took an action on this PR • (05/14/24)

1 reviewer was added to this PR based on Keith Williams's automation.

"Add consumer team as reviewer" took an action on this PR • (05/14/24)

1 reviewer was added to this PR based on Keith Williams's automation.

@supalarry
Copy link
Contributor Author

schedules refactor: before & after

Creating schedule

1. Before

  1. availabilities days are numbers instead of names
  2. start and end times must be dates even though api only cares about hours and minutes
  3. no way to pass overrides - dates and times when availabilities change

Request body:

{
  "name": "working hours",
  "timeZone": "Europe/Rome",
  "availabilities": [
    {
      "days": [
        1,
        2,
        3,
        4,
        5
      ],
      "startTime": "1970-01-01T09:00:00.289Z",
      "endTime": "1970-01-01T17:00:00.289Z"
    }
  ],
  "isDefault": true
}

Response:

{
  "status": "success",
  "data": {
    "id": 256,
    "name": "working hours",
    "isManaged": false,
    "workingHours": [
      {
        "days": [
          1,
          2,
          3,
          4,
          5
        ],
        "startTime": 540,
        "endTime": 1020,
        "userId": 480
      }
    ],
    "schedule": [
      {
        "id": 462,
        "userId": 480,
        "eventTypeId": null,
        "days": [
          1,
          2,
          3,
          4,
          5
        ],
        "startTime": "1970-01-01T09:00:00.000Z",
        "endTime": "1970-01-01T17:00:00.000Z",
        "date": null,
        "scheduleId": 256
      }
    ],
    "availability": [
      [],
      [
        {
          "start": "2024-05-15T09:00:00.000Z",
          "end": "2024-05-15T17:00:00.000Z"
        }
      ],
      [
        {
          "start": "2024-05-15T09:00:00.000Z",
          "end": "2024-05-15T17:00:00.000Z"
        }
      ],
      [
        {
          "start": "2024-05-15T09:00:00.000Z",
          "end": "2024-05-15T17:00:00.000Z"
        }
      ],
      [
        {
          "start": "2024-05-15T09:00:00.000Z",
          "end": "2024-05-15T17:00:00.000Z"
        }
      ],
      [
        {
          "start": "2024-05-15T09:00:00.000Z",
          "end": "2024-05-15T17:00:00.000Z"
        }
      ],
      []
    ],
    "timeZone": "Europe/Rome",
    "dateOverrides": [],
    "isDefault": true,
    "isLastSchedule": true,
    "readOnly": false
  }
}

2. After

  1. availabilities body key has been renamed to availability
  2. Days are words e.g. Sunday instead of number e.g. 0
  3. Overrides can be passed.
Screenshot 2024-05-14 at 12 21 46

Updating schedule

1. Before

  1. To update availabilities they had to be passed under body.schedule in data format different than when creating a schedule.
  2. The response did not include schedule availabilities

Request body:

{
  "schedule": [
      [],
      [{ "start": "2022-01-01T10:00:00.000Z", "end": "2022-01-02T16:00:00.000Z" }],
      [],
      [],
      [],
      [],
      []
    ]
}

Response:

{
  "status": "success",
  "data": {
    "schedule": {
      "userId": 480,
      "name": "working hours",
      "id": 256
    },
    "isDefault": true
  }
}

2. After

  1. Shape is exactly the same as when creating schedule
  2. Response includes availabilities and is in same shape as when creating a schedule
Screenshot 2024-05-14 at 12 25 24

Fetching schedule

1. Before

Response is in same shape as when creating.

2. After

Response is in same shape as wen creating and updating.
Screenshot 2024-05-14 at 12 23 35

@ThyMinimalDev ThyMinimalDev merged commit c25c835 into main May 16, 2024
41 checks passed
@ThyMinimalDev ThyMinimalDev deleted the lauris/cal-3580-apiv2-schedules-endpoints-should-be-client-agnostic branch May 16, 2024 12:20
supalarry added a commit that referenced this pull request May 20, 2024
supalarry added a commit that referenced this pull request May 20, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
api area: API, enterprise API, access token, OAuth core area: core, team members only platform Anything related to our platform plan 💻 refactor
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants