-
Notifications
You must be signed in to change notification settings - Fork 375
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: Handle breaking changes in NgRx actions #19013
Conversation
@@ -196,12 +223,20 @@ export class LoadB2BUsers extends StateUtils.EntityLoadAction { | |||
} | |||
|
|||
export class LoadB2BUsersFail extends StateUtils.EntityFailAction { | |||
error: ErrorActionType = this.payload.error; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[NOTE]
such lines are not needed in classes that extend from StateUtils.EntityFailAction
. It's already done inside StateUtils.EntityFailAction
super class
…ase: constructor param `payload: any`, but looking up a property `payload.error` inside
…ce over deprecated `any`) and change `null | undefined` to `any`
…onstructor" to "for distinguishing deprecated constructor
…." + fix the updated import paths of error-action.ts
interface CreateCartFailPayload extends CreateCartPayload { | ||
error: ErrorActionType; | ||
} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[NOTE]
Removed this interface in favor of inline info about error
:
CreateCartPayload & { error: any }
CreateCartPayload & { error:
ActionErrorProperty }`
@@ -79,13 +89,28 @@ interface LoadWishListFailPayload { | |||
* temporary cart used to track loading/error state or to normal wish list entity. | |||
*/ | |||
cartId: string; | |||
error: ErrorActionType; | |||
error: ActionErrorProperty; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[NOTE]
I didn't split the LoadWishListFailPayload
, to keep benefiting from its useful existing JSDocs
interface TypeOf_SetStoreDetailsFailure { | ||
(props: { error: ActionErrorProperty }): { | ||
error: ActionErrorProperty; | ||
} & TypedAction<typeof STORE_DETAILS_FAIL>; | ||
|
||
/** | ||
* @deprecated Use the `error` parameter with a non-null, non-undefined value. | ||
* Support for `null` or `undefined` will be removed in future versions, | ||
* along with the feature toggle `ssrStrictErrorHandlingForHttpAndNgrx`. | ||
*/ | ||
(props: { error: any }): { error: any } & TypedAction< | ||
typeof STORE_DETAILS_FAIL | ||
>; | ||
} | ||
|
||
// Note for future: when removing the old deprecated code, let's do the following: | ||
// - remove `TypeOf_SetStoreDetailsFailure` | ||
// - replace the implementation of `SetStoreDetailsFailure` with the following: | ||
// ``` | ||
// export const SetStoreDetailsFailure = createAction( | ||
// STORE_DETAILS_FAIL, | ||
// props<{ error: ActionErrorProperty }>() | ||
// ); | ||
// ``` | ||
|
||
export const SetStoreDetailsFailure: TypeOf_SetStoreDetailsFailure = | ||
createAction(STORE_DETAILS_FAIL, props<{ error: any }>()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[NOTE]
It's a single exceptional case where NgRx actions are not classes (with constructors), but factories created with the createAction
ngrx util.
This exceptional case required an exceptional solution.
…| undefinied instead of any in deprecated constructor
…+ reorder consctructor delcarations
interface LoadUserTokenFailurePayload { | ||
error: ErrorActionType; | ||
initialActionPayload: LoadUserTokenPayload; | ||
} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[NOTE]
Removed in favor of defining initialActionPayload: LoadUserTokenPayload
separately, and error: <specific_type>
separately
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[NOTE]
Moved within core lib. From /model/error-action.ts
to error-handling/effects-error-handler/error-action.ts
/** | ||
* @deprecated Please use the `error` parameter with a non-null, non-undefined value. | ||
* Support for `null` or `undefined` will be removed in future versions, | ||
* along with the feature toggle `ssrStrictErrorHandlingForHttpAndNgrx`. | ||
*/ | ||
constructor( | ||
entityType: string, | ||
id: EntityId, | ||
// eslint-disable-next-line @typescript-eslint/unified-signatures -- needed to deprecate only the old constructor | ||
error: any | ||
); | ||
/** | ||
* @deprecated Please use the `error` parameter with a non-null, non-undefined value. | ||
* The `error` parameter will become required in future versions, | ||
* along with removing the feature toggle `ssrStrictErrorHandlingForHttpAndNgrx`. | ||
*/ | ||
constructor(entityType: string, id: EntityId); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[NOTE]
In this exceptional case I added 2 (but not 1) deprecated constructors, because:
error
can beany
- AND MOREOVER
error
can be not passed at all (aserror?
argument is optional)
constructor(error: ActionErrorProperty); | ||
/** | ||
* @deprecated Please use the `error` parameter with a non-null, non-undefined value. | ||
* Support for `null` or `undefined` will be removed in future versions, | ||
* along with the feature toggle `ssrStrictErrorHandlingForHttpAndNgrx`. | ||
*/ | ||
constructor( | ||
// eslint-disable-next-line @typescript-eslint/unified-signatures -- for distinguishing deprecated constructor | ||
error: any | ||
); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[NOTE]
Some ngrx actions were not refactored in the past PR https://github.com/SAP/spartacus/pull/17657/files from any
to ErrorActionType
.
So in this PR I'm refactoring them directly from any
to ActionErrorProperty
.
Note: I expect in the upcoming weeks in develop
there might be more new ngrx actions with error: any
that we should refactor analogically, before releasing our "Ssr Error Handling" epic
Have you considered creating a separate ticket to track deprecations which eventually will be removed and attaching its ID to all related places? |
STORE_DETAILS_FAIL, | ||
props<{ error: ErrorActionType }>() | ||
); | ||
interface TypeOf_SetStoreDetailsFailure { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
query:
What about simply calling it SetStoreDetailsFailure
? Using TypeOf_
feels strange to me - not sure whether it's a common approach and I'm not familiar with it, but I'm happy to learn. Besides, it is possible to have the same name for const and interface/type. Interface won't be exported outside this file, so I guess it's not a problem to use the same name.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good point, TypeOf_
it looks odd, even to me. (... "odd even" 🙃).
It would be too bad if anyone gets inspired by this and copies it to other places.
I'll try your suggestion.
* Support for `null` or `undefined` will be removed in future versions, | ||
* along with the feature toggle `ssrStrictErrorHandlingForHttpAndNgrx`. | ||
*/ | ||
constructor(payload: { ownerKey: string; error: null | undefined }); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
query:
Is the order od constructor's overriding proper? Shouldn't the new, not deprecated definition, be first?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good catch! will fix it
* Support for `null` or `undefined` will be removed in future versions, | ||
* along with the feature toggle `ssrStrictErrorHandlingForHttpAndNgrx`. | ||
*/ | ||
constructor(payload: { ownerKey: string; error: null | undefined }); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
query:
Is the order od constructor's overriding proper? Shouldn't the new, not deprecated definition, be first?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Many thanks for a careful review. Good catch!
Hmm. it seems like I could not have saved and commited latest changes in this file.
Moreover, I can see null | undefined
being used here, instead of any
(but should be any
).
*/ | ||
constructor(payload: { ownerKey: string; error: null | undefined }); | ||
constructor( | ||
// eslint-disable-next-line @typescript-eslint/unified-signatures -- needed to deprecate only the old constructor |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
query:
Shouldn't the eslint-disable-next-line
comment be used in a deprecated constructor? Or actually it doesn't matter? Nevertheless, it's different than in other places.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
it's a natural consequence of the mistake you commented on there. it fill be fixed by fixing the mistake there
*/ | ||
constructor(payload: { ownerKey: string; error: null | undefined }); | ||
constructor( | ||
// eslint-disable-next-line @typescript-eslint/unified-signatures -- needed to deprecate only the old constructor |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
query:
Shouldn't the eslint-disable-next-line
comment be used in a deprecated constructor? Or actually it doesn't matter? Nevertheless, it's different than in other places.
*/ | ||
constructor(payload: { ownerKey: string; error: null | undefined }); | ||
constructor( | ||
// eslint-disable-next-line @typescript-eslint/unified-signatures -- needed to deprecate only the old constructor |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
query
: Shouldn't the eslint-disable-next-line
comment be used in a deprecated constructor? Or actually it doesn't matter? Nevertheless, it's different than in other places.
* Support for `null` or `undefined` will be removed in future versions, | ||
* along with the feature toggle `ssrStrictErrorHandlingForHttpAndNgrx`. | ||
*/ | ||
constructor(payload: { ownerKey: string; error: null | undefined }); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
query:
Is the order od constructor's overriding proper? Shouldn't the new, not deprecated definition, be first?
error: null | undefined, | ||
scope?: string | ||
); | ||
constructor( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
query:
Is the order od constructor's overriding proper? Shouldn't the new, not deprecated definition, be first?
* Note: Allowing for `null` or `undefined` will be removed in future versions | ||
* together with the feature toggle `ssrStrictErrorHandlingForHttpAndNgrx`. | ||
**/ | ||
constructor( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
query:
Shouldn't the eslint-disable-next-line comment be used in a deprecated constructor? Or actually it doesn't matter? Nevertheless, it's different than in other places.
- put non-deprecated constructor before the deprecated one - update outdated @deprecate JSdoc - to be more concise - change `null | undefined` to `any` in @deprecated constructors
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM 👌
…t in favor of the new property `error`
… in favor of the new property `error`
After re-thinking, the value in runtime can be anything unknown, so there is no sense in deprecating null | undefined, because it can be null or undefined anyway in runtime. So closing in favor of #19036 |
In previous past PR within this Epic branch, we've made breaking changes - changing constructor signatures of ngrx actions from having
error: any
toerror: <something more specific>
(i.e.error: ErrorActionType
). In this PR, we revert this change - allowing forerror: any
but at the same time deprecating it. Moreover, the non-deprecated constructor requires the value to be non-null, non-undefined.fixes https://jira.tools.sap/browse/CXSPA-7198 (it's part 1/2; the part 2/2 - adding
implements ErrorAction
- will arrive in a separate PR for the breviety of this PR)QA steps
To conduct the QA steps, please add the code snippets given below to your
app.module.ts
(please make sure to import the pasted items; note: for auto-adding missing imports in VSCode you might find this trick helpful).Table of contents:
EntityFailAction
)undefined
errornull
errorany
errorunknown
errorerror?
3rd argument in the actionEntityFailAction
(it's the only one example with optionalerror?
argument. the rest of actions in examples below have a requirederror
param).(screenshot: )
![image](https://private-user-images.githubusercontent.com/4001059/344625846-7b430961-bb50-48e5-9c48-eb434d01d457.png?jwt=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJnaXRodWIuY29tIiwiYXVkIjoicmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbSIsImtleSI6ImtleTUiLCJleHAiOjE3MjE4MDE4NTcsIm5iZiI6MTcyMTgwMTU1NywicGF0aCI6Ii80MDAxMDU5LzM0NDYyNTg0Ni03YjQzMDk2MS1iYjUwLTQ4ZTUtOWM0OC1lYjQzNGQwMWQ0NTcucG5nP1gtQW16LUFsZ29yaXRobT1BV1M0LUhNQUMtU0hBMjU2JlgtQW16LUNyZWRlbnRpYWw9QUtJQVZDT0RZTFNBNTNQUUs0WkElMkYyMDI0MDcyNCUyRnVzLWVhc3QtMSUyRnMzJTJGYXdzNF9yZXF1ZXN0JlgtQW16LURhdGU9MjAyNDA3MjRUMDYxMjM3WiZYLUFtei1FeHBpcmVzPTMwMCZYLUFtei1TaWduYXR1cmU9ZDUyYjhhMjFkNjZlYTc1ZmFhNWU2MTIwMjg5MjcwMzQzZDRlNDJjZjU3NjYzYTQ3ZmZlNDc2ZTBmNGFlNDkxNCZYLUFtei1TaWduZWRIZWFkZXJzPWhvc3QmYWN0b3JfaWQ9MCZrZXlfaWQ9MCZyZXBvX2lkPTAifQ.jlKSaKtX_auntmk9LX3VRApt5uMzy5-qdUEUGLdcpWU)
undefined
error object to various actions, e.g. that in your VSCode the names of the constructed classes are crossed out:(screenshot: )
![image](https://private-user-images.githubusercontent.com/4001059/344628155-72470de1-5af1-4f5e-b102-035dac36364e.png?jwt=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJnaXRodWIuY29tIiwiYXVkIjoicmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbSIsImtleSI6ImtleTUiLCJleHAiOjE3MjE4MDE4NTcsIm5iZiI6MTcyMTgwMTU1NywicGF0aCI6Ii80MDAxMDU5LzM0NDYyODE1NS03MjQ3MGRlMS01YWYxLTRmNWUtYjEwMi0wMzVkYWMzNjM2NGUucG5nP1gtQW16LUFsZ29yaXRobT1BV1M0LUhNQUMtU0hBMjU2JlgtQW16LUNyZWRlbnRpYWw9QUtJQVZDT0RZTFNBNTNQUUs0WkElMkYyMDI0MDcyNCUyRnVzLWVhc3QtMSUyRnMzJTJGYXdzNF9yZXF1ZXN0JlgtQW16LURhdGU9MjAyNDA3MjRUMDYxMjM3WiZYLUFtei1FeHBpcmVzPTMwMCZYLUFtei1TaWduYXR1cmU9ODQ2ZjU0ZTFkNWU2NzI3Y2I4YzE5YjgxYmMwNzEzZTc3MTAxMjYyYTdmYjgzZGFhMjE2ZWRkNzY3NDlhZjUxZCZYLUFtei1TaWduZWRIZWFkZXJzPWhvc3QmYWN0b3JfaWQ9MCZrZXlfaWQ9MCZyZXBvX2lkPTAifQ.61T8MMK923CKZ-v4nNd2adoHIgmw00ZzoY9vHkm8hQg)
(screenshot: )
![image](https://private-user-images.githubusercontent.com/4001059/344632443-12ad3037-c657-467d-9473-5f0f32763015.png?jwt=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJnaXRodWIuY29tIiwiYXVkIjoicmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbSIsImtleSI6ImtleTUiLCJleHAiOjE3MjE4MDE4NTcsIm5iZiI6MTcyMTgwMTU1NywicGF0aCI6Ii80MDAxMDU5LzM0NDYzMjQ0My0xMmFkMzAzNy1jNjU3LTQ2N2QtOTQ3My01ZjBmMzI3NjMwMTUucG5nP1gtQW16LUFsZ29yaXRobT1BV1M0LUhNQUMtU0hBMjU2JlgtQW16LUNyZWRlbnRpYWw9QUtJQVZDT0RZTFNBNTNQUUs0WkElMkYyMDI0MDcyNCUyRnVzLWVhc3QtMSUyRnMzJTJGYXdzNF9yZXF1ZXN0JlgtQW16LURhdGU9MjAyNDA3MjRUMDYxMjM3WiZYLUFtei1FeHBpcmVzPTMwMCZYLUFtei1TaWduYXR1cmU9ZjI2NzAyM2JhM2ExZmUyN2QwNWVhYzI3ZWY2NGEzZWNmM2EyNjIxMzU0Mjk1YTIwMjk3OTFkMjA2YjZhNmIyOSZYLUFtei1TaWduZWRIZWFkZXJzPWhvc3QmYWN0b3JfaWQ9MCZrZXlfaWQ9MCZyZXBvX2lkPTAifQ.LTQemGpg4z4b4tjE0CNKb54tvWZH8rKennYpg9sqWks)
(screenshot: )
![image](https://private-user-images.githubusercontent.com/4001059/344632675-4a2edba1-4b5e-4130-b546-8c1ebf209536.png?jwt=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJnaXRodWIuY29tIiwiYXVkIjoicmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbSIsImtleSI6ImtleTUiLCJleHAiOjE3MjE4MDE4NTcsIm5iZiI6MTcyMTgwMTU1NywicGF0aCI6Ii80MDAxMDU5LzM0NDYzMjY3NS00YTJlZGJhMS00YjVlLTQxMzAtYjU0Ni04YzFlYmYyMDk1MzYucG5nP1gtQW16LUFsZ29yaXRobT1BV1M0LUhNQUMtU0hBMjU2JlgtQW16LUNyZWRlbnRpYWw9QUtJQVZDT0RZTFNBNTNQUUs0WkElMkYyMDI0MDcyNCUyRnVzLWVhc3QtMSUyRnMzJTJGYXdzNF9yZXF1ZXN0JlgtQW16LURhdGU9MjAyNDA3MjRUMDYxMjM3WiZYLUFtei1FeHBpcmVzPTMwMCZYLUFtei1TaWduYXR1cmU9ZmRlMjY1YjY0NWI0MmQ4ZDI5OGI2MWEzYTcyMDE2MjE2ZTI2ZjBkYjljOGExZTE1NzhmZWI2ZDY5YWQ2ZmU2MCZYLUFtei1TaWduZWRIZWFkZXJzPWhvc3QmYWN0b3JfaWQ9MCZrZXlfaWQ9MCZyZXBvX2lkPTAifQ.rFpfQZUW8Fib3k3xk0qhwgdwaF1z7N7hWLSni6fFzIU)
(screenshot: )
![image](https://private-user-images.githubusercontent.com/4001059/344632816-dbe8b1d1-562c-46f8-ac63-91ca9159a66d.png?jwt=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJnaXRodWIuY29tIiwiYXVkIjoicmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbSIsImtleSI6ImtleTUiLCJleHAiOjE3MjE4MDE4NTcsIm5iZiI6MTcyMTgwMTU1NywicGF0aCI6Ii80MDAxMDU5LzM0NDYzMjgxNi1kYmU4YjFkMS01NjJjLTQ2ZjgtYWM2My05MWNhOTE1OWE2NmQucG5nP1gtQW16LUFsZ29yaXRobT1BV1M0LUhNQUMtU0hBMjU2JlgtQW16LUNyZWRlbnRpYWw9QUtJQVZDT0RZTFNBNTNQUUs0WkElMkYyMDI0MDcyNCUyRnVzLWVhc3QtMSUyRnMzJTJGYXdzNF9yZXF1ZXN0JlgtQW16LURhdGU9MjAyNDA3MjRUMDYxMjM3WiZYLUFtei1FeHBpcmVzPTMwMCZYLUFtei1TaWduYXR1cmU9NTgyOGI4NTg5YzE3M2Q5YTA3NzQwZmE1NzRiOTYzNjczYmIwNTMzMTFlNDQ2MDUzZTk3ZWJlM2JiOTViODk1MSZYLUFtei1TaWduZWRIZWFkZXJzPWhvc3QmYWN0b3JfaWQ9MCZrZXlfaWQ9MCZyZXBvX2lkPTAifQ.es2_tQjF3_K-fEx3IX4dSAmKbS__-kTFb6U0mlmTdo8)
(screenshot: )
![image](https://private-user-images.githubusercontent.com/4001059/344631616-f6dae91d-c527-42f2-b591-76ca5cb5aa77.png?jwt=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJnaXRodWIuY29tIiwiYXVkIjoicmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbSIsImtleSI6ImtleTUiLCJleHAiOjE3MjE4MDE4NTcsIm5iZiI6MTcyMTgwMTU1NywicGF0aCI6Ii80MDAxMDU5LzM0NDYzMTYxNi1mNmRhZTkxZC1jNTI3LTQyZjItYjU5MS03NmNhNWNiNWFhNzcucG5nP1gtQW16LUFsZ29yaXRobT1BV1M0LUhNQUMtU0hBMjU2JlgtQW16LUNyZWRlbnRpYWw9QUtJQVZDT0RZTFNBNTNQUUs0WkElMkYyMDI0MDcyNCUyRnVzLWVhc3QtMSUyRnMzJTJGYXdzNF9yZXF1ZXN0JlgtQW16LURhdGU9MjAyNDA3MjRUMDYxMjM3WiZYLUFtei1FeHBpcmVzPTMwMCZYLUFtei1TaWduYXR1cmU9YTFlYTU4YmQwMWYxMmUzNzE4Zjg0ZjlmMjNhZDVlNmIzMmI2ZWMzZjhhZTFmYTI1MmE5YjJkMTAyZGM4OGIwMSZYLUFtei1TaWduZWRIZWFkZXJzPWhvc3QmYWN0b3JfaWQ9MCZrZXlfaWQ9MCZyZXBvX2lkPTAifQ.IOJjh8W_MS7IuvF-6jsu6KkOQyluvXMcWi7FVvr8xCc)
(screenshot: )
![image](https://private-user-images.githubusercontent.com/4001059/344632992-3dbc01ba-187d-46af-b83f-2634c21be515.png?jwt=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJnaXRodWIuY29tIiwiYXVkIjoicmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbSIsImtleSI6ImtleTUiLCJleHAiOjE3MjE4MDE4NTcsIm5iZiI6MTcyMTgwMTU1NywicGF0aCI6Ii80MDAxMDU5LzM0NDYzMjk5Mi0zZGJjMDFiYS0xODdkLTQ2YWYtYjgzZi0yNjM0YzIxYmU1MTUucG5nP1gtQW16LUFsZ29yaXRobT1BV1M0LUhNQUMtU0hBMjU2JlgtQW16LUNyZWRlbnRpYWw9QUtJQVZDT0RZTFNBNTNQUUs0WkElMkYyMDI0MDcyNCUyRnVzLWVhc3QtMSUyRnMzJTJGYXdzNF9yZXF1ZXN0JlgtQW16LURhdGU9MjAyNDA3MjRUMDYxMjM3WiZYLUFtei1FeHBpcmVzPTMwMCZYLUFtei1TaWduYXR1cmU9OGVjZWYxNTcwZmM0Yzc5YTE1YTI4MzNjMzYzYzRkYjhhM2Q2MDZlMTUyYWRhNTBhYTRhODNjNmY3OTZkYTdhNiZYLUFtei1TaWduZWRIZWFkZXJzPWhvc3QmYWN0b3JfaWQ9MCZrZXlfaWQ9MCZyZXBvX2lkPTAifQ.y2uGKXq8zKLOefPJ7r6CAlU9z-iBvvhHJAV8OUI3Jsc)
(screenshot: )
![image](https://private-user-images.githubusercontent.com/4001059/344633123-5ed4662e-fc0a-4431-b3d4-deeba6c87fc4.png?jwt=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJnaXRodWIuY29tIiwiYXVkIjoicmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbSIsImtleSI6ImtleTUiLCJleHAiOjE3MjE4MDE4NTcsIm5iZiI6MTcyMTgwMTU1NywicGF0aCI6Ii80MDAxMDU5LzM0NDYzMzEyMy01ZWQ0NjYyZS1mYzBhLTQ0MzEtYjNkNC1kZWViYTZjODdmYzQucG5nP1gtQW16LUFsZ29yaXRobT1BV1M0LUhNQUMtU0hBMjU2JlgtQW16LUNyZWRlbnRpYWw9QUtJQVZDT0RZTFNBNTNQUUs0WkElMkYyMDI0MDcyNCUyRnVzLWVhc3QtMSUyRnMzJTJGYXdzNF9yZXF1ZXN0JlgtQW16LURhdGU9MjAyNDA3MjRUMDYxMjM3WiZYLUFtei1FeHBpcmVzPTMwMCZYLUFtei1TaWduYXR1cmU9OWM4N2Y0NmEzNWFhZTY2NDIyYjZlYWJmMWQ0NzViNmNiMjJiZjBkZmY0NzI0MTlkZDAzMzFlYTQ3Mjg0YTNkYiZYLUFtei1TaWduZWRIZWFkZXJzPWhvc3QmYWN0b3JfaWQ9MCZrZXlfaWQ9MCZyZXBvX2lkPTAifQ.-4F-EA8Tms4F-br2kG3KWuNEPTEqsG-ZTPnbkq5CBOg)