Created
August 5, 2021 11:23
-
-
Save DGollings/5305d5a540c95a3e52fdee2ada969c1d to your computer and use it in GitHub Desktop.
Generated by XState Viz: https://xstate.js.org/viz
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| // Available variables: | |
| // - Machine | |
| // - interpret | |
| // - assign | |
| // - send | |
| // - sendParent | |
| // - spawn | |
| // - raise | |
| // - actions | |
| // - XState (all XState exports) | |
| const fetchMachine = Machine( { | |
| id: "ParcelStates", | |
| context: { | |
| timestamp: 0, | |
| parcelID: "", | |
| isPickup: false, | |
| isPlanned: false, | |
| comment: "", | |
| updatingQuantity: false, | |
| verifiedQuantity: false, | |
| quantityExpected: 0, | |
| quantityActual: -1, | |
| errorMessage: "", | |
| }, | |
| initial: "created", | |
| states: { | |
| created: { | |
| always: [ | |
| { target: "pending_planning_pickup", cond: "isPickup" }, | |
| { target: "pending_planning_delivery", cond: "isDelivery" }, | |
| ], | |
| exit: ["updateTimestamp"], | |
| }, | |
| pending_planning_pickup: { | |
| meta: { | |
| label: "Pending planning pickup", | |
| error: false, | |
| completed: false, | |
| } , | |
| on: { | |
| PRIVATE_SAVE_COMMENT: { | |
| actions: ["setCommentField"], | |
| }, | |
| PRIVATE_IS_PLANNED_TRUE: { | |
| actions: [ | |
| assign({ | |
| isPlanned: context => (context.isPlanned = true), | |
| }), | |
| ], | |
| target: "planned_for_pickup", | |
| }, | |
| }, | |
| exit: ["updateTimestamp"], | |
| }, | |
| pending_planning_delivery: { | |
| meta: { | |
| label: "Pending planning delivery", | |
| error: false, | |
| completed: false, | |
| } , | |
| on: { | |
| PRIVATE_SAVE_COMMENT: { | |
| actions: ["setCommentField"], | |
| }, | |
| PRIVATE_IS_PLANNED_TRUE: { | |
| actions: "setPlanned", | |
| target: "planned_for_delivery", | |
| }, | |
| }, | |
| exit: ["updateTimestamp"], | |
| }, | |
| planned_for_delivery: { | |
| meta: { | |
| label: "Planned for delivery", | |
| error: false, | |
| completed: false, | |
| } , | |
| on: { | |
| LOAD_VEHICLE: "verify_quantity", | |
| PRIVATE_SAVE_COMMENT: { | |
| actions: ["setCommentField"], | |
| }, | |
| PRIVATE_IS_PLANNED_FALSE: { | |
| actions: "setNotPlanned", | |
| target: "pending_planning_delivery", | |
| }, | |
| }, | |
| exit: ["updateTimestamp"], | |
| }, | |
| planned_for_pickup: { | |
| meta: { | |
| label: "Planned for pickup", | |
| error: false, | |
| completed: false, | |
| } , | |
| on: { | |
| PICKING_UP: "picking_up", | |
| PRIVATE_IS_PLANNED_FALSE: { | |
| actions: "setNotPlanned", | |
| target: "pending_planning_delivery", | |
| }, | |
| }, | |
| exit: ["updateTimestamp"], | |
| }, | |
| picking_up: { | |
| meta: { | |
| label: "Picking up", | |
| error: false, | |
| completed: false, | |
| } , | |
| on: { | |
| VERIFY_QUANTITY: "verify_quantity", | |
| WRONG_ADDRESS: "wrong_address", | |
| ADDRESS_NOT_FOUND: "address_not_found", | |
| ERROR_OTHER: "exception_other", | |
| PRIVATE_SAVE_COMMENT: { | |
| actions: ["setCommentField"], | |
| }, | |
| PRIVATE_BACK: { | |
| target: "planned_for_pickup", | |
| }, | |
| PRIVATE_IS_PLANNED_FALSE: [ | |
| { | |
| actions: "setNotPlanned", | |
| target: "pending_planning_delivery", | |
| cond: "isDelivery", | |
| }, | |
| { | |
| actions: "setNotPlanned", | |
| target: "pending_planning_pickup", | |
| cond: "isPickup", | |
| }, | |
| ], | |
| }, | |
| exit: ["updateTimestamp"], | |
| }, | |
| verify_quantity: { | |
| meta: { | |
| label: "Verify quantity", | |
| overrideLabel: "Verified quantity", | |
| error: false, | |
| completed: false, | |
| } , | |
| entry: "updatingQuantityTrue", | |
| on: { | |
| VERIFIED_QUANTITY: [ | |
| { | |
| target: "ready_for_departure", | |
| actions: "setQuantity", | |
| cond: "checkQuantityValueDelivery", | |
| }, | |
| { | |
| target: "picked_up", | |
| actions: "setQuantity", | |
| cond: "checkQuantityValuePickup", | |
| }, | |
| ], | |
| PARCEL_TOO_LARGE: "parcel_too_large", | |
| PARCEL_TOO_HEAVY: "parcel_too_heavy", | |
| PARCEL_MISSING: "parcel_missing", | |
| PARCEL_DAMAGED_ALREADY: "parcel_damaged_already", | |
| PARCEL_COULD_NOT_BE_PICKED_UP: "parcel_could_not_be_picked_up", | |
| PRIVATE_UPDATE_QUANTITY: { | |
| actions: "setQuantity", | |
| cond: "checkQuantityValue", | |
| }, | |
| PRIVATE_BACK: [ | |
| { target: "ready_for_departure", cond: "isDelivery" }, | |
| { target: "picking_up", cond: "isPickup" }, | |
| ], | |
| PRIVATE_SAVE_COMMENT: { | |
| actions: ["setCommentField"], | |
| }, | |
| PRIVATE_IS_PLANNED_FALSE: [ | |
| { | |
| actions: "setNotPlanned", | |
| target: "pending_planning_delivery", | |
| cond: "isDelivery", | |
| }, | |
| { | |
| actions: "setNotPlanned", | |
| target: "pending_planning_pickup", | |
| cond: "isPickup", | |
| }, | |
| ], | |
| }, | |
| exit: [ | |
| "updatingQuantityFalse", | |
| "verifiedQuantityTrue", | |
| "updateTimestamp", | |
| ], | |
| }, | |
| picked_up: { | |
| meta: { | |
| label: "Picked up", | |
| error: false, | |
| completed: false, | |
| } , | |
| on: { | |
| PRIVATE_SAVE_COMMENT: { | |
| actions: ["setCommentField"], | |
| }, | |
| DELIVERED_TO_HUB: { | |
| target: "delivered_to_hub", | |
| }, | |
| IN_TRANSIT: { | |
| target: "in_transit", | |
| }, | |
| PRIVATE_BACK: { | |
| target: "verify_quantity", | |
| }, | |
| OVERRIDE_LABEL_ADJUST_QUANTITY: { | |
| target: "verify_quantity", | |
| }, | |
| PRIVATE_IS_PLANNED_FALSE: [ | |
| { | |
| actions: "setNotPlanned", | |
| target: "pending_planning_delivery", | |
| cond: "isDelivery", | |
| }, | |
| { | |
| actions: "setNotPlanned", | |
| target: "pending_planning_pickup", | |
| cond: "isPickup", | |
| }, | |
| ], | |
| }, | |
| exit: ["updateTimestamp"], | |
| }, | |
| delivered_to_hub: { | |
| meta: { | |
| label: "Delivered to hub", | |
| error: false, | |
| completed: true, | |
| } , | |
| on: { | |
| PRIVATE_SAVE_COMMENT: { | |
| actions: ["setCommentField"], | |
| }, | |
| PRIVATE_BACK: { | |
| target: ["picked_up"], | |
| }, | |
| }, | |
| exit: ["updateTimestamp"], | |
| }, | |
| ready_for_departure: { | |
| meta: { | |
| label: "Ready for departure", | |
| error: false, | |
| completed: false, | |
| } , | |
| on: { | |
| DEPART: { | |
| target: "in_transit", | |
| }, | |
| OVERRIDE_LABEL_ADJUST_QUANTITY: { | |
| target: "verify_quantity", | |
| }, | |
| PRIVATE_SAVE_COMMENT: { | |
| actions: ["setCommentField"], | |
| }, | |
| PRIVATE_IS_PLANNED_FALSE: [ | |
| { | |
| actions: "setNotPlanned", | |
| target: "pending_planning_delivery", | |
| cond: "isDelivery", | |
| }, | |
| { | |
| actions: "setNotPlanned", | |
| target: "pending_planning_pickup", | |
| cond: "isPickup", | |
| }, | |
| ], | |
| }, | |
| exit: ["updateTimestamp"], | |
| }, | |
| in_transit: { | |
| meta: { | |
| label: "In transit", | |
| error: false, | |
| completed: false, | |
| } , | |
| on: { | |
| AT_DELIVERY_LOCATION: "at_delivery_location", | |
| PARCEL_LOST: "parcel_lost", | |
| PARCEL_DAMAGED_IN_TRANSIT: "parcel_damaged_in_transit", | |
| WRONG_ADDRESS: "wrong_address", | |
| ADDRESS_NOT_FOUND: "address_not_found", | |
| ERROR_OTHER: "exception_other", | |
| PRIVATE_SAVE_COMMENT: { | |
| actions: ["setCommentField"], | |
| }, | |
| PRIVATE_BACK: [ | |
| { target: "ready_for_departure", cond: "isDelivery" }, | |
| { target: "picked_up", cond: "isPickup" }, | |
| ], | |
| PRIVATE_IS_PLANNED_FALSE: [ | |
| { | |
| actions: "setNotPlanned", | |
| target: "pending_planning_delivery", | |
| cond: "isDelivery", | |
| }, | |
| { | |
| actions: "setNotPlanned", | |
| target: "pending_planning_pickup", | |
| cond: "isPickup", | |
| }, | |
| ], | |
| }, | |
| exit: ["updateTimestamp"], | |
| }, | |
| at_delivery_location: { | |
| meta: { | |
| label: "At delivery location", | |
| error: false, | |
| completed: false, | |
| } , | |
| on: { | |
| ADDRESSEE: "delivered", | |
| NEIGHBOURS: "delivered_neighbours", | |
| COLLECTION_POINT: "delivered_collection_point", | |
| DELIVERY_REFUSED: "delivery_refused", | |
| ERROR_OTHER: "exception_other", | |
| PRIVATE_SAVE_COMMENT: { | |
| actions: ["setCommentField"], | |
| }, | |
| PRIVATE_BACK: { | |
| target: ["in_transit"], | |
| }, | |
| PRIVATE_IS_PLANNED_FALSE: [ | |
| { | |
| actions: "setNotPlanned", | |
| target: "pending_planning_delivery", | |
| cond: "isDelivery", | |
| }, | |
| { | |
| actions: "setNotPlanned", | |
| target: "pending_planning_pickup", | |
| cond: "isPickup", | |
| }, | |
| ], | |
| }, | |
| exit: ["updateTimestamp"], | |
| }, | |
| delivered: { | |
| meta: { | |
| label: "Delivered", | |
| error: false, | |
| completed: true, | |
| } , | |
| on: { | |
| PRIVATE_SAVE_COMMENT: { | |
| actions: ["setCommentField"], | |
| }, | |
| PRIVATE_BACK: { | |
| target: ["at_delivery_location"], | |
| }, | |
| }, | |
| exit: ["updateTimestamp"], | |
| }, | |
| delivered_neighbours: { | |
| meta: { | |
| label: "Delivered neighbours", | |
| error: false, | |
| completed: true, | |
| } , | |
| on: { | |
| PRIVATE_SAVE_COMMENT: { | |
| actions: ["setCommentField"], | |
| }, | |
| PRIVATE_BACK: { | |
| target: ["at_delivery_location"], | |
| }, | |
| }, | |
| exit: ["updateTimestamp"], | |
| }, | |
| delivered_collection_point: { | |
| meta: { | |
| label: "Delivered collection point", | |
| error: false, | |
| completed: true, | |
| } , | |
| on: { | |
| PRIVATE_SAVE_COMMENT: { | |
| actions: ["setCommentField"], | |
| }, | |
| PRIVATE_BACK: { | |
| target: ["at_delivery_location"], | |
| }, | |
| }, | |
| exit: ["updateTimestamp"], | |
| }, | |
| parcel_missing: { | |
| meta: { | |
| label: "Parcel missing", | |
| error: true, | |
| completed: true, | |
| } , | |
| on: { | |
| PRIVATE_SAVE_COMMENT: { | |
| actions: ["setCommentField"], | |
| }, | |
| PRIVATE_BACK: { | |
| target: ["verify_quantity"], | |
| }, | |
| }, | |
| exit: ["updateTimestamp"], | |
| }, | |
| parcel_damaged_already: { | |
| meta: { | |
| label: "Parcel damaged already", | |
| error: true, | |
| completed: true, | |
| } , | |
| on: { | |
| PRIVATE_SAVE_COMMENT: { | |
| actions: ["setCommentField"], | |
| }, | |
| PRIVATE_BACK: { | |
| target: ["verify_quantity"], | |
| }, | |
| }, | |
| exit: ["updateTimestamp"], | |
| }, | |
| parcel_could_not_be_picked_up: { | |
| meta: { | |
| label: "Parcel could not be picked up", | |
| error: true, | |
| completed: false, | |
| } , | |
| on: { | |
| PRIVATE_SAVE_COMMENT: { | |
| actions: ["setCommentField"], | |
| }, | |
| PRIVATE_BACK: { | |
| target: ["verify_quantity"], | |
| }, | |
| PRIVATE_IS_PLANNED_FALSE: [ | |
| { | |
| actions: "setNotPlanned", | |
| target: "pending_planning_delivery", | |
| cond: "isDelivery", | |
| }, | |
| { | |
| actions: "setNotPlanned", | |
| target: "pending_planning_pickup", | |
| cond: "isPickup", | |
| }, | |
| ], | |
| }, | |
| exit: ["updateTimestamp"], | |
| }, | |
| wrong_address: { | |
| meta: { | |
| label: "Wrong address", | |
| error: true, | |
| completed: false, | |
| } , | |
| on: { | |
| PRIVATE_SAVE_COMMENT: { | |
| actions: ["setCommentField"], | |
| }, | |
| PRIVATE_BACK: [ | |
| { target: "in_transit", cond: "isDelivery" }, | |
| { target: "picking_up", cond: "isPickup" }, | |
| ], | |
| PRIVATE_IS_PLANNED_FALSE: [ | |
| { | |
| actions: "setNotPlanned", | |
| target: "pending_planning_delivery", | |
| cond: "isDelivery", | |
| }, | |
| { | |
| actions: "setNotPlanned", | |
| target: "pending_planning_pickup", | |
| cond: "isPickup", | |
| }, | |
| ], | |
| }, | |
| exit: ["updateTimestamp"], | |
| }, | |
| address_not_found: { | |
| meta: { | |
| label: "Address not found", | |
| error: true, | |
| completed: false, | |
| } , | |
| on: { | |
| PRIVATE_SAVE_COMMENT: { | |
| actions: ["setCommentField"], | |
| }, | |
| PRIVATE_BACK: [ | |
| { target: "in_transit", cond: "isDelivery" }, | |
| { target: "picking_up", cond: "isPickup" }, | |
| ], | |
| PRIVATE_IS_PLANNED_FALSE: [ | |
| { | |
| actions: "setNotPlanned", | |
| target: "pending_planning_delivery", | |
| cond: "isDelivery", | |
| }, | |
| { | |
| actions: "setNotPlanned", | |
| target: "pending_planning_pickup", | |
| cond: "isPickup", | |
| }, | |
| ], | |
| }, | |
| exit: ["updateTimestamp"], | |
| }, | |
| parcel_lost: { | |
| meta: { | |
| label: "Parcel lost", | |
| error: true, | |
| completed: true, | |
| } , | |
| on: { | |
| PRIVATE_SAVE_COMMENT: { | |
| actions: ["setCommentField"], | |
| }, | |
| PRIVATE_BACK: { | |
| target: ["in_transit"], | |
| }, | |
| }, | |
| exit: ["updateTimestamp"], | |
| }, | |
| parcel_damaged_in_transit: { | |
| meta: { | |
| label: "Parcel damaged in transit", | |
| error: true, | |
| completed: true, | |
| } , | |
| on: { | |
| PRIVATE_SAVE_COMMENT: { | |
| actions: ["setCommentField"], | |
| }, | |
| PRIVATE_BACK: { | |
| target: ["in_transit"], | |
| }, | |
| }, | |
| exit: ["updateTimestamp"], | |
| }, | |
| parcel_too_large: { | |
| meta: { | |
| label: "Parcel too large", | |
| error: true, | |
| completed: false, | |
| } , | |
| on: { | |
| PRIVATE_SAVE_COMMENT: { | |
| actions: ["setCommentField"], | |
| }, | |
| OVERRIDE_LABEL_ADJUST_QUANTITY: { | |
| target: "verify_quantity", | |
| }, | |
| PRIVATE_BACK: { | |
| target: "verify_quantity", | |
| }, | |
| PRIVATE_IS_PLANNED_FALSE: [ | |
| { | |
| actions: "setNotPlanned", | |
| target: "pending_planning_delivery", | |
| cond: "isDelivery", | |
| }, | |
| { | |
| actions: "setNotPlanned", | |
| target: "pending_planning_pickup", | |
| cond: "isPickup", | |
| }, | |
| ], | |
| }, | |
| exit: ["updateTimestamp"], | |
| }, | |
| parcel_too_heavy: { | |
| meta: { | |
| label: "Parcel too heavy", | |
| error: true, | |
| completed: false, | |
| } , | |
| on: { | |
| PRIVATE_SAVE_COMMENT: { | |
| actions: ["setCommentField"], | |
| }, | |
| OVERRIDE_LABEL_ADJUST_QUANTITY: { | |
| target: "verify_quantity", | |
| }, | |
| PRIVATE_BACK: { | |
| target: "verify_quantity", | |
| }, | |
| PRIVATE_IS_PLANNED_FALSE: [ | |
| { | |
| actions: "setNotPlanned", | |
| target: "pending_planning_delivery", | |
| cond: "isDelivery", | |
| }, | |
| { | |
| actions: "setNotPlanned", | |
| target: "pending_planning_pickup", | |
| cond: "isPickup", | |
| }, | |
| ], | |
| }, | |
| exit: ["updateTimestamp"], | |
| }, | |
| delivery_refused: { | |
| meta: { | |
| label: "Delivery refused", | |
| error: true, | |
| completed: true, | |
| } , | |
| on: { | |
| PRIVATE_SAVE_COMMENT: { | |
| actions: ["setCommentField"], | |
| }, | |
| PRIVATE_BACK: { | |
| target: ["at_delivery_location"], | |
| }, | |
| }, | |
| exit: ["updateTimestamp"], | |
| }, | |
| delivery_failed: { | |
| meta: { | |
| label: "Delivery failed", | |
| error: true, | |
| completed: false, | |
| } , | |
| on: { | |
| PRIVATE_SAVE_COMMENT: { | |
| actions: ["setCommentField"], | |
| }, | |
| PRIVATE_BACK: { | |
| target: "verify_quantity", | |
| }, | |
| PRIVATE_IS_PLANNED_FALSE: [ | |
| { | |
| actions: "setNotPlanned", | |
| target: "pending_planning_delivery", | |
| cond: "isDelivery", | |
| }, | |
| { | |
| actions: "setNotPlanned", | |
| target: "pending_planning_pickup", | |
| cond: "isPickup", | |
| }, | |
| ], | |
| }, | |
| exit: ["updateTimestamp"], | |
| }, | |
| exception_other: { | |
| meta: { | |
| label: "Unknown error", | |
| error: true, | |
| completed: false, | |
| } , | |
| on: { | |
| PRIVATE_SAVE_COMMENT: { | |
| actions: ["setCommentField"], | |
| }, | |
| PRIVATE_BACK: [ | |
| { target: "in_transit", cond: "isDelivery" }, | |
| { target: "picking_up", cond: "isPickup" }, | |
| ], | |
| PRIVATE_IS_PLANNED_FALSE: [ | |
| { | |
| actions: "setNotPlanned", | |
| target: "pending_planning_delivery", | |
| cond: "isDelivery", | |
| }, | |
| { | |
| actions: "setNotPlanned", | |
| target: "pending_planning_pickup", | |
| cond: "isPickup", | |
| }, | |
| ], | |
| }, | |
| exit: ["updateTimestamp"], | |
| }, | |
| restart: { | |
| always: [ | |
| { | |
| actions: ["resetContext"], | |
| target: "created", | |
| }, | |
| ], | |
| exit: ["updateTimestamp"], | |
| }, | |
| }, | |
| on: { | |
| FORCE_PICKED_UP: { | |
| target: "picked_up", | |
| }, | |
| FORCE_PARCEL_COULD_NOT_BE_PICKED_UP: { | |
| target: "parcel_could_not_be_picked_up", | |
| }, | |
| FORCE_IN_TRANSIT: { | |
| target: "in_transit", | |
| }, | |
| FORCE_ADDRESSEE: { | |
| target: "delivered", | |
| }, | |
| FORCE_NEIGHBOURS: { | |
| target: "delivered_neighbours", | |
| }, | |
| FORCE_DELIVERY_REFUSED: { | |
| target: "delivery_refused", | |
| }, | |
| FORCE_DELIVERY_FAILED: { | |
| target: "delivery_failed", | |
| }, | |
| }, | |
| }, | |
| { | |
| guards: { | |
| isPickup: context => context.isPickup === true, | |
| isDelivery: context => context.isPickup === false, | |
| isPlannedForDelivery: context => | |
| context.isPlanned === true && context.isPickup === false, | |
| isPlannedForPickup: context => | |
| context.isPlanned === true && context.isPickup === true, | |
| checkQuantityValue: (context, event) => { | |
| event = event ; | |
| switch (event.input_type) { | |
| case "text": | |
| if (!event.value) { | |
| console.log("EVENT VALUE", event.value); | |
| context.errorMessage = "value is not a number"; | |
| return false; | |
| } | |
| break; | |
| case "number": { | |
| const value = Number(event.value); | |
| if (isNaN(value)) { | |
| context.errorMessage = "value is not a number"; | |
| return false; | |
| } | |
| break; | |
| } | |
| } | |
| context.errorMessage = ""; | |
| return true; | |
| }, | |
| // cant figure out how to 'chain' guards (https://github.com/statelyai/xstate/issues/414) so just copying and pasting code for now | |
| checkQuantityValuePickup: (context, event) => { | |
| if (!context.isPickup) { | |
| return false; | |
| } | |
| event = event ; | |
| switch (event.input_type) { | |
| case "text": | |
| if (!event.value) { | |
| console.log("EVENT VALUE", event.value); | |
| context.errorMessage = "value is not a number"; | |
| return false; | |
| } | |
| break; | |
| case "number": { | |
| const value = Number(event.value); | |
| if (isNaN(value)) { | |
| context.errorMessage = "value is not a number"; | |
| return false; | |
| } | |
| break; | |
| } | |
| } | |
| context.errorMessage = ""; | |
| return true; | |
| }, | |
| checkQuantityValueDelivery: (context, event) => { | |
| if (context.isPickup) { | |
| return false; | |
| } | |
| event = event ; | |
| switch (event.input_type) { | |
| case "text": | |
| if (!event.value) { | |
| console.log("EVENT VALUE", event.value); | |
| context.errorMessage = "value is not a number"; | |
| return false; | |
| } | |
| break; | |
| case "number": { | |
| const value = Number(event.value); | |
| if (isNaN(value)) { | |
| context.errorMessage = "value is not a number"; | |
| return false; | |
| } | |
| break; | |
| } | |
| } | |
| context.errorMessage = ""; | |
| return true; | |
| }, | |
| // /cant figure out chaining guards | |
| }, | |
| actions: { | |
| updateTimestamp: context => { | |
| context.timestamp = Date.now(); | |
| }, | |
| resetContext: context => { | |
| context.comment = ""; | |
| context.updatingQuantity = false; | |
| context.verifiedQuantity = false; | |
| }, | |
| setCommentField: (context, event) => { | |
| context.comment = event.comment ? event.comment : ""; | |
| }, | |
| verifiedQuantityTrue: context => (context.verifiedQuantity = true), | |
| updatingQuantityTrue: context => (context.updatingQuantity = true), | |
| updatingQuantityFalse: context => (context.updatingQuantity = false), | |
| setQuantity: (context, event) => { | |
| event = event ; | |
| context.quantityActual = Number(event.value); | |
| }, | |
| setPlanned: context => (context.isPlanned = true), | |
| setNotPlanned: context => (context.isPlanned = false), | |
| }, | |
| }); | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment