├── .gitignore
├── README.md
├── applets
├── twilio-conversations
│ ├── README.md
│ ├── code
│ │ ├── retool
│ │ │ ├── retool-custom-component.html
│ │ │ └── twilio-conversations-retool-app.json
│ │ └── twilio-functions
│ │ │ └── chat_token.js
│ ├── docs
│ │ ├── retool-setup.md
│ │ └── twilio-setup.md
│ └── images
│ │ └── twilio-converations.png
└── twilio-dialer
│ ├── README.md
│ ├── code
│ ├── retool
│ │ ├── retool-custom-component.html
│ │ └── twilio-dialer-retool-app.json
│ └── twilio-functions
│ │ ├── twiml-app.js
│ │ └── voice-token.js
│ ├── docs
│ ├── retool-setup.md
│ └── twilio-setup.md
│ └── images
│ └── twilio-dialer.png
├── embed-url-generator
├── README.md
├── code
│ └── embed_url_generator.json
└── images
│ ├── 1_create_api_key.png
│ ├── 2_add_api_generator_as_resource.png
│ ├── 3_update_app_with_new_resource.jpeg
│ ├── 4_copy_app_uuid.jpeg
│ ├── 5_copy_group_id.gif
│ └── 6_embed_url_app.png
├── incident-central
├── README.md
├── code
│ ├── Incident-Central-Details.json
│ ├── Incident-Central-Home.json
│ ├── Incident-Navbar.json
│ ├── Report-Incident.json
│ └── extras
│ │ └── Database-Setup.json
├── images
│ ├── home-page.png
│ ├── incident-central-demo.gif
│ ├── incident-details.png
│ ├── report-incident-created.png
│ └── report-incident.png
└── setup-guides
│ ├── README.md
│ ├── images
│ ├── adjust-navbar-link.png
│ ├── adjust-report-incident-link.png
│ ├── fill-in-slack-team-id.png
│ ├── pagerduty-api-resource.png
│ └── slack-api-resource.png
│ ├── set-up-database.md
│ ├── set-up-pagerduty.md
│ ├── set-up-retool-apps.md
│ ├── set-up-retool-resources.md
│ └── set-up-slack.md
├── snowflake-resource-optimization
├── README.md
├── code
│ └── Snowflake-Resource-Optimization-Setup-Configuration.json
├── images
│ ├── home-page.png
│ ├── setup-config-demo.gif
│ ├── statement-timeouts.png
│ ├── users.png
│ └── warehouses.png
└── setup-guides
│ ├── README.md
│ ├── images
│ └── snowflake-resource.png
│ ├── set-up-retool-app.md
│ ├── set-up-retool-resource.md
│ └── set-up-snowflake.md
├── snowflake-uar-reviews
├── README.md
├── code
│ ├── Compliance_Review_Portal.json
│ ├── Create_Review_Cycle.json
│ └── Manager_Review_Portal.json
├── images
│ ├── compliance_review.png
│ ├── create_cycle_employees.png
│ ├── create_cycle_name_cycle.png
│ ├── create_cycle_system_roles.png
│ ├── manager_review_bulk_approve.png
│ ├── manager_review_bulk_revoke.png
│ └── manager_review_landing.png
└── setup-guides
│ ├── images
│ └── snowflake_resource.png
│ ├── set-up-retool-app.md
│ ├── set-up-retool-resource.md
│ └── set-up-snowflake.md
└── usage-viewer
├── README.md
├── code
├── usage_analytics.json
└── usage_analytics_spaces.json
└── images
└── usage_analytics.png
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | ## ✨ Retool App Exchange ✨
2 |
3 | The Retool App Exchange is a collection of open source apps and applets built by Retools and community members.
4 |
5 | We welcome you to download apps to use, contribute extensions to apps, or contribute new apps that you think others might find helpful.
6 |
7 | Each app consists of ready-to-use code that you can download and set up in your own Retool instance, plus a detailed setup guide.
8 |
9 |
10 | ### 🛠 Updates
11 | * [Apr 2023] [Embed URL Generator](./embed-url-generator) - An app for customers of [Retool Embed](https://retool.com/products/embed) that lets you preview embedded Retool apps with different metadata and other parameters.
12 | * [Sep 2022] [Twilio Conversations](./applets/twilio-conversations) - An applet with an embeded Twilio Conversations client, enabling bi-directional messaging with an SMS client
13 | * [Aug 2022] [Snowflake: User Access Roles Review](./snowflake-uar-reviews/) - An app that allows managers and compliance teams to audit and review user access roles in Snowflake.
14 | * [Jul 2022] [Twilio Dialer](./applets/twilio-dialer/) - An applet with an embedded Twilio dialer, enabling outbound voice calls
15 | * [May 2022] [Usage Viewer](./usage-viewer/) - An app that helps you identify top apps by pageviews and queries in your Retool instance
16 | * [Mar 2022] [Snowflake Resource Optimization: Setup & Configuration](./snowflake-resource-optimization/) - An app that helps you monitor & manage Snowflake resource consumption
17 | * [Jan 2022] [Incident Central](./incident-central/) - An incident response hub for engineering teams
18 |
19 | ### 👩💻 Contributing
20 | To contribute, open a Github Issue on this repo, and let us know what you are thinking of contributing! We encourage you to reach out before you get started building to get early feedback.
21 |
--------------------------------------------------------------------------------
/applets/twilio-conversations/README.md:
--------------------------------------------------------------------------------
1 | # Twilio Conversations
2 |
3 | ## Overview
4 | This applet demonstrates how to embed Twilio Conversations in a Retool app to enable bi-directional chat with an SMS client. The app requires an SMS-enabled phone number which you must register with Twilio in order to test.
5 |
6 | This serves as an example of how to use Retool [custom components](https://docs.retool.com/docs/custom-components) to add advanced functionality to your apps. You'd need to extensively modify the parts outside of the custom component to use it for anything real!
7 |
8 | If you'd just like a guided tour of how the applet works, check out our [video walkthrough](https://www.youtube.com/watch?v=NL8liiIYN0o). Otherwise, read on to learn how to set it up in your own environment!
9 |
10 |
11 |
12 |
13 | ## Who is this for?
14 | This applet will be most interesting for developers who are familiar with the core concepts of building Retool apps and are looking to take the next step with custom components.
15 |
16 | If you're just getting started with Retool, check out [Retool University](https://docs.retool.com/docs/retool-university)!
17 |
18 | ## Setting up the app
19 | To send and receive messages from your Retool app, you'll first need to get up and running with Twilio:
20 |
21 | [Twilio Setup](./docs/twilio-setup.md)
22 |
23 | Once you've gotten Twilio set up, follow these steps to get it all working in Retool:
24 |
25 | [Retool Setup](./docs/retool-setup.md)
26 |
27 | ## How to contribute
28 | Please open a Github Issue on this repo, and let us know about your interest in contributing! We encourage you to reach out before you get started building to get early feedback.
29 |
--------------------------------------------------------------------------------
/applets/twilio-conversations/code/retool/retool-custom-component.html:
--------------------------------------------------------------------------------
1 |
14 |
15 |
16 |
17 |
18 | Embedded Twilio Conversations Client for Retool
19 |
20 |
25 |
26 |
104 |
105 |
106 | Twilio Conversations Custom Component
107 |
108 |
109 |
--------------------------------------------------------------------------------
/applets/twilio-conversations/code/twilio-functions/chat_token.js:
--------------------------------------------------------------------------------
1 | exports.handler = function (context, event, callback) {
2 |
3 | const IDENTITY = event.request.headers.identity;
4 |
5 | const { ACCOUNT_SID } = context;
6 |
7 | // set these values in your .env file
8 | const { API_KEY_SID, API_KEY_SECRET, CONVERSATIONS_SID } = context;
9 |
10 | const { AccessToken } = Twilio.jwt;
11 | const { ChatGrant } = AccessToken;
12 |
13 | const accessToken = new AccessToken(ACCOUNT_SID, API_KEY_SID, API_KEY_SECRET);
14 | accessToken.identity = IDENTITY;
15 | const grant = new ChatGrant({
16 | serviceSid: CONVERSATIONS_SID
17 | });
18 | accessToken.addGrant(grant);
19 |
20 | const response = new Twilio.Response();
21 |
22 | response.appendHeader('Content-Type', 'application/json');
23 | response.setBody({
24 | identity: IDENTITY,
25 | token: accessToken.toJwt(),
26 | });
27 | callback(null, response);
28 | };
--------------------------------------------------------------------------------
/applets/twilio-conversations/docs/retool-setup.md:
--------------------------------------------------------------------------------
1 | # Retool Setup
2 |
3 | Make sure you've completed the steps described in the [Twilio Setup](./twilio-setup.md) documentation before proceeding!
4 |
5 | ## 1. Create a Retool REST API Resource to retrieve chat tokens
6 |
7 | In order for your Retool app to authenticate with Twilio, you'll need to get a valid chat token. To set this up:
8 |
9 | 1. Navigate to the `/resources` page in your instance and click "Create a new resource."
10 | 2. Select REST API as the resource type.
11 | 3. Name the resource “Twilio Conversations Chat Token”.
12 | 4. Specify the URL of your `chat_token` function as noted in step six of the Twilio setup.
13 | 5. Specify a header with a key of `identity` and a value of `{{current_user.email}}`. This is used to pass the email address of the Retool user to Twilio for use in the token identity.
14 | 6. Save your changes.
15 |
16 | ## 2. Create a Twilio API Resource to manage Conversations and Participants
17 |
18 | Your Retool app will need to delete previously created Conversations in order to start new Conversations with the same SMS number, as well as add new participants to the Conversation. To set this up:
19 |
20 | 1. Navigate to the `/resources` page in your instance and click "Create a new resource".
21 | 2. Select Twilio as the resource type.
22 | 3. Name the resource "Twilio Conversations API".
23 | 4. Specify your Twilio API Key SID as the value of `Account SID` and your Twilio API Key Secret as the value of `Auth Token`.
24 | - These credentials were created in Step 2 of the [Twilio Setup](twilio-setup.md).
25 | - The current version the Twilio resource doesn't correctly validate API Keys, so the "test connection" button won't work on the Resource configuration screen.
26 | 5. Save your changes.
27 |
28 | ## 3. Download the Retool app code
29 |
30 | Download [twilio-conversations-retool-app.json](/applets/twilio-conversations/code/retool/twilio-conversations-retool-app.json) from this repository.
31 |
32 | ## 4. Import the Retool app code
33 | On the [Retool main page](https://docs.retool.com/docs/protected-applications-getting-started#importing-the-application), click `Create new` and select `Import an app`. Upload the JSON file containing the app code, and name the app.
34 |
35 | You may need to connect the Resource Queries in the Retool app with the Resources you created in the previous steps:
36 |
37 | | Folder | Resource Query | Resource |
38 | | --- | --- | --- |
39 | | gets | getChatToken | Twilio Conversations Chat Token |
40 | | gets | getConversations | Twilio Conversations API |
41 | | writes | addParticipant | Twilio Conversations API |
42 | | writes | deleteConversations | Twilio Conversations API |
43 |
44 | ## 5. Explore the Retool app
45 |
46 | If everything is working as expected, you should now be able to send and receive messages between the Retool app and the phone number you registered! To use the app:
47 |
48 | 1. When first loading the app, you'll be prompted to enter your Twilio Conversations Service SID. This is the Service SID you retrieved in step 3 of the Twilio setup.
49 | 2. Once the app has initialized, fill out the SMS settings:
50 | - Enter the phone number you were assigned in step 0 of the Twilio setup as "Twilio Outbound number".
51 | - Enter the SMS-enabled phone number that you verified in step 1 of the Twilio setup as "Customer number" and click Submit.
52 | 4. Enter some text in the chat box and press "enter" to send!
53 |
54 | ## 6. Learn about how the custom component works
55 |
56 | You can read more about [Retool custom components](https://docs.retool.com/docs/custom-components). You can also view the code used in the Twilio Conversations component in Retool, the contents of which are included in [retool-custom-component.html](/applets/twilio-conversations/code/retool/retool-custom-component.html) for easy reading!
57 |
58 | Also, check out our in-depth [video walkthrough](https://www.youtube.com/watch?v=NL8liiIYN0o) of how this app is put together!
59 |
--------------------------------------------------------------------------------
/applets/twilio-conversations/docs/twilio-setup.md:
--------------------------------------------------------------------------------
1 | # Twilio Setup
2 | Before you can get this Retool app working, you'll need to do some pre-work in Twilio.
3 |
4 | ## 0. Sign up for a Twilio account and get a phone number
5 |
6 | If you don't already have a Twilio account, you'll need to get one! The good news is that Twilio makes it easy (and free!) to get started. Just head over to https://www.twilio.com/try-twilio and sign up!
7 |
8 | As you go through the onboarding flow, indicate that you'll be building a messaging app. Once you're logged into Twilio console, you'll be prompted to get a Twilio phone number. Note the phone number you were assigned for later!
9 |
10 | ## 1. Verify your SMS-enabled phone number
11 | [Verify your phone number](https://support.twilio.com/hc/en-us/articles/223180048-Adding-a-Verified-Phone-Number-or-Caller-ID-with-Twilio) to enable outbound SMS to that number. Note that this is only necessary with trial Twilio accounts.
12 |
13 | ## 2. Create an API key/secret pair
14 |
15 | [Create an API key](https://www.twilio.com/docs/iam/keys/api-key-resource) and note the key's `SID` and `Secret` for later.
16 |
17 | ## 3. Create a Twilio Conversations Service
18 | [Create a Conversations Service](https://www.twilio.com/docs/conversations/api/service-resource) and note the `SID` for later. A Conversations Service is a top-level container for other resources in the Twilio Conversations REST API.
19 |
20 | Note: you must create a new Conversations Service for use exclusively with this demo! The demo deletes all Conversations inside of this service during initialization, so using an existing Conversations Service is unsafe!
21 |
22 | ## 4. Create a Twilio Functions Service
23 | A [Twilio Service](https://www.twilio.com/docs/runtime/functions/create-service) is a container for the server-less functions which make it possible for your Retool app to get a chat access token.
24 |
25 | ## 5. Set Twilio Service environment variables
26 |
27 | In your new Twilio Service, set the following environment variables:
28 |
29 | | Key | Value |
30 | | ---- | ---- |
31 | | `CONVERSATIONS_SID` | The `Conversations SID` you noted in step three |
32 | | `API_KEY_SID` | The `SID` you noted in step one |
33 | | `API_KEY_SECRET` | The `Secret` you noted in step one |
34 |
35 | ## 6. Configure Twilio Service dependencies
36 |
37 | The server-less functions running in Twilio need access to a few software libraries to function properly. Import the following NPM modules:
38 |
39 | | Module | Version |
40 | | --- | --- |
41 | | `lodash` | `4.17.11` |
42 | | `@twilio/runtime-handler` | `1.2.1` |
43 | | `twilio` | `3.29.2` |
44 | | `xmldom` | `0.1.27` |
45 | | `util` | `0.11.0` |
46 |
47 | ## 7. Create a function to issue chat tokens
48 |
49 | In order for the embedded Twilio Conversations client to authenticate with Twilio's services, it needs a valid access token. To create a function for issuing chat tokens:
50 |
51 | 1. Add a function
52 | 2. Specify a path `chat_token`
53 | 3. Set the function to `Public`
54 | - **Heads up! This means that anyone who can guess the URL of your function can issue valid access tokens. You would need to implement authentication on this endpoint for a production use-case!**
55 | 4. Copy and paste the contents of [chat_token.js](/applets/twilio-conversations/code/twilio-functions/chat_token.js) into the function and save
56 | 5. Copy the URL for this function for later use
57 |
58 | ## 8. Deploy your Twilio Service!
59 |
60 | This finalizes all of the changes you've made.
61 |
62 | ## Next steps
63 | Next, set up the Retool app to make calls using your Twilio service!
64 |
65 | [Retool Setup](./retool-setup.md)
66 |
--------------------------------------------------------------------------------
/applets/twilio-conversations/images/twilio-converations.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tryretool/retool-app-exchange/ea48efde500e831b0435a0e7ae1e28d1e5021e83/applets/twilio-conversations/images/twilio-converations.png
--------------------------------------------------------------------------------
/applets/twilio-dialer/README.md:
--------------------------------------------------------------------------------
1 | # Twilio Dialer
2 |
3 | ## Overview
4 | This applet demonstrates how to embed a Twilio dialer in a Retool app. The app is set up with a hardcoded table of "leads" to simulate a simple call center app. You can edit the contents of the leads table directly, or simply type your phone number in the field to try calling yourself.
5 |
6 | This serves as an example of how to use Retool [custom components](https://docs.retool.com/docs/custom-components) to add advanced functionality to your apps. You'd need to extensively modify the parts outside of the custom component to use it for anything real!
7 |
8 | If you'd just like a guided tour of how the applet works, check out our [video walkthrough](https://youtu.be/xPSS3kp3XEI). Otherwise, read on to learn how to set it up in your own environment!
9 |
10 |
11 |
12 |
13 | ## Who is this for?
14 | This applet will be most interesting for developers who are familiar with the core concepts of building Retool apps and are looking to take the next step with custom components.
15 |
16 | If you're just getting started with Retool, check out [Retool University](https://docs.retool.com/docs/retool-university)!
17 |
18 | ## Setting up the app
19 | To make outbound calls from your Retool app, you'll first need to get up and running with Twilio:
20 |
21 | [Twilio Setup](./docs/twilio-setup.md)
22 |
23 | Once you've gotten Twilio set up, follow these steps to get it all working in Retool:
24 |
25 | [Retool Setup](./docs/retool-setup.md)
26 |
27 | ## How to contribute
28 | Please open a Github Issue on this repo, and let us know about your interest in contributing! We encourage you to reach out before you get started building to get early feedback.
29 |
--------------------------------------------------------------------------------
/applets/twilio-dialer/code/retool/retool-custom-component.html:
--------------------------------------------------------------------------------
1 |
8 |
9 |
10 |
11 |
12 | Embedded Twilio Dialer for Retool
13 |
61 |
62 |
63 |
66 |
67 |
68 |
72 |
139 |
140 |
141 |
--------------------------------------------------------------------------------
/applets/twilio-dialer/code/retool/twilio-dialer-retool-app.json:
--------------------------------------------------------------------------------
1 | {"uuid":"9cf3009e-f335-11ec-84e1-53690545aee8","page":{"id":79078,"data":{"appState":"[\"~#iR\",[\"^ \",\"n\",\"appTemplate\",\"v\",[\"^ \",\"isFetching\",false,\"plugins\",[\"~#iOM\",[\"callButton\",[\"^0\",[\"^ \",\"n\",\"pluginTemplate\",\"v\",[\"^ \",\"id\",\"callButton\",\"type\",\"widget\",\"subtype\",\"ButtonWidget2\",\"namespace\",null,\"resourceName\",null,\"resourceDisplayName\",null,\"template\",[\"^3\",[\"horizontalAlign\",\"stretch\",\"clickable\",false,\"iconAfter\",\"\",\"submitTargetId\",null,\"hidden\",false,\"text\",\"{{ twilioDevice.model.status === \\\"ready_for_call\\\" ? \\\"Call\\\" : \\\"End\\\" }}\",\"showInEditor\",false,\"tooltipText\",\"\",\"style\",[\"^3\",[\"background\",\"{{ twilioDevice.model.status === \\\"ready_for_call\\\" ? theme.success : theme.danger }}\"]],\"styleVariant\",\"solid\",\"submit\",false,\"iconBefore\",\"{{ twilioDevice.model.status === \\\"ready_for_call\\\" ? \\\"/icon:bold/phone-telephone\\\" : \\\"/icon:bold/entertainment-control-button-stop\\\" }}\",\"events\",[\"~#iL\",[[\"^3\",[\"event\",\"click\",\"type\",\"widget\",\"method\",\"updateModel\",\"pluginId\",\"twilioDevice\",\"targetId\",null,\"params\",[\"^3\",[\"model\",\"{{ twilioDevice.model.status === \\\"ready_for_call\\\" ? {phoneNumber: phoneNumberEntry.value, status: \\\"retool_called\\\"} : {status: \\\"retool_ended\\\"} }}\"]],\"waitType\",\"debounce\",\"waitMs\",\"0\"]],[\"^3\",[\"event\",\"click\",\"type\",\"widget\",\"method\",\"setHidden\",\"pluginId\",\"welcomeText\",\"targetId\",null,\"params\",[\"^3\",[]],\"waitType\",\"debounce\",\"waitMs\",\"0\"]]]],\"loading\",false,\"loaderPosition\",\"auto\",\"disabled\",\"{{phoneNumberEntry.validationMessage || getVoiceToken.error}}\",\"maintainSpaceWhenHidden\",false]],\"style\",[\"^3\",[]],\"position2\",[\"^0\",[\"^ \",\"n\",\"position2\",\"v\",[\"^ \",\"container\",\"\",\"rowGroup\",\"body\",\"subcontainer\",\"\",\"row\",0.6000000000000002,\"col\",10,\"height\",1,\"width\",2,\"tabNum\",0]]],\"mobilePosition2\",null,\"mobileAppPosition\",null,\"tabIndex\",null,\"^=\",\"\",\"createdAt\",\"~m1656103970637\",\"updatedAt\",\"~m1666196727621\",\"folder\",\"\",\"screen\",null]]],\"getVoiceToken\",[\"^0\",[\"^ \",\"n\",\"pluginTemplate\",\"v\",[\"^ \",\"id\",\"getVoiceToken\",\"^4\",\"datasource\",\"^5\",\"RESTQuery\",\"^6\",null,\"^7\",\"6c59b7bd-9a66-49da-aa46-59d336560a01\",\"^8\",\"Twilio Voice Token\",\"^9\",[\"^3\",[\"queryRefreshTime\",\"\",\"paginationLimit\",\"\",\"body\",\"\",\"lastReceivedFromResourceAt\",null,\"queryDisabledMessage\",\"\",\"successMessage\",\"\",\"queryDisabled\",\"\",\"playgroundQuerySaveId\",\"latest\",\"resourceNameOverride\",\"\",\"runWhenModelUpdates\",true,\"paginationPaginationField\",\"\",\"headers\",\"\",\"showFailureToaster\",true,\"paginationEnabled\",false,\"query\",\"\",\"playgroundQueryUuid\",\"\",\"playgroundQueryId\",null,\"error\",null,\"privateParams\",[\"^:\",[]],\"runWhenPageLoadsDelay\",\"\",\"data\",null,\"importedQueryInputs\",[\"^3\",[]],\"isImported\",false,\"showSuccessToaster\",true,\"cacheKeyTtl\",\"\",\"cookies\",\"\",\"metadata\",null,\"changesetObject\",\"\",\"errorTransformer\",\"// The variable 'data' allows you to reference the request's data in the transformer. \\n// example: return data.find(element => element.isError)\\nreturn data.error\",\"confirmationMessage\",null,\"isFetching\",false,\"changeset\",\"\",\"rawData\",null,\"queryTriggerDelay\",\"0\",\"resourceTypeOverride\",null,\"watchedParams\",[\"^:\",[]],\"enableErrorTransformer\",false,\"showLatestVersionUpdatedWarning\",false,\"paginationDataField\",\"\",\"timestamp\",0,\"importedQueryDefaults\",[\"^3\",[]],\"enableTransformer\",false,\"showUpdateSetValueDynamicallyToggle\",true,\"runWhenPageLoads\",false,\"transformer\",\"// type your code here\\n// example: return formatDataAsArray(data).filter(row => row.quantity > 20)\\nreturn data\",\"events\",[\"^:\",[[\"^3\",[\"event\",\"success\",\"type\",\"widget\",\"method\",\"updateModel\",\"pluginId\",\"twilioDevice\",\"targetId\",null,\"params\",[\"^3\",[\"model\",\"{\\n data: {{getVoiceToken.data}},\\n}\"]],\"waitType\",\"debounce\",\"waitMs\",\"0\"]]]],\"queryTimeout\",\"10000\",\"requireConfirmation\",false,\"type\",\"GET\",\"queryFailureConditions\",\"\",\"changesetIsObject\",false,\"enableCaching\",false,\"allowedGroups\",[\"^:\",[]],\"bodyType\",\"json\",\"queryThrottleTime\",\"750\",\"updateSetValueDynamically\",false,\"notificationDuration\",\"\"]],\"^;\",null,\"^<\",null,\"^C\",null,\"^D\",null,\"^E\",null,\"^=\",\"\",\"^F\",\"~m1656023746140\",\"^G\",\"~m1657759435164\",\"^H\",\"\",\"^I\",null]]],\"leadsTable\",[\"^0\",[\"^ \",\"n\",\"pluginTemplate\",\"v\",[\"^ \",\"id\",\"leadsTable\",\"^4\",\"widget\",\"^5\",\"TableWidget\",\"^6\",null,\"^7\",null,\"^8\",null,\"^9\",[\"^3\",[\"showCustomButton\",false,\"sortMappedValue\",[\"^3\",[]],\"_filteredSortedRenderedDataWithTypes\",null,\"heightType\",\"fixed\",\"normalizedData\",null,\"saveChangesDisabled\",\"\",\"columnTypeProperties\",[\"^3\",[\"phoneNumber\",[\"^3\",[]]]],\"columnWidths\",[\"^:\",[]],\"showSummaryFooter\",false,\"disableRowSelectInteraction\",false,\"columnWidthsMobile\",[\"^:\",[]],\"hasNextAfterCursor\",\"\",\"columnTypeSpecificExtras\",[\"^3\",[]],\"onRowAdded\",\"\",\"columnHeaderNames\",[\"^3\",[]],\"alwaysShowPaginator\",false,\"columnColors\",[\"^3\",[\"id\",\"\",\"name\",\"\",\"email\",\"\",\"phoneNumber\",\"\",\"phoneNumer\",\"\",\"phoneNumebr\",\"\",\"sales\",\"\"]],\"columnFrozenAlignments\",[\"^3\",[]],\"allowMultiRowSelect\",false,\"columnFormats\",[\"^3\",[\"phoneNumber\",\"TextDataCell\"]],\"columnRestrictedEditing\",[\"^3\",[]],\"showFilterButton\",true,\"_columnVisibility\",[\"^3\",[\"id\",true,\"name\",true,\"email\",true,\"phoneNumber\",true]],\"_columnSummaryTypes\",[\"^3\",[\"phoneNumber\",\"\"]],\"_columnsWithLegacyBackgroundColor\",[\"~#iOS\",[]],\"showAddRowButton\",false,\"_unfilteredSelectedIndex\",null,\"nextBeforeCursor\",\"\",\"columnVisibility\",[\"^3\",[\"id\",true,\"name\",true,\"email\",true,\"phoneNumber\",true]],\"selectedPageIndex\",\"0\",\"applyDynamicSettingsToColumnOrder\",true,\"rowColor\",[],\"actionButtonColumnName\",\"Actions\",\"resetAfterSave\",true,\"filterStackType\",\"and\",\"downloadRawData\",false,\"showFetchingIndicator\",true,\"serverPaginated\",false,\"data\",\"{{[{\\n \\\"id\\\": 1,\\n \\\"name\\\": \\\"Evan Weiss\\\",\\n \\\"email\\\": \\\"evan@weiss.com\\\",\\n \\\"phoneNumber\\\": \\\"+19255055511\\\"\\n}, {\\n \\\"id\\\": 2,\\n \\\"name\\\": \\\"Sue Shei\\\",\\n \\\"email\\\": \\\"sueshei@example.com\\\",\\n \\\"phoneNumber\\\": \\\"+19255593244\\\"\\n}, {\\n \\\"id\\\": 3,\\n \\\"name\\\": \\\"Jason Response\\\",\\n \\\"email\\\": \\\"jason@response.com\\\",\\n \\\"phoneNumber\\\": \\\"+19255595266\\\"\\n}, {\\n \\\"id\\\": 4,\\n \\\"name\\\": \\\"Cher Actor\\\",\\n \\\"email\\\": \\\"cher@example.com\\\",\\n \\\"phoneNumber\\\": \\\"+19255590643\\\"\\n}, {\\n \\\"id\\\": 5,\\n \\\"name\\\": \\\"Erica Widget\\\",\\n \\\"email\\\": \\\"erica@widget.org\\\",\\n \\\"phoneNumber\\\": \\\"+19255590604\\\"\\n}]}}\",\"displayedData\",null,\"actionButtons\",[\"^:\",[]],\"actionButtonSelectsRow\",true,\"selectRowByDefault\",true,\"defaultSortByColumn\",\"\",\"paginationOffset\",0,\"columnAlignment\",[\"^3\",[\"phoneNumber\",\"left\"]],\"columnSummaries\",[\"^ \"],\"showBoxShadow\",true,\"sortedDesc\",false,\"customButtonName\",\"\",\"columnMappersRenderAsHTML\",[\"^3\",[]],\"showRefreshButton\",true,\"pageSize\",5,\"useDynamicColumnSettings\",false,\"actionButtonPosition\",\"left\",\"dynamicRowHeights\",false,\"bulkUpdateAction\",\"\",\"afterCursor\",\"\",\"onCustomButtonPressQueryName\",\"\",\"changeSet\",[\"^ \"],\"sortedColumn\",\"\",\"_columnSummaryValues\",[\"^3\",[\"phoneNumber\",\"\"]],\"checkboxRowSelect\",true,\"_compatibilityMode\",false,\"showColumnBorders\",false,\"clearSelectionLabel\",\"Clear selection\",\"_renderedDataWithTypes\",null,\"columnAllowOverflow\",[\"^3\",[]],\"beforeCursor\",\"\",\"serverPaginationType\",\"limitOffsetBased\",\"onRowSelect\",\"\",\"showDownloadButton\",true,\"selectedIndex\",null,\"defaultSortDescending\",false,\"_sortedDisplayedDataIndices\",null,\"dynamicColumnSettings\",null,\"totalRowCount\",\"\",\"recordUpdates\",[],\"newRow\",null,\"emptyMessage\",\"No rows found\",\"columnEditable\",[\"^3\",[]],\"_viewerColumnSummaryTypes\",[\"^ \"],\"filters\",[],\"displayedDataIndices\",null,\"disableSorting\",[\"^3\",[]],\"columnMappers\",[\"^3\",[]],\"showClearSelection\",false,\"doubleClickToEdit\",true,\"overflowType\",\"pagination\",\"_reverseSortedDisplayedDataIndices\",null,\"showTableBorder\",true,\"selectedCell\",[\"^ \",\"index\",null,\"data\",null,\"columnName\",null],\"columns\",[\"^:\",[\"id\",\"name\",\"email\",\"phoneNumber\"]],\"defaultSelectedRow\",\"first\",\"freezeActionButtonColumns\",false,\"sort\",null,\"_columns\",[\"^:\",[\"id\",\"name\",\"email\",\"phoneNumber\"]],\"sortByRawValue\",[\"^3\",[]],\"calculatedColumns\",[\"^:\",[]],\"selectedRow\",[\"^ \",\"^K\",null,\"^L\",null],\"showPaginationOnTop\",false,\"_reverseDisplayedDataIndices\",null,\"nextAfterCursor\",\"\",\"useCompactMode\",false]],\"^;\",[\"^3\",[]],\"^<\",[\"^0\",[\"^ \",\"n\",\"position2\",\"v\",[\"^ \",\"^=\",\"\",\"^>\",\"body\",\"^?\",\"\",\"row\",1.2000000000000002,\"col\",0,\"^@\",8,\"^A\",8,\"^B\",0]]],\"^C\",null,\"^D\",null,\"^E\",null,\"^=\",\"\",\"^F\",\"~m1656117436953\",\"^G\",\"~m1657759435168\",\"^H\",\"\",\"^I\",null]]],\"leadsTableTitle\",[\"^0\",[\"^ \",\"n\",\"pluginTemplate\",\"v\",[\"^ \",\"id\",\"leadsTableTitle\",\"^4\",\"widget\",\"^5\",\"TextWidget2\",\"^6\",null,\"^7\",null,\"^8\",null,\"^9\",[\"^3\",[\"heightType\",\"auto\",\"horizontalAlign\",\"left\",\"hidden\",false,\"imageWidth\",\"fit\",\"showInEditor\",false,\"verticalAlign\",\"center\",\"_defaultValue\",\"\",\"tooltipText\",\"\",\"value\",\"## Leads\",\"disableMarkdown\",false,\"overflowType\",\"scroll\",\"maintainSpaceWhenHidden\",false]],\"^;\",[\"^3\",[]],\"^<\",[\"^0\",[\"^ \",\"n\",\"position2\",\"v\",[\"^ \",\"^=\",\"\",\"^>\",\"body\",\"^?\",\"\",\"row\",0,\"col\",0,\"^@\",0.6,\"^A\",1,\"^B\",0]]],\"^C\",null,\"^D\",null,\"^E\",null,\"^=\",\"\",\"^F\",\"~m1656117442328\",\"^G\",\"~m1657759435168\",\"^H\",\"\",\"^I\",null]]],\"phoneNumberEntry\",[\"^0\",[\"^ \",\"n\",\"pluginTemplate\",\"v\",[\"^ \",\"id\",\"phoneNumberEntry\",\"^4\",\"widget\",\"^5\",\"TextInputWidget2\",\"^6\",null,\"^7\",null,\"^8\",null,\"^9\",[\"^3\",[\"spellCheck\",false,\"readOnly\",\"\",\"iconAfter\",\"\",\"showCharacterCount\",false,\"autoComplete\",false,\"maxLength\",null,\"hidden\",false,\"customValidation\",\"{{/^\\\\+1[0-9]{10}$/.test(phoneNumberEntry.value) ? '' : 'Please enter a 9-digit US phone number prefixed with \\\"+1\\\"'}}\",\"patternType\",\"\",\"hideValidationMessage\",false,\"textBefore\",\"\",\"validationMessage\",\"\",\"textAfter\",\"\",\"showInEditor\",false,\"_defaultValue\",\"\",\"showClear\",false,\"pattern\",\"\",\"tooltipText\",\"\",\"labelAlign\",\"left\",\"formDataKey\",\"{{ self.id }}\",\"value\",\"{{leadsTable.selectedRow.data.phoneNumber}}\",\"labelCaption\",\"\",\"labelWidth\",\"33\",\"autoFill\",\"\",\"placeholder\",\"Phone number\",\"label\",\"\",\"_validate\",false,\"labelWidthUnit\",\"%\",\"invalid\",false,\"iconBefore\",\"\",\"minLength\",null,\"inputTooltip\",\"\",\"events\",[\"^3\",[]],\"autoCapitalize\",\"none\",\"loading\",false,\"disabled\",\"{{ getVoiceToken.error}}\",\"labelPosition\",\"left\",\"labelWrap\",false,\"maintainSpaceWhenHidden\",false,\"required\",false]],\"^;\",[\"^3\",[]],\"^<\",[\"^0\",[\"^ \",\"n\",\"position2\",\"v\",[\"^ \",\"^=\",\"\",\"^>\",\"body\",\"^?\",\"\",\"row\",0.6000000000000002,\"col\",8,\"^@\",1,\"^A\",2,\"^B\",0]]],\"^C\",null,\"^D\",null,\"^E\",null,\"^=\",\"\",\"^F\",\"~m1656117765580\",\"^G\",\"~m1666196741721\",\"^H\",\"\",\"^I\",null]]],\"twilioDevice\",[\"^0\",[\"^ \",\"n\",\"pluginTemplate\",\"v\",[\"^ \",\"id\",\"twilioDevice\",\"^4\",\"widget\",\"^5\",\"CustomComponentWidget\",\"^6\",null,\"^7\",null,\"^8\",null,\"^9\",[\"^3\",[\"allowTopNavigation\",false,\"allowDownloads\",false,\"allowFullscreen\",false,\"allowForms\",false,\"model\",\"{status: \\\"ready_for_call\\\"}\",\"hidden\",\"\",\"allowCamera\",false,\"allowModals\",false,\"allowPopups\",false,\"iframeCode\",\"\\n\\n\\n\\n \\n Embedded Twilio Dialer for Retool\\n \\n \\n \\n \\n\\n \\n \\n \\n \\n\\n\",\"allowMicrophone\",true,\"allowSameOrigin\",true,\"allowPopupsToEscapeSandbox\",false,\"allowPayment\",false,\"allowGeolocation\",false]],\"^;\",[\"^3\",[]],\"^<\",[\"^0\",[\"^ \",\"n\",\"position2\",\"v\",[\"^ \",\"^=\",\"\",\"^>\",\"body\",\"^?\",\"\",\"row\",1.5999999999999983,\"col\",8,\"^@\",5.000000000000002,\"^A\",4,\"^B\",0]]],\"^C\",null,\"^D\",null,\"^E\",null,\"^=\",\"\",\"^F\",\"~m1656018956100\",\"^G\",\"~m1657759435169\",\"^H\",\"\",\"^I\",null]]],\"welcomeText\",[\"^0\",[\"^ \",\"n\",\"pluginTemplate\",\"v\",[\"^ \",\"id\",\"welcomeText\",\"^4\",\"widget\",\"^5\",\"TextWidget2\",\"^6\",null,\"^7\",null,\"^8\",null,\"^9\",[\"^3\",[\"heightType\",\"auto\",\"horizontalAlign\",\"left\",\"hidden\",false,\"imageWidth\",\"fit\",\"showInEditor\",false,\"verticalAlign\",\"center\",\"_defaultValue\",\"\",\"tooltipText\",\"\",\"value\",\"**Welcome to the Twilio dialer demo! Select a lead from the table or enter a phone number to call below!**\",\"disableMarkdown\",false,\"overflowType\",\"scroll\",\"maintainSpaceWhenHidden\",true]],\"^;\",[\"^3\",[]],\"^<\",[\"^0\",[\"^ \",\"n\",\"position2\",\"v\",[\"^ \",\"^=\",\"\",\"^>\",\"body\",\"^?\",\"\",\"row\",0,\"col\",8,\"^@\",0.6,\"^A\",4,\"^B\",0]]],\"^C\",null,\"^D\",null,\"^E\",null,\"^=\",\"\",\"^F\",\"~m1657130540164\",\"^G\",\"~m1657759435169\",\"^H\",\"\",\"^I\",null]]],\"$main\",[\"^0\",[\"^ \",\"n\",\"pluginTemplate\",\"v\",[\"^ \",\"id\",\"$main\",\"^4\",\"frame\",\"^5\",\"Frame\",\"^6\",null,\"^7\",null,\"^8\",null,\"^9\",[\"^3\",[\"type\",\"main\",\"sticky\",false]],\"^;\",[\"^3\",[]],\"^<\",null,\"^C\",null,\"^D\",null,\"^E\",null,\"^=\",\"\",\"^F\",\"~m1662507702498\",\"^G\",\"~m1662507702498\",\"^H\",\"\",\"^I\",null]]],\"getVoiceTokenError\",[\"^0\",[\"^ \",\"n\",\"pluginTemplate\",\"v\",[\"^ \",\"id\",\"getVoiceTokenError\",\"^4\",\"widget\",\"^5\",\"AlertWidget\",\"^6\",null,\"^7\",null,\"^8\",null,\"^9\",[\"^3\",[\"valueToCopy\",\"\",\"internalUrlHashParams\",\"\",\"hidden\",\"{{!getVoiceToken.error}}\",\"exportFileType\",\"csv\",\"buttonType\",\"action\",\"url\",\"\",\"newWindow\",false,\"exportQuery\",\"\",\"action\",\"\",\"internalUrlQuery\",\"\",\"internalUrlPath\",\"\",\"title\",\"getVoiceToken query failed\",\"type\",\"error\",\"exportFileName\",\"\",\"description\",\"{{getVoiceToken.error}}\",\"buttonText\",\"\"]],\"^;\",[\"^3\",[]],\"^<\",[\"^0\",[\"^ \",\"n\",\"position2\",\"v\",[\"^ \",\"^=\",\"\",\"^>\",\"body\",\"^?\",\"\",\"row\",0,\"col\",1,\"^@\",1,\"^A\",7,\"^B\",0]]],\"^C\",null,\"^D\",null,\"^E\",null,\"^=\",\"\",\"^F\",\"~m1666196786615\",\"^G\",\"~m1666196895174\",\"^H\",\"\",\"^I\",null]]]]],\"^F\",null,\"version\",\"2.97.4\",\"appThemeId\",null,\"preloadedAppJavaScript\",\"$(function () {\\n var device;\\n\\n log(\\\"Requesting Access Token...\\\");\\n // Using a relative link to access the Voice Token function\\n $.getJSON(\\\"./token\\\")\\n .then(function (data) {\\n log(\\\"Got a token.\\\");\\n console.log(\\\"Token: \\\" + data.token);\\n\\n // Setup Twilio.Device\\n device = new Twilio.Device(data.token, {\\n // Set Opus as our preferred codec. Opus generally performs better, requiring less bandwidth and\\n // providing better audio quality in restrained network conditions. Opus will be default in 2.0.\\n codecPreferences: [\\\"opus\\\", \\\"pcmu\\\"],\\n // Use fake DTMF tones client-side. Real tones are still sent to the other end of the call,\\n // but the client-side DTMF tones are fake. This prevents the local mic capturing the DTMF tone\\n // a second time and sending the tone twice. This will be default in 2.0.\\n fakeLocalDTMF: true,\\n // Use `enableRingingState` to enable the device to emit the `ringing`\\n // state. The TwiML backend also needs to have the attribute\\n // `answerOnBridge` also set to true in the `Dial` verb. This option\\n // changes the behavior of the SDK to consider a call `ringing` starting\\n // from the connection to the TwiML backend to when the recipient of\\n // the `Dial` verb answers.\\n enableRingingState: true,\\n debug: true,\\n });\\n\\n device.on(\\\"ready\\\", function (device) {\\n log(\\\"Twilio.Device Ready!\\\");\\n });\\n\\n device.on(\\\"error\\\", function (error) {\\n log(\\\"Twilio.Device Error: \\\" + error.message);\\n });\\n\\n device.on(\\\"connect\\\", function (conn) {\\n log('Successfully established call ! ');\\n $('#modal-call-in-progress').modal('show')\\n });\\n\\n device.on(\\\"disconnect\\\", function (conn) {\\n log(\\\"Call ended.\\\");\\n $('.modal').modal('hide')\\n });\\n\\n })\\n .catch(function (err) {\\n console.log(err);\\n log(\\\"Could not get a token from server!\\\");\\n });\\n\\n // Bind button to make call\\n $('#btnDial').bind('click', function () {\\n $('#modal-dial').modal('hide')\\n\\n // get the phone number to connect the call to\\n var params = {\\n To: document.getElementById(\\\"phoneNumber\\\").value\\n };\\n\\n // output destination number\\n $(\\\"#txtPhoneNumber\\\").text(params.To)\\n \\n\\n console.log(\\\"Calling \\\" + params.To + \\\"...\\\");\\n if (device) {\\n var outgoingConnection = device.connect(params);\\n outgoingConnection.on(\\\"ringing\\\", function () {\\n log(\\\"Ringing...\\\");\\n });\\n }\\n\\n })\\n\\n // Bind button to hangup call\\n\\n $('.btnHangUp').bind('click', function () {\\n $('.modal').modal('hide')\\n log(\\\"Hanging up...\\\");\\n if (device) {\\n device.disconnectAll();\\n }\\n })\\n\\n // Activity log\\n function log(message) {\\n var logDiv = document.getElementById(\\\"log\\\");\\n logDiv.innerHTML += \\\"> \\\" + message + \\\"
\\\";\\n logDiv.scrollTop = logDiv.scrollHeight;\\n }\\n\\n});\\n\",\"preloadedAppJSLinks\",[],\"testEntities\",[],\"tests\",[],\"appStyles\",\"\",\"responsiveLayoutDisabled\",false,\"loadingIndicatorsDisabled\",false,\"urlFragmentDefinitions\",[\"^:\",[]],\"pageLoadValueOverrides\",[\"^:\",[]],\"customDocumentTitle\",\"\",\"customDocumentTitleEnabled\",false,\"customShortcuts\",[],\"isGlobalWidget\",false,\"isMobileApp\",false,\"multiScreenMobileApp\",false,\"folders\",[\"^:\",[]],\"queryStatusVisibility\",true,\"markdownLinkBehavior\",\"auto\",\"inAppRetoolPillAppearance\",\"NO_OVERRIDE\",\"rootScreen\",null,\"instrumentationEnabled\",false,\"experimentalPerfFeatures\",[\"^ \",\"batchCommitModelEnabled\",false,\"skipDepCycleCheckingEnabled\",false,\"serverDepGraphEnabled\",false,\"useRuntimeV2\",false],\"experimentalDataTabEnabled\",false]]]"},"changesRecord":[{"type":"WIDGET_REPOSITION2","payload":{"moves":[{"move":{"col":0,"row":-1,"width":0,"height":0},"moveType":"keyboard","widgetIds":["phoneNumberEntry","callButton","twilioDevice"]}],"largeScreen":true},"hideChangelogEntry":false},{"type":"WIDGET_REPOSITION2","payload":{"moves":[{"move":{"col":0,"row":-1,"width":0,"height":0},"moveType":"keyboard","widgetIds":["phoneNumberEntry","callButton","twilioDevice"]}],"largeScreen":true},"hideChangelogEntry":false},{"type":"WIDGET_REPOSITION2","payload":{"moves":[{"move":{"col":0,"row":-1,"width":0,"height":0},"moveType":"keyboard","widgetIds":["phoneNumberEntry","callButton","twilioDevice"]}],"largeScreen":true},"hideChangelogEntry":false},{"type":"WIDGET_REPOSITION2","payload":{"moves":[{"move":{"col":0,"row":-1,"width":0,"height":0},"moveType":"keyboard","widgetIds":["phoneNumberEntry","callButton","twilioDevice"]}],"largeScreen":true},"hideChangelogEntry":false},{"type":"WIDGET_REPOSITION2","payload":{"moves":[{"move":{"col":0,"row":-1,"width":0,"height":0},"moveType":"keyboard","widgetIds":["phoneNumberEntry","callButton","twilioDevice"]},{"move":{"row":-3,"height":0},"widgetIds":["phoneNumberEntry"]},{"move":{"row":-3,"height":0},"widgetIds":["callButton"]},{"move":{"row":-3,"height":0},"widgetIds":["twilioDevice"]}],"largeScreen":true},"hideChangelogEntry":false}],"gitSha":"49c209d35dd6f6405450a4162a84caddf1b6afd0","checksum":null,"createdAt":"2022-10-19T16:28:30.813Z","updatedAt":"2022-10-19T16:31:43.679Z","pageId":343,"userId":57,"branchId":"c930888a-bbea-48c4-a2e1-536965147038"},"modules":{}}
--------------------------------------------------------------------------------
/applets/twilio-dialer/code/twilio-functions/twiml-app.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Checks if the given value is valid as phone number
3 | * @param {Number|String} number
4 | * @return {Boolean}
5 | */
6 | function isAValidPhoneNumber(number) {
7 | return /^[\d\+\-\(\) ]+$/.test(number);
8 | }
9 |
10 | exports.handler = function (context, event, callback) {
11 | const twiml = new Twilio.twiml.VoiceResponse();
12 |
13 | if (event.To) {
14 | /*
15 | * Wrap the phone number or client name in the appropriate TwiML verb
16 | * if is a valid phone number
17 | */
18 | const attr = isAValidPhoneNumber(event.To) ? 'number' : 'client';
19 |
20 | const dial = twiml.dial({
21 | answerOnBridge: true,
22 | callerId: process.env.CALLER_ID,
23 | });
24 | dial[attr]({}, event.To);
25 | } else {
26 | twiml.say('Thanks for calling!');
27 | }
28 |
29 | callback(null, twiml);
30 | };
31 |
--------------------------------------------------------------------------------
/applets/twilio-dialer/code/twilio-functions/voice-token.js:
--------------------------------------------------------------------------------
1 | exports.handler = function (context, event, callback) {
2 |
3 | const IDENTITY = event.request.headers.identity;
4 |
5 | const { ACCOUNT_SID } = context;
6 |
7 | // set these values in your .env file
8 | const { TWIML_APPLICATION_SID, API_KEY_SID, API_KEY_SECRET } = context;
9 |
10 | const { AccessToken } = Twilio.jwt;
11 | const { VoiceGrant } = AccessToken;
12 |
13 | const accessToken = new AccessToken(ACCOUNT_SID, API_KEY_SID, API_KEY_SECRET);
14 | accessToken.identity = IDENTITY;
15 | const grant = new VoiceGrant({
16 | outgoingApplicationSid: TWIML_APPLICATION_SID,
17 | incomingAllow: true,
18 | });
19 | accessToken.addGrant(grant);
20 |
21 | const response = new Twilio.Response();
22 |
23 | response.appendHeader('Content-Type', 'application/json');
24 | response.setBody({
25 | identity: IDENTITY,
26 | token: accessToken.toJwt(),
27 | });
28 | callback(null, response);
29 | };
30 |
--------------------------------------------------------------------------------
/applets/twilio-dialer/docs/retool-setup.md:
--------------------------------------------------------------------------------
1 | # Retool Setup
2 |
3 | Make sure you've completed the steps described in the [Twilio Setup](./twilio-setup.md) documentation before proceeding!
4 |
5 | ## 1. Create a Retool REST API Resource to retrieve voice tokens
6 |
7 | In order for your Retool app to authenticate with Twilio, you'll need to get a valid voice token. To set this up:
8 |
9 | 1. Navigate to the `/resources` page in your instance and click "Create a new resource."
10 | 2. Select REST API as the resource type.
11 | 3. Name the resource “Twilio Voice Token.”
12 | 4. Specify the URL of your `voice-token` function as noted in step six of the Twilio setup.
13 | 5. Specify a header with a key of `identity` and a value of `{{current_user.email}}`. This is used to pass the email address of the Retool user to Twilio for use in the token identity.
14 | 6. Save your changes.
15 |
16 | ## 2. Download the Retool app code
17 |
18 | Download [twilio-dialer-retool-app.json](/applets/twilio-dialer/code/retool/twilio-dialer-retool-app.json) from this repository.
19 |
20 | ## 3. Import the Retool app code
21 | On the [Retool main page](https://docs.retool.com/docs/protected-applications-getting-started#importing-the-application), click `Create new` and select `Import an app`. Upload the JSON file containing the app code, and name the app.
22 |
23 | You may need to connect the `getVoiceToken` Resource Query to the REST API Resource you created previously.
24 |
25 | ## 4. Explore the Retool app
26 |
27 | If everything is working as expected, you should now be able to place outbound calls from the Retool app! The app is set up with a hardcoded table of "leads" to simulate a simple call center app. You can edit the contents of the leads table directly, or simply type your phone number in the field to try calling yourself.
28 |
29 | ## 5. Learn about how the custom component works
30 |
31 | You can read more about [Retool custom components](https://docs.retool.com/docs/custom-components). You can also view the code used in the Twilio Dialer component in Retool, the contents of which are included in [retool-custom-component.html](/applets/twilio-dialer/code/retool/retool-custom-component.html) for easy reading!
32 |
33 | Also, take 18 minutes to check out our [video walkthrough](https://youtu.be/xPSS3kp3XEI) of how this app is put together!
--------------------------------------------------------------------------------
/applets/twilio-dialer/docs/twilio-setup.md:
--------------------------------------------------------------------------------
1 | # Twilio Setup
2 | Before you can get this Retool app working, you'll need to do some pre-work in Twilio.
3 |
4 | ## 0. Sign up for a Twilio account and get a phone number
5 |
6 | If you don't already have a Twilio account, you'll need to get one! The good news is that Twilio makes it easy (and free!) to get started. Just head over to https://www.twilio.com/try-twilio and sign up!
7 |
8 | As you go through the onboarding flow, indicate that you'll be building a voice app. Once you're logged into Twilio console, you'll be prompted to get a Twilio phone number. Note the phone number you were assigned for later!
9 |
10 | ## 1. Create a TwiML App
11 |
12 | A [TwiML Application](https://www.twilio.com/docs/usage/api/applications) tells Twilio how to handle the calls you'll make from your Retool app. Specify a name for your TwiML app and note the `TwiML App SID` for later. Leave all other settings blank, we'll fill them in later on!
13 |
14 | ## 2. Create an API key/secret pair
15 |
16 | [Create an API key](https://www.twilio.com/docs/iam/keys/api-key-resource) and note the key's `SID` and `Secret` for later.
17 |
18 | ## 3. Create a Twilio Service
19 |
20 | A [Twilio Service](https://www.twilio.com/docs/runtime/functions/create-service) is a container for the server-less functions which make it possible for your Retool app to get a voice access token, and for outbound calls to be routed correctly.
21 |
22 | ## 4. Set Twilio Service environment variables
23 |
24 | In your new Twilio Service, set the following environment variables:
25 |
26 | | Key | Value |
27 | | ---- | ---- |
28 | | `TWIML_APPLICATION_SID` | The `TwiML App SID` you noted in step one |
29 | | `API_KEY_SID` | The `API Key SID` you noted in step two |
30 | | `API_KEY_SECRET` | The `API Key Secret` you noted in step two |
31 | | `CALLER_ID` | The phone number you noted in step zero. Make sure to include a `+` and the country code at the beginning! |
32 |
33 | ## 5. Configure Twilio Service dependencies
34 |
35 | The server-less functions running in Twilio need access to a few software libraries to function properly. Import the following NPM modules:
36 |
37 | | Module | Version |
38 | | --- | --- |
39 | | `lodash` | `4.17.11` |
40 | | `@twilio/runtime-handler` | `1.2.3` |
41 | | `twilio` | `^3.77.3` |
42 | | `xmldom` | `0.1.27` |
43 | | `util` | `0.11.0` |
44 |
45 | ## 6. Create a function to issue voice tokens
46 |
47 | In order for the embedded Twilio dialer to authenticate with Twilio's services, it needs a valid access token. To create a function for issuing voice tokens:
48 |
49 | 1. Add a function
50 | 2. Specify a path `voice-token`
51 | 3. Set the function to `Public`.
52 | - **Heads up! This means that anyone who can guess the URL of your function can issue valid access tokens. You would need to implement authentication on this endpoint for a production use-case!**
53 | 4. Copy and paste the contents of [voice-token.js](/applets/twilio-dialer/code/twilio-functions/voice-token.js) into the function and save
54 | 5. Copy the URL for this function for later use
55 |
56 | ## 7. Create a function to route outbound calls
57 |
58 | In order for Twilio to know what to do when it receives a call from your app, you need a call routing app. To create a call routing function:
59 |
60 | 1. Add a function
61 | 2. Specify a path `twiml-app`
62 | 3. Set the function to `Protected`
63 | 4. Copy and paste the contents of [twiml-app.js](/applets/twilio-dialer/code/twilio-functions/twiml-app.js) into the function and save
64 | 5. Copy the URL for this function for later use
65 |
66 | ## 8. Deploy your Twilio Service!
67 |
68 | This finalizes all of the changes you've made.
69 |
70 | ## 9. Finish configuring your TwiML app
71 |
72 | Remember that empty TwiML app we created in step one? Edit the app settings and provide the URL for your `twiml-app` function as the value of the **Voice Configuration Request URL**. Specify a **Request Method** of `HTTP POST` and save your changes.
73 |
74 | This step connects inbound requests from your Retool app to the call routing function.
75 |
76 | ## Next steps
77 | Next, set up the Retool app to make calls using your Twilio service!
78 |
79 | [Retool Setup](./retool-setup.md)
--------------------------------------------------------------------------------
/applets/twilio-dialer/images/twilio-dialer.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tryretool/retool-app-exchange/ea48efde500e831b0435a0e7ae1e28d1e5021e83/applets/twilio-dialer/images/twilio-dialer.png
--------------------------------------------------------------------------------
/embed-url-generator/README.md:
--------------------------------------------------------------------------------
1 | # Embed URL Generator / Embedded App Tester
2 |
3 | If you are using [Retool Embed](https://docs.retool.com/docs/retool-embed), we've created a Retool application that lets you preview how your embedded Retool app will look and behave within your parent application.
4 |
5 | **Note**: We recommend using this application in *Incognito Mode* in your browser, to prevent conflicts between the sample embedded user and your original Retool login. We also recommend reading the docs linked above before using, especially the parts about passing in [metadata.](https://docs.retool.com/docs/embed-retool-apps#control-access-and-app-behavior-with-metadata)
6 |
7 |
8 |
9 | ## Setup
10 | The instructions below are outlined within the Retool application. You can import the app and continue in Retool, or follow along below. Once set up, skip to Step 6 to begin using the application.
11 |
12 | ### 0. Make sure you have the proper flags enabled
13 |
14 | This is only for customers who have [Retool Embed](https://retool.com/products/embed) enabled. If you don't have it, but are interested in learning more, use [this link](https://retool.com/products/embed) to book a demo with our team.
15 |
16 | ### 1. Set up API with Embed Scope
17 |
18 | You'll need to create a Retool API token with the `Embed ` Scope. You can access this page by going to your `settings`. Note that you must be an Admin on your instance to set this up. See screenshot below and link to our documentation [here](https://docs.retool.com/docs/embed-retool-apps#1-generate-an-access-token). Save the token to use in the next step.
19 |
20 |
21 |
22 | ## 2. Connect the Retool API as a Resource
23 |
24 | 1. Navigate to the `/resources` page in your instance and click "Create a new resource"
25 | 2. Select REST API as the resource type
26 | 3. Name the resource “Embed URL Generator”
27 | 4. Insert per the below screenshot, updating `` with your Retool domain (eg retool.yourdomain.com) and `` with the token from the prior step:
28 |
29 |
30 |
31 | Save the resource, and it should be available for use by your apps.
32 |
33 | ### 3a. Download app code
34 | Download the app code from the `/code` directory in this repository.
35 |
36 | To do this, clone this Github repo: `git clone https://github.com/tryretool/retool-app-exchange.git`
37 |
38 | (Alternatively, you can manually download `embed_url_generator.json` from the [`code/`](../code) directory.)
39 |
40 | ### 3b. Import the app code
41 | On the [Retool main page](https://docs.retool.com/docs/protected-applications-getting-started#importing-the-application), click `Create new` and select `Import an app`. Upload the JSON file containing the app code, and name the app.
42 |
43 | ### 3c. Rewire the Resource for the postEmbedURL query
44 | Within the app, update the `postEmbedURL` query to point at the resource you created in Step 2. See screenshot below:
45 |
46 |
47 |
48 | ### 4. Get App UUID
49 | Now the Embed URL generator application is set up. To test out embedding an app, we need to find another application within your instance you'd like to use. Navigate to that other application and grab the UUID by either copying it from your app URL or by using the [command palette](https://docs.retool.com/docs/command-palette). See screenshot below for the command palette:
50 |
51 |
52 |
53 | ### 5. Get Group ID
54 | Now that we have an app to embed, we need to pass in a Retool permission group to the API call. You can either use an existing group or create a new group. To learn more about permission groups and how they work with embedded apps, see our docs [here](https://docs.retool.com/docs/embed-retool-apps#2-create-permission-groups-for-your-users).
55 |
56 |
57 |
58 | ### 6. Use the app!
59 | Now we are ready to test out embedded apps! You can use the form on the left of the app under `Submit Request Form` to input different parameters, the JSON on the right under `Submit Request Body` is a preview of what is getting passed to the API in the body, and below the form you can toggle "Preview in Iframe" on to show the app in an iframe below the page, or keep it off to provide the generated embed URL as a link.
60 |
61 | **Note**: Once you have gone through these steps, toggle in the `Setup Complete` tab in the top right of your applciation to navigate to that testing step automatically in the future.
62 |
63 |
64 | ## How to contribute
65 | Please open a Github Issue on this repo, and let us know about your interest in contributing! We encourage you to reach out before you get started building to get early feedback.
66 |
--------------------------------------------------------------------------------
/embed-url-generator/images/1_create_api_key.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tryretool/retool-app-exchange/ea48efde500e831b0435a0e7ae1e28d1e5021e83/embed-url-generator/images/1_create_api_key.png
--------------------------------------------------------------------------------
/embed-url-generator/images/2_add_api_generator_as_resource.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tryretool/retool-app-exchange/ea48efde500e831b0435a0e7ae1e28d1e5021e83/embed-url-generator/images/2_add_api_generator_as_resource.png
--------------------------------------------------------------------------------
/embed-url-generator/images/3_update_app_with_new_resource.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tryretool/retool-app-exchange/ea48efde500e831b0435a0e7ae1e28d1e5021e83/embed-url-generator/images/3_update_app_with_new_resource.jpeg
--------------------------------------------------------------------------------
/embed-url-generator/images/4_copy_app_uuid.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tryretool/retool-app-exchange/ea48efde500e831b0435a0e7ae1e28d1e5021e83/embed-url-generator/images/4_copy_app_uuid.jpeg
--------------------------------------------------------------------------------
/embed-url-generator/images/5_copy_group_id.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tryretool/retool-app-exchange/ea48efde500e831b0435a0e7ae1e28d1e5021e83/embed-url-generator/images/5_copy_group_id.gif
--------------------------------------------------------------------------------
/embed-url-generator/images/6_embed_url_app.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tryretool/retool-app-exchange/ea48efde500e831b0435a0e7ae1e28d1e5021e83/embed-url-generator/images/6_embed_url_app.png
--------------------------------------------------------------------------------
/incident-central/README.md:
--------------------------------------------------------------------------------
1 | # Incident Central - An incident response hub for engineering teams
2 |
3 |
4 |
5 |
6 |
7 | ## Why you need this
8 | In any engineering organization, things will go wrong. No matter what kind of product you're building, what size you are, or how hard you try, something will break.
9 |
10 | You'll want to develop a robust incident response process, as well as supporting tools to empower people to use the process. For example, a tool can make it easy to report an incident, alert the right people to respond, and give responders a communication channel to collaborate to solve the problem.
11 |
12 | In addition, you want to be able to get a bird's-eye view of incidents—one place you can go to see a list of all open incidents. You also want to be able to find past incidents, in order to review them, reflect on them, and improve.
13 |
14 | ## What does Incident Central do?
15 | Incident Central is a ready-made app that lets you do these things:
16 | 1. Anyone at your company (engineer or not) can report an incident via a simple user interface. When an incident is reported through Incident Central, the following things will happen:
17 | - A unique human-readable incident name will be generated for the incident.
18 | - The PagerDuty on-call rotation for the problematic service will be triggered, thus alerting the on-call.
19 | - A Slack channel for incident response will be created.
20 | - The information above about the incident will be written to a database that you provide, so that you have a means to look up current and past incidents.
21 |
22 | 2. You can see all open incidents, and quickly navigate to the relevant incident Slack channel.
23 | 3. You can drill into a specific incident, and edit its metadata.
24 |
25 | In addition, you can build out any other incident-related functionality you want. (For example, you might decide that you want to also create a Jira ticket every time an incident is opened.)
26 |
27 | In the future, we will likely add a page that displays aggregate incident metrics. Stay tuned.
28 |
29 | ## Screenshots
30 |
31 |
32 |  |
33 |  |
34 |
35 |
36 |  |
37 |  |
38 |
39 |
40 |
41 | ### Who is this tool for?
42 | You likely want everyone (or most people) at your company to be able to report an incident. Thus, the Report Incident UI within Incident Central is designed to be used by anyone at your company.
43 |
44 | Members of the engineering team will likely use the other functionality: looking up, editing, and reviewing incident data.
45 |
46 | ### Where does this tool fits into the incident response process?
47 | There are five main steps in an incident management process [0]:
48 | 1. Triage
49 | 2. Coordinate
50 | 3. Mitigate
51 | 4. Resolve
52 | 5. Follow-Up
53 |
54 | In Incident Central, the "Report Incident" UI will help kick off Step 1: "Triage", by alerting the right people. It will also set up a communication channel for people to do Step 2: "Coordinate."
55 |
56 | During an incident ("Coordinate"-"Resolve"), people can go to Incident Central to find the link to the incident communication channel and other metadata.
57 |
58 | After an incident, you can use Incident Central to look up information about the incident, as part of Step 5: "Follow-Up."
59 |
60 |
61 | ## Technical Details and Setup
62 | ### Dependencies - backends
63 | The initial version of Incident Central depends on the following backend services:
64 | * Slack
65 | * PagerDuty
66 | * A database (e.g. PostgreSQL or MySQL)
67 |
68 | You are welcome to adapt Incident Central to an alternative backend (for example, perhaps Microsoft Teams instead of Slack). We encourage you to submit your modified version of the app here for others to use. See the **"How to contribute"** section below.
69 |
70 | ### How to set up Incident Central in your Retool instance
71 | See the detailed setup guides in the [Setup Guides folder](./setup-guides).
72 |
73 | As an overview, these guides will take you through:
74 |
75 | 1. Setting up backend dependencies.
76 | - Configuring your Slack and PagerDuty instances.
77 | - Creating a relational database, if you don't have one already.
78 | 2. Setting up these backends as Resources in Retool.
79 | 3. Downloading and configuring the Retool app files.
80 |
81 |
82 | ## What's next?
83 | ### Ideas for extending this app
84 | * Do you use a different chat application instead of Slack? You can swap the Slack REST API calls in this app for calls to your chat application instead.
85 | * Do you want other things to happen when someone reports an incident? (Perhaps, you want a Jira ticket to be created, and a Google Doc to be spun up?) You can extend the Report Incident page to also do these things (e.g. by adding calls to Jira and Google APIs.)
86 |
87 | ### How to contribute
88 | Please open a Github Issue on this repo, and let us know about your interest in contributing! We encourage you to reach out before you get started building to get early feedback.
89 |
90 | ## Appendix
91 |
92 | ### Learn more about incident response process and tooling
93 | As part of the research in building this app, we interviewed an engineer who built incident response tools at Stripe. Check out the blog post: [From checklist to service: scaling Stripe’s incident response](https://retool.com/blog/incident-response-tools-stripe/)
94 |
95 | ### Citations
96 | [0] Five main steps in an incident management process: [Increment magazine](https://increment.com/on-call/when-the-pager-goes-off/)
97 |
--------------------------------------------------------------------------------
/incident-central/code/Incident-Central-Home.json:
--------------------------------------------------------------------------------
1 | {"uuid":"d2b883fc-4fc0-11ec-8282-5311b8afe0de","page":{"id":55925865,"data":{"appState":"[\"~#iR\",[\"^ \",\"n\",\"appTemplate\",\"v\",[\"^ \",\"isFetching\",false,\"plugins\",[\"~#iOM\",[\"combinedIncidents\",[\"^0\",[\"^ \",\"n\",\"pluginTemplate\",\"v\",[\"^ \",\"id\",\"combinedIncidents\",\"type\",\"function\",\"subtype\",\"Function\",\"resourceName\",null,\"resourceDisplayName\",null,\"template\",[\"^3\",[\"funcBody\",\"// Combine the PagerDuty and DB data. These should contain the same incidents.\\nconst pdIncidents = {{getPDIncidents.data}};\\nconst dbIncidents = {{getDBIncidents.data}};\\n\\nconst dbIncidentsIdToData = {};\\nformatDataAsArray(dbIncidents).forEach((dbIncident) => {\\n dbIncidentsIdToData[dbIncident.id] = dbIncident;\\n});\\n\\nconst combinedIdToData = {};\\npdIncidents.forEach((pdIncident) => {\\n combinedIdToData[pdIncident.incident_key] = {\\n pd: pdIncident,\\n db: dbIncidentsIdToData[pdIncident.incident_key], \\n }\\n});\\nreturn combinedIdToData;\",\"value\",\"\"]],\"style\",null,\"position2\",null,\"mobilePosition2\",null,\"mobileAppPosition\",null,\"tabIndex\",null,\"createdAt\",\"~m1639619882296\",\"updatedAt\",\"~m1639620866859\",\"container\",\"\",\"folder\",\"filtered_data\",\"screen\",null]]],\"selectedStart\",[\"^0\",[\"^ \",\"n\",\"pluginTemplate\",\"v\",[\"^ \",\"id\",\"selectedStart\",\"^4\",\"function\",\"^5\",\"Function\",\"^6\",null,\"^7\",null,\"^8\",[\"^3\",[\"funcBody\",\"// Returns the start date in YYYY-MM-DD format.\\nconst shouldUseCustomDateRange = {{useCustomDatesToggle.value}};\\nif (shouldUseCustomDateRange) {\\n return {{dateRangeInput.value.start}};\\n} else {\\n const end = moment();\\n const numDays = parseInt({{daysSelect.value}});\\n const start = moment().subtract(numDays, 'd');\\n return start;\\n}\",\"value\",\"\"]],\"^9\",null,\"^:\",null,\"^;\",null,\"^<\",null,\"^=\",null,\"^>\",\"~m1639606388980\",\"^?\",\"~m1643594693993\",\"^@\",\"\",\"^A\",\"user_filters\",\"^B\",null]]],\"displayedIncidents\",[\"^0\",[\"^ \",\"n\",\"pluginTemplate\",\"v\",[\"^ \",\"id\",\"displayedIncidents\",\"^4\",\"function\",\"^5\",\"Function\",\"^6\",null,\"^7\",null,\"^8\",[\"^3\",[\"funcBody\",\"const allIncidents = {{combinedIncidents.value}};\\n\\n// Date range filtering is done in the original data fetch.\\n// We need to do the Status and Priority filters ourselves, because PagerDuty's List Incidents API does \\n// not let us filter by these properties.\\nconst showActive = {{statusCheckboxes.value.includes('Active')}};\\nconst showResolved = {{statusCheckboxes.value.includes('Resolved')}};\\nconst shownPriorities = new Set({{selectedPriorities.value}});\\n\\nreturn Object.values(allIncidents).filter((incident, i) => {\\n if (!showActive && PAGERDUTY.hasActiveStatus(incident.pd)) {\\n return false;\\n }\\n if (!showResolved && !PAGERDUTY.hasActiveStatus(incident.pd)) {\\n return false;\\n }\\n if (incident.pd.priority?.id && !shownPriorities.has(incident.pd.priority.id)) {\\n return false;\\n }\\n return true;\\n});\",\"value\",\"\"]],\"^9\",null,\"^:\",null,\"^;\",null,\"^<\",null,\"^=\",null,\"^>\",\"~m1639063060804\",\"^?\",\"~m1641854246752\",\"^@\",\"\",\"^A\",\"filtered_data\",\"^B\",null]]],\"getDBIncidents\",[\"^0\",[\"^ \",\"n\",\"pluginTemplate\",\"v\",[\"^ \",\"id\",\"getDBIncidents\",\"^4\",\"datasource\",\"^5\",\"SqlQueryUnified\",\"^6\",\"d9ddb8f4-0a90-45a2-aac2-3185c7341752\",\"^7\",\"Incident Database\",\"^8\",[\"^3\",[\"queryRefreshTime\",\"\",\"records\",\"\",\"lastReceivedFromResourceAt\",null,\"databasePasswordOverride\",\"\",\"queryDisabledMessage\",\"\",\"successMessage\",\"\",\"queryDisabled\",\"\",\"playgroundQuerySaveId\",\"latest\",\"resourceNameOverride\",\"\",\"runWhenModelUpdates\",true,\"showFailureToaster\",true,\"query\",\"select\\n\\tid,\\n\\tcreated_at,\\n pagerduty_id,\\n\\tpagerduty_url,\\n\\tslack_channel_name,\\n slack_channel_url,\\n slack_channel_id\\nfrom incidents\\nwhere created_at >= {{selectedStart.value}}\\n and created_at <= {{selectedEnd.value}}\\norder by created_at desc;\",\"playgroundQueryUuid\",\"\",\"playgroundQueryId\",null,\"privateParams\",[\"~#iL\",[]],\"runWhenPageLoadsDelay\",\"\",\"warningCodes\",[\"^C\",[]],\"data\",null,\"recordId\",\"\",\"importedQueryInputs\",[\"^3\",[]],\"isImported\",false,\"showSuccessToaster\",false,\"dataArray\",[\"^C\",[]],\"cacheKeyTtl\",\"\",\"filterBy\",\"\",\"databaseHostOverride\",\"\",\"metadata\",null,\"editorMode\",\"sql\",\"actionType\",\"\",\"changesetObject\",\"\",\"shouldUseLegacySql\",false,\"errorTransformer\",\"// The variable 'data' allows you to reference the request's data in the transformer. \\n// example: return data.find(element => element.isError)\\nreturn data.error\",\"databaseNameOverride\",\"\",\"confirmationMessage\",null,\"isFetching\",false,\"changeset\",\"\",\"rawData\",null,\"queryTriggerDelay\",\"0\",\"watchedParams\",[\"^C\",[]],\"enableErrorTransformer\",false,\"enableBulkUpdates\",false,\"showLatestVersionUpdatedWarning\",false,\"timestamp\",0,\"importedQueryDefaults\",[\"^3\",[]],\"enableTransformer\",false,\"showUpdateSetValueDynamicallyToggle\",true,\"bulkUpdatePrimaryKey\",\"\",\"runWhenPageLoads\",false,\"transformer\",\"// type your code here\\n// example: return formatDataAsArray(data).filter(row => row.quantity > 20)\\nreturn data\",\"events\",[\"^C\",[[\"^3\",[\"event\",\"success\",\"type\",\"datasource\",\"method\",\"trigger\",\"pluginId\",\"getPDIncidents\",\"params\",[\"^3\",[]],\"waitType\",\"debounce\",\"waitMs\",\"0\"]]]],\"tableName\",\"\",\"queryTimeout\",\"10000\",\"requireConfirmation\",false,\"queryFailureConditions\",\"\",\"changesetIsObject\",false,\"enableCaching\",false,\"allowedGroups\",[\"^C\",[]],\"databaseUsernameOverride\",\"\",\"shouldEnableBatchQuerying\",false,\"doNotThrowOnNoOp\",false,\"queryThrottleTime\",\"750\",\"updateSetValueDynamically\",false,\"notificationDuration\",\"\"]],\"^9\",null,\"^:\",null,\"^;\",null,\"^<\",null,\"^=\",null,\"^>\",\"~m1639606259563\",\"^?\",\"~m1643594911891\",\"^@\",\"\",\"^A\",\"database\",\"^B\",null]]],\"helper_getOnePageOfPDIncidents\",[\"^0\",[\"^ \",\"n\",\"pluginTemplate\",\"v\",[\"^ \",\"id\",\"helper_getOnePageOfPDIncidents\",\"^4\",\"datasource\",\"^5\",\"RESTQuery\",\"^6\",\"8ac5f7df-7b87-4df1-b6f0-e622b75a8bdd\",\"^7\",\"PagerDuty API for Incident Central\",\"^8\",[\"^3\",[\"queryRefreshTime\",\"\",\"paginationLimit\",\"\",\"body\",\"\",\"lastReceivedFromResourceAt\",null,\"queryDisabledMessage\",\"\",\"successMessage\",\"\",\"queryDisabled\",\"\",\"playgroundQuerySaveId\",\"latest\",\"resourceNameOverride\",\"\",\"runWhenModelUpdates\",false,\"paginationPaginationField\",\"\",\"headers\",\"[]\",\"showFailureToaster\",true,\"paginationEnabled\",false,\"query\",\"incidents?limit={{limit}}&offset={{offset}}&sort_by=created_at:desc&since={{sinceDate}}&until={{untilDate}}\",\"playgroundQueryUuid\",\"\",\"playgroundQueryId\",null,\"privateParams\",[\"^C\",[]],\"runWhenPageLoadsDelay\",\"\",\"data\",null,\"importedQueryInputs\",[\"^3\",[]],\"isImported\",false,\"showSuccessToaster\",false,\"cacheKeyTtl\",\"\",\"cookies\",\"\",\"metadata\",null,\"changesetObject\",\"\",\"errorTransformer\",\"// The variable 'data' allows you to reference the request's data in the transformer. \\n// example: return data.find(element => element.isError)\\nreturn data.error\",\"confirmationMessage\",null,\"isFetching\",false,\"changeset\",\"\",\"rawData\",null,\"queryTriggerDelay\",\"0\",\"watchedParams\",[\"^C\",[]],\"enableErrorTransformer\",false,\"showLatestVersionUpdatedWarning\",false,\"paginationDataField\",\"\",\"timestamp\",0,\"importedQueryDefaults\",[\"^3\",[]],\"enableTransformer\",false,\"showUpdateSetValueDynamicallyToggle\",true,\"runWhenPageLoads\",false,\"transformer\",\"\",\"events\",[\"^C\",[]],\"queryTimeout\",\"10000\",\"requireConfirmation\",false,\"type\",\"GET\",\"queryFailureConditions\",\"\",\"changesetIsObject\",false,\"enableCaching\",false,\"allowedGroups\",[\"^C\",[]],\"bodyType\",\"json\",\"queryThrottleTime\",\"750\",\"updateSetValueDynamically\",false,\"notificationDuration\",\"\"]],\"^9\",null,\"^:\",null,\"^;\",null,\"^<\",null,\"^=\",null,\"^>\",\"~m1638971262353\",\"^?\",\"~m1643587771167\",\"^@\",\"\",\"^A\",\"pagerduty\",\"^B\",null]]],\"getPagerDutyIncidentPriorities\",[\"^0\",[\"^ \",\"n\",\"pluginTemplate\",\"v\",[\"^ \",\"id\",\"getPagerDutyIncidentPriorities\",\"^4\",\"datasource\",\"^5\",\"RESTQuery\",\"^6\",\"8ac5f7df-7b87-4df1-b6f0-e622b75a8bdd\",\"^7\",\"PagerDuty API for Incident Central\",\"^8\",[\"^3\",[\"queryRefreshTime\",\"\",\"paginationLimit\",\"\",\"body\",\"\",\"lastReceivedFromResourceAt\",null,\"queryDisabledMessage\",\"\",\"successMessage\",\"\",\"queryDisabled\",\"\",\"playgroundQuerySaveId\",\"latest\",\"resourceNameOverride\",\"\",\"runWhenModelUpdates\",true,\"paginationPaginationField\",\"\",\"headers\",\"[]\",\"showFailureToaster\",true,\"paginationEnabled\",false,\"query\",\"priorities\",\"playgroundQueryUuid\",\"\",\"playgroundQueryId\",null,\"privateParams\",[\"^C\",[]],\"runWhenPageLoadsDelay\",\"\",\"data\",null,\"importedQueryInputs\",[\"^3\",[]],\"isImported\",false,\"showSuccessToaster\",false,\"cacheKeyTtl\",\"\",\"cookies\",\"\",\"metadata\",null,\"changesetObject\",\"\",\"errorTransformer\",\"// The variable 'data' allows you to reference the request's data in the transformer. \\n// example: return data.find(element => element.isError)\\nreturn data.error\",\"confirmationMessage\",null,\"isFetching\",false,\"changeset\",\"\",\"rawData\",null,\"queryTriggerDelay\",\"0\",\"watchedParams\",[\"^C\",[]],\"enableErrorTransformer\",false,\"showLatestVersionUpdatedWarning\",false,\"paginationDataField\",\"\",\"timestamp\",0,\"importedQueryDefaults\",[\"^3\",[]],\"enableTransformer\",true,\"showUpdateSetValueDynamicallyToggle\",true,\"runWhenPageLoads\",false,\"transformer\",\"return data.priorities;\",\"events\",[\"^C\",[]],\"queryTimeout\",\"10000\",\"requireConfirmation\",false,\"type\",\"GET\",\"queryFailureConditions\",\"\",\"changesetIsObject\",false,\"enableCaching\",false,\"allowedGroups\",[\"^C\",[]],\"bodyType\",\"json\",\"queryThrottleTime\",\"750\",\"updateSetValueDynamically\",false,\"notificationDuration\",\"\"]],\"^9\",null,\"^:\",null,\"^;\",null,\"^<\",null,\"^=\",null,\"^>\",\"~m1643587695025\",\"^?\",\"~m1643587712140\",\"^@\",\"\",\"^A\",\"pagerduty\",\"^B\",null]]],\"listView1\",[\"^0\",[\"^ \",\"n\",\"pluginTemplate\",\"v\",[\"^ \",\"id\",\"listView1\",\"^4\",\"widget\",\"^5\",\"ListViewWidget\",\"^6\",null,\"^7\",null,\"^8\",[\"^3\",[\"showBorder\",false,\"data\",\"\",\"showDropShadow\",false,\"scroll\",true,\"rowKeys\",\"{{displayedIncidents.value.map(i => i.db.id)}}\",\"formDataKey\",\"{{ self.id }}\",\"style\",[\"^3\",[\"accent-background\",\"hsl(208, 70%, 55%)\",\"primary-surface\",\"\"]],\"dynamicHeightsEnabled\",true,\"itemHeight\",\"3\",\"instances\",\"{{displayedIncidents.value.length}}\"]],\"^9\",[\"^3\",[]],\"^:\",[\"^0\",[\"^ \",\"n\",\"position2\",\"v\",[\"^ \",\"^@\",\"\",\"rowGroup\",\"body\",\"subcontainer\",\"\",\"row\",-8.215650382226158e-15,\"col\",3,\"height\",22.600000000000005,\"width\",9,\"tabNum\",0]]],\"^;\",null,\"^<\",null,\"^=\",null,\"^>\",\"~m1638473334211\",\"^?\",\"~m1639621504177\",\"^@\",\"\",\"^A\",\"\",\"^B\",null]]],\"container1\",[\"^0\",[\"^ \",\"n\",\"pluginTemplate\",\"v\",[\"^ \",\"id\",\"container1\",\"^4\",\"widget\",\"^5\",\"ContainerWidget2\",\"^6\",null,\"^7\",null,\"^8\",[\"^3\",[\"_disabledByIndex\",[\"^C\",[\"\"]],\"selectedView\",\"View 1\",\"heightType\",\"auto\",\"currentViewKey\",\"\",\"iconByIndex\",[],\"clickable\",false,\"_viewLabels\",[\"^C\",[]],\"_iconByIndex\",[\"^C\",[\"\"]],\"viewLabels\",[],\"hidden\",false,\"showHeader\",false,\"hoistFetching\",true,\"views\",[],\"showInEditor\",false,\"tooltipText\",\"\",\"style\",[\"^3\",[\"background\",\"{{COLOR.bkgrnd_gray}}\",\"border\",\"{{COLOR.border_green}}\",\"borderRadius\",\"5px\"]],\"hiddenByIndex\",[],\"_hiddenByIndex\",[\"^C\",[\"\"]],\"currentViewIndex\",null,\"_hasMigratedNestedItems\",true,\"transition\",\"none\",\"itemMode\",\"static\",\"_tooltipByIndex\",[\"^C\",[\"\"]],\"tooltipByIndex\",[],\"showFooter\",false,\"_viewKeys\",[\"^C\",[\"View 1\"]],\"events\",[\"^3\",[]],\"_ids\",[\"^C\",[\"\"]],\"viewKeys\",[],\"iconPositionByIndex\",[],\"_iconPositionByIndex\",[\"^C\",[\"\"]],\"loading\",false,\"overflowType\",\"scroll\",\"_labels\",[\"^C\",[\"\"]],\"styleContext\",[\"^3\",[\"textDark\",\"\"]],\"disabledByIndex\",[],\"maintainSpaceWhenHidden\",false,\"labels\",[]]],\"^9\",[\"^3\",[]],\"^:\",[\"^0\",[\"^ \",\"n\",\"position2\",\"v\",[\"^ \",\"^@\",\"listView1\",\"^D\",\"body\",\"^E\",\"\",\"row\",0,\"col\",0,\"^F\",6,\"^G\",12,\"^H\",0]]],\"^;\",null,\"^<\",null,\"^=\",null,\"^>\",\"~m1638473393858\",\"^?\",\"~m1639348307890\",\"^@\",\"\",\"^A\",\"\",\"^B\",null]]],\"containerTitle3\",[\"^0\",[\"^ \",\"n\",\"pluginTemplate\",\"v\",[\"^ \",\"id\",\"containerTitle3\",\"^4\",\"widget\",\"^5\",\"TextWidget2\",\"^6\",null,\"^7\",null,\"^8\",[\"^3\",[\"heightType\",\"auto\",\"horizontalAlign\",\"left\",\"hidden\",false,\"imageWidth\",\"fit\",\"showInEditor\",false,\"verticalAlign\",\"center\",\"tooltipText\",\"\",\"value\",\"###### Container title\",\"disableMarkdown\",false,\"overflowType\",\"scroll\",\"maintainSpaceWhenHidden\",false]],\"^9\",[\"^3\",[]],\"^:\",[\"^0\",[\"^ \",\"n\",\"position2\",\"v\",[\"^ \",\"^@\",\"container1\",\"^D\",\"header\",\"^E\",\"\",\"row\",0,\"col\",0,\"^F\",0.6,\"^G\",12,\"^H\",0]]],\"^;\",null,\"^<\",null,\"^=\",null,\"^>\",\"~m1638473394207\",\"^?\",\"~m1638909430659\",\"^@\",\"\",\"^A\",\"\",\"^B\",null]]],\"text3\",[\"^0\",[\"^ \",\"n\",\"pluginTemplate\",\"v\",[\"^ \",\"id\",\"text3\",\"^4\",\"widget\",\"^5\",\"TextWidget2\",\"^6\",null,\"^7\",null,\"^8\",[\"^3\",[\"heightType\",\"auto\",\"horizontalAlign\",\"left\",\"hidden\",false,\"imageWidth\",\"fit\",\"showInEditor\",false,\"verticalAlign\",\"center\",\"tooltipText\",\"\",\"value\",\"#### [{{displayedIncidents.value[i].pd?.priority?.name}}] {{displayedIncidents.value[i].db.id}}\",\"style\",[\"^3\",[\"color\",\"\"]],\"disableMarkdown\",false,\"overflowType\",\"scroll\",\"maintainSpaceWhenHidden\",false]],\"^9\",[\"^3\",[]],\"^:\",[\"^0\",[\"^ \",\"n\",\"position2\",\"v\",[\"^ \",\"^@\",\"container1\",\"^D\",\"body\",\"^E\",\"\",\"row\",0.8,\"col\",2,\"^F\",0.6,\"^G\",9,\"^H\",0]]],\"^;\",null,\"^<\",null,\"^=\",null,\"^>\",\"~m1638970252749\",\"^?\",\"~m1639623910265\",\"^@\",\"\",\"^A\",\"\",\"^B\",null]]],\"text4\",[\"^0\",[\"^ \",\"n\",\"pluginTemplate\",\"v\",[\"^ \",\"id\",\"text4\",\"^4\",\"widget\",\"^5\",\"TextWidget2\",\"^6\",null,\"^7\",null,\"^8\",[\"^3\",[\"heightType\",\"auto\",\"horizontalAlign\",\"left\",\"hidden\",false,\"imageWidth\",\"fit\",\"showInEditor\",false,\"verticalAlign\",\"center\",\"tooltipText\",\"\",\"value\",\"###### {{displayedIncidents.value[i].pd.title}}\",\"disableMarkdown\",false,\"overflowType\",\"scroll\",\"maintainSpaceWhenHidden\",false]],\"^9\",[\"^3\",[]],\"^:\",[\"^0\",[\"^ \",\"n\",\"position2\",\"v\",[\"^ \",\"^@\",\"container1\",\"^D\",\"body\",\"^E\",\"\",\"row\",1.8000000000000003,\"col\",2,\"^F\",0.6,\"^G\",5,\"^H\",0]]],\"^;\",null,\"^<\",null,\"^=\",null,\"^>\",\"~m1638970293894\",\"^?\",\"~m1639175029435\",\"^@\",\"\",\"^A\",\"\",\"^B\",null]]],\"tags1\",[\"^0\",[\"^ \",\"n\",\"pluginTemplate\",\"v\",[\"^ \",\"id\",\"tags1\",\"^4\",\"widget\",\"^5\",\"TagsWidget\",\"^6\",null,\"^7\",null,\"^8\",[\"^3\",[\"horizontalAlign\",\"left\",\"hidden\",false,\"showInEditor\",false,\"colors\",\"[{{TAGS[displayedIncidents.value[i].pd.status].color}}]\",\"tooltipText\",\"\",\"value\",\"[{{TAGS[displayedIncidents.value[i].pd.status].text}}]\",\"allowWrap\",true,\"hashColors\",true,\"maintainSpaceWhenHidden\",false]],\"^9\",[\"^3\",[]],\"^:\",[\"^0\",[\"^ \",\"n\",\"position2\",\"v\",[\"^ \",\"^@\",\"container1\",\"^D\",\"body\",\"^E\",\"\",\"row\",0.7999999999999996,\"col\",0,\"^F\",0.8,\"^G\",2,\"^H\",0]]],\"^;\",null,\"^<\",null,\"^=\",null,\"^>\",\"~m1638970331419\",\"^?\",\"~m1639067584109\",\"^@\",\"\",\"^A\",\"\",\"^B\",null]]],\"text6\",[\"^0\",[\"^ \",\"n\",\"pluginTemplate\",\"v\",[\"^ \",\"id\",\"text6\",\"^4\",\"widget\",\"^5\",\"TextWidget2\",\"^6\",null,\"^7\",null,\"^8\",[\"^3\",[\"heightType\",\"auto\",\"horizontalAlign\",\"left\",\"hidden\",false,\"imageWidth\",\"fit\",\"showInEditor\",false,\"verticalAlign\",\"center\",\"tooltipText\",\"\",\"value\",\"**Assigned to** {{displayedIncidents.value[i].pd.assignments.map(a => a.assignee.summary).join(', ')}}\",\"style\",[\"^3\",[\"color\",\"rgba(106, 106, 106, 0.9)\"]],\"disableMarkdown\",false,\"overflowType\",\"scroll\",\"maintainSpaceWhenHidden\",false]],\"^9\",[\"^3\",[]],\"^:\",[\"^0\",[\"^ \",\"n\",\"position2\",\"v\",[\"^ \",\"^@\",\"container1\",\"^D\",\"body\",\"^E\",\"\",\"row\",3.000000000000003,\"col\",2,\"^F\",0.6,\"^G\",5,\"^H\",0]]],\"^;\",null,\"^<\",null,\"^=\",null,\"^>\",\"~m1638970439276\",\"^?\",\"~m1639321829190\",\"^@\",\"\",\"^A\",\"\",\"^B\",null]]],\"detailsLink\",[\"^0\",[\"^ \",\"n\",\"pluginTemplate\",\"v\",[\"^ \",\"id\",\"detailsLink\",\"^4\",\"widget\",\"^5\",\"LinkWidget\",\"^6\",null,\"^7\",null,\"^8\",[\"^3\",[\"horizontalAlign\",\"left\",\"clickable\",false,\"iconAfter\",\"\",\"hidden\",false,\"text\",\"View details\",\"showUnderline\",\"always\",\"showInEditor\",false,\"tooltipText\",\"\",\"style\",[\"^3\",[\"text\",\"{{COLOR.link_green}}\"]],\"allowWrap\",false,\"iconBefore\",\"bold/interface-link-square-alternate\",\"events\",[\"^C\",[[\"^3\",[\"event\",\"click\",\"type\",\"util\",\"method\",\"openApp\",\"pluginId\",\"\",\"params\",[\"^3\",[\"options\",[\"^3\",[\"newTab\",true,\"queryParams\",[\"^C\",[[\"^3\",[\"key\",\"\",\"value\",\"\"]]]],\"hashParams\",[\"^C\",[[\"^3\",[\"key\",\"incident_name\",\"value\",\"{{displayedIncidents.value[i].pd.incident_key}}\"]]]]]],\"uuid\",\"f601490a-5913-11ec-9083-a700d6721666\"]],\"waitType\",\"debounce\",\"waitMs\",\"0\"]]]],\"loading\",false,\"loaderPosition\",\"auto\",\"disabled\",false,\"maintainSpaceWhenHidden\",false]],\"^9\",[\"^3\",[]],\"^:\",[\"^0\",[\"^ \",\"n\",\"position2\",\"v\",[\"^ \",\"^@\",\"container1\",\"^D\",\"body\",\"^E\",\"\",\"row\",3.5999999999999974,\"col\",8,\"^F\",0.6,\"^G\",4,\"^H\",0]]],\"^;\",null,\"^<\",null,\"^=\",null,\"^>\",\"~m1638970508087\",\"^?\",\"~m1639321105708\",\"^@\",\"\",\"^A\",\"\",\"^B\",null]]],\"text8\",[\"^0\",[\"^ \",\"n\",\"pluginTemplate\",\"v\",[\"^ \",\"id\",\"text8\",\"^4\",\"widget\",\"^5\",\"TextWidget2\",\"^6\",null,\"^7\",null,\"^8\",[\"^3\",[\"heightType\",\"auto\",\"horizontalAlign\",\"right\",\"hidden\",false,\"imageWidth\",\"fit\",\"showInEditor\",false,\"verticalAlign\",\"center\",\"tooltipText\",\"\",\"value\",\"
\",\"disableMarkdown\",false,\"overflowType\",\"scroll\",\"maintainSpaceWhenHidden\",false]],\"^9\",[\"^3\",[]],\"^:\",[\"^0\",[\"^ \",\"n\",\"position2\",\"v\",[\"^ \",\"^@\",\"container1\",\"^D\",\"body\",\"^E\",\"\",\"row\",1.8,\"col\",7,\"^F\",0.6,\"^G\",1,\"^H\",0]]],\"^;\",null,\"^<\",null,\"^=\",null,\"^>\",\"~m1638980352132\",\"^?\",\"~m1639518201445\",\"^@\",\"\",\"^A\",\"\",\"^B\",null]]],\"text9\",[\"^0\",[\"^ \",\"n\",\"pluginTemplate\",\"v\",[\"^ \",\"id\",\"text9\",\"^4\",\"widget\",\"^5\",\"TextWidget2\",\"^6\",null,\"^7\",null,\"^8\",[\"^3\",[\"heightType\",\"auto\",\"horizontalAlign\",\"right\",\"hidden\",false,\"imageWidth\",\"fit\",\"showInEditor\",false,\"verticalAlign\",\"center\",\"tooltipText\",\"\",\"value\",\"
\",\"disableMarkdown\",false,\"overflowType\",\"scroll\",\"maintainSpaceWhenHidden\",false]],\"^9\",[\"^3\",[]],\"^:\",[\"^0\",[\"^ \",\"n\",\"position2\",\"v\",[\"^ \",\"^@\",\"container1\",\"^D\",\"body\",\"^E\",\"\",\"row\",2.4,\"col\",7,\"^F\",0.6,\"^G\",1,\"^H\",0]]],\"^;\",null,\"^<\",null,\"^=\",null,\"^>\",\"~m1638980549104\",\"^?\",\"~m1638980630605\",\"^@\",\"\",\"^A\",\"\",\"^B\",null]]],\"text11\",[\"^0\",[\"^ \",\"n\",\"pluginTemplate\",\"v\",[\"^ \",\"id\",\"text11\",\"^4\",\"widget\",\"^5\",\"TextWidget2\",\"^6\",null,\"^7\",null,\"^8\",[\"^3\",[\"heightType\",\"auto\",\"horizontalAlign\",\"left\",\"hidden\",false,\"imageWidth\",\"fit\",\"showInEditor\",false,\"verticalAlign\",\"center\",\"tooltipText\",\"\",\"value\",\"**Created** {{moment(displayedIncidents.value[i].pd.created_at).format('ddd, MMM Do YYYY, h:mm a')}}\",\"style\",[\"^3\",[\"color\",\"#6a6a6a\"]],\"disableMarkdown\",false,\"overflowType\",\"scroll\",\"maintainSpaceWhenHidden\",false]],\"^9\",[\"^3\",[]],\"^:\",[\"^0\",[\"^ \",\"n\",\"position2\",\"v\",[\"^ \",\"^@\",\"container1\",\"^D\",\"body\",\"^E\",\"\",\"row\",2.4000000000000017,\"col\",2,\"^F\",0.6,\"^G\",5,\"^H\",0]]],\"^;\",null,\"^<\",null,\"^=\",null,\"^>\",\"~m1638980784910\",\"^?\",\"~m1639321825570\",\"^@\",\"\",\"^A\",\"\",\"^B\",null]]],\"incidentNavbar1\",[\"^0\",[\"^ \",\"n\",\"pluginTemplate\",\"v\",[\"^ \",\"id\",\"incidentNavbar1\",\"^4\",\"widget\",\"^5\",\"GlobalWidget\",\"^6\",null,\"^7\",null,\"^8\",[\"^3\",[\"showFetchingIndicator\",false,\"name\",\"Incident-Navbar\",\"pageUuid\",\"cd581076-585d-11ec-a633-936fd80bda50\",\"childNamespace\",\"incidentNavbar1\"]],\"^9\",[\"^3\",[]],\"^:\",[\"^0\",[\"^ \",\"n\",\"position2\",\"v\",[\"^ \",\"^@\",\"\",\"^D\",\"body\",\"^E\",\"header\",\"row\",0,\"col\",0,\"^F\",1,\"^G\",12,\"^H\",0]]],\"^;\",null,\"^<\",null,\"^=\",null,\"^>\",\"~m1638992365312\",\"^?\",\"~m1638992365312\",\"^@\",\"\",\"^A\",\"\",\"^B\",null]]],\"container6\",[\"^0\",[\"^ \",\"n\",\"pluginTemplate\",\"v\",[\"^ \",\"id\",\"container6\",\"^4\",\"widget\",\"^5\",\"ContainerWidget2\",\"^6\",null,\"^7\",null,\"^8\",[\"^3\",[\"_disabledByIndex\",[\"^C\",[\"\"]],\"selectedView\",\"View 1\",\"heightType\",\"auto\",\"currentViewKey\",\"\",\"iconByIndex\",[],\"clickable\",false,\"_viewLabels\",[\"^C\",[]],\"_iconByIndex\",[\"^C\",[\"\"]],\"viewLabels\",[],\"hidden\",false,\"showHeader\",false,\"hoistFetching\",true,\"views\",[],\"showInEditor\",false,\"tooltipText\",\"\",\"style\",[\"^3\",[\"background\",\"{{COLOR.bkgrnd_gray}}\",\"border\",\"#ffffff\",\"borderRadius\",\"5px\"]],\"hiddenByIndex\",[],\"_hiddenByIndex\",[\"^C\",[\"\"]],\"currentViewIndex\",null,\"_hasMigratedNestedItems\",true,\"transition\",\"none\",\"itemMode\",\"static\",\"_tooltipByIndex\",[\"^C\",[\"\"]],\"tooltipByIndex\",[],\"showFooter\",false,\"_viewKeys\",[\"^C\",[\"View 1\"]],\"events\",[\"^3\",[]],\"_ids\",[\"^C\",[\"\"]],\"viewKeys\",[],\"iconPositionByIndex\",[],\"_iconPositionByIndex\",[\"^C\",[\"\"]],\"loading\",false,\"overflowType\",\"scroll\",\"_labels\",[\"^C\",[\"\"]],\"disabledByIndex\",[],\"maintainSpaceWhenHidden\",false,\"labels\",[]]],\"^9\",[\"^3\",[]],\"^:\",[\"^0\",[\"^ \",\"n\",\"position2\",\"v\",[\"^ \",\"^@\",\"\",\"^D\",\"body\",\"^E\",\"\",\"row\",1.609823385706477e-15,\"col\",0,\"^F\",13.600000000000001,\"^G\",3,\"^H\",0]]],\"^;\",null,\"^<\",null,\"^=\",null,\"^>\",\"~m1639056008585\",\"^?\",\"~m1639583561968\",\"^@\",\"\",\"^A\",\"\",\"^B\",null]]],\"containerTitle8\",[\"^0\",[\"^ \",\"n\",\"pluginTemplate\",\"v\",[\"^ \",\"id\",\"containerTitle8\",\"^4\",\"widget\",\"^5\",\"TextWidget2\",\"^6\",null,\"^7\",null,\"^8\",[\"^3\",[\"heightType\",\"auto\",\"horizontalAlign\",\"left\",\"hidden\",false,\"imageWidth\",\"fit\",\"showInEditor\",false,\"verticalAlign\",\"center\",\"tooltipText\",\"\",\"value\",\"#### Container title\",\"disableMarkdown\",false,\"overflowType\",\"scroll\",\"maintainSpaceWhenHidden\",false]],\"^9\",[\"^3\",[]],\"^:\",[\"^0\",[\"^ \",\"n\",\"position2\",\"v\",[\"^ \",\"^@\",\"container6\",\"^D\",\"header\",\"^E\",\"\",\"row\",0,\"col\",0,\"^F\",0.6,\"^G\",12,\"^H\",0]]],\"^;\",null,\"^<\",null,\"^=\",null,\"^>\",\"~m1639056008793\",\"^?\",\"~m1639056008793\",\"^@\",\"\",\"^A\",\"\",\"^B\",null]]],\"daysSelect\",[\"^0\",[\"^ \",\"n\",\"pluginTemplate\",\"v\",[\"^ \",\"id\",\"daysSelect\",\"^4\",\"widget\",\"^5\",\"SelectWidget2\",\"^6\",null,\"^7\",null,\"^8\",[\"^3\",[\"_disabledByIndex\",[\"^C\",[]],\"showSelectionIndicator\",true,\"_values\",[\"^C\",[]],\"values\",\"['7', '14', '30', '60']\",\"readOnly\",false,\"iconAfter\",\"\",\"inputValue\",\"\",\"_data\",[\"^C\",[]],\"hidden\",false,\"customValidation\",\"\",\"data\",[],\"searchMode\",\"fuzzy\",\"hideValidationMessage\",false,\"textBefore\",\"\",\"selectedItem\",null,\"validationMessage\",\"\",\"textAfter\",\"\",\"showInEditor\",false,\"showClear\",false,\"tooltipText\",\"\",\"labelAlign\",\"left\",\"formDataKey\",\"{{ self.id }}\",\"value\",\"'30'\",\"hiddenByIndex\",[],\"labelCaption\",\"\",\"hideLabel\",true,\"labelWidth\",\"33\",\"deprecatedLabels\",[],\"_hiddenByIndex\",[\"^C\",[]],\"placeholder\",\"Select an option\",\"_captionByIndex\",[\"^C\",[]],\"label\",\"Time period\",\"_hasMigratedNestedItems\",false,\"captionByIndex\",[],\"_validate\",false,\"itemMode\",\"dynamic\",\"labelWidthUnit\",\"%\",\"allowCustomValue\",false,\"invalid\",false,\"selectedIndex\",null,\"_tooltipByIndex\",[\"^C\",[]],\"tooltipByIndex\",[],\"iconBefore\",\"\",\"optionMode\",\"dynamic\",\"selectedLabel\",\"\",\"events\",[\"^3\",[]],\"_ids\",[\"^C\",[]],\"emptyMessage\",\"\",\"overlayMaxHeight\",null,\"loading\",false,\"disabled\",\"{{useCustomDatesToggle.value}}\",\"labelPosition\",\"top\",\"_labels\",[\"^C\",[]],\"labelWrap\",false,\"disabledValues\",[],\"disabledByIndex\",[],\"maintainSpaceWhenHidden\",false,\"required\",false,\"labels\",\"['Last 7 days', 'Last 14 days', 'Last 30 days', 'Last 60 days']\"]],\"^9\",[\"^3\",[]],\"^:\",[\"^0\",[\"^ \",\"n\",\"position2\",\"v\",[\"^ \",\"^@\",\"container6\",\"^D\",\"body\",\"^E\",\"\",\"row\",3.4000000000000004,\"col\",1,\"^F\",1,\"^G\",10,\"^H\",0]]],\"^;\",null,\"^<\",null,\"^=\",null,\"^>\",\"~m1639056199817\",\"^?\",\"~m1639584385297\",\"^@\",\"\",\"^A\",\"\",\"^B\",null]]],\"spacer1\",[\"^0\",[\"^ \",\"n\",\"pluginTemplate\",\"v\",[\"^ \",\"id\",\"spacer1\",\"^4\",\"widget\",\"^5\",\"SpacerWidget\",\"^6\",null,\"^7\",null,\"^8\",[\"^3\",[\"hidden\",false,\"maintainSpaceWhenHidden\",false,\"showInEditor\",false]],\"^9\",[\"^3\",[]],\"^:\",[\"^0\",[\"^ \",\"n\",\"position2\",\"v\",[\"^ \",\"^@\",\"container1\",\"^D\",\"body\",\"^E\",\"\",\"row\",4.600000000000001,\"col\",0,\"^F\",0.6000000000000001,\"^G\",12,\"^H\",0]]],\"^;\",null,\"^<\",null,\"^=\",null,\"^>\",\"~m1639056586466\",\"^?\",\"~m1639056586466\",\"^@\",\"\",\"^A\",\"\",\"^B\",null]]],\"spacer2\",[\"^0\",[\"^ \",\"n\",\"pluginTemplate\",\"v\",[\"^ \",\"id\",\"spacer2\",\"^4\",\"widget\",\"^5\",\"SpacerWidget\",\"^6\",null,\"^7\",null,\"^8\",[\"^3\",[\"hidden\",false,\"maintainSpaceWhenHidden\",false,\"showInEditor\",false]],\"^9\",[\"^3\",[]],\"^:\",[\"^0\",[\"^ \",\"n\",\"position2\",\"v\",[\"^ \",\"^@\",\"container1\",\"^D\",\"body\",\"^E\",\"\",\"row\",0.2,\"col\",0,\"^F\",0.6000000000000001,\"^G\",12,\"^H\",0]]],\"^;\",null,\"^<\",null,\"^=\",null,\"^>\",\"~m1639056671973\",\"^?\",\"~m1639056671973\",\"^@\",\"\",\"^A\",\"\",\"^B\",null]]],\"statusCheckboxes\",[\"^0\",[\"^ \",\"n\",\"pluginTemplate\",\"v\",[\"^ \",\"id\",\"statusCheckboxes\",\"^4\",\"widget\",\"^5\",\"CheckboxGroupWidget2\",\"^6\",null,\"^7\",null,\"^8\",[\"^3\",[\"_disabledByIndex\",[\"^C\",[]],\"_values\",[\"^C\",[]],\"values\",\"['Active', 'Resolved']\",\"selectedIndexes\",[],\"selectedItems\",[],\"_data\",[\"^C\",[]],\"groupLayout\",\"multiColumn\",\"hidden\",false,\"customValidation\",\"\",\"data\",[],\"maxCount\",null,\"hideValidationMessage\",false,\"validationMessage\",\"\",\"showInEditor\",false,\"tooltipText\",\"\",\"labelAlign\",\"left\",\"formDataKey\",\"{{ self.id }}\",\"value\",\"['Active']\",\"style\",[\"^3\",[\"checkedBackground\",\"{{COLOR.link_green}}\"]],\"hiddenByIndex\",[],\"labelCaption\",\"\",\"labelWidth\",\"33\",\"deprecatedLabels\",[],\"_hiddenByIndex\",[\"^C\",[]],\"_captionByIndex\",[\"^C\",[]],\"label\",\"\",\"_hasMigratedNestedItems\",false,\"captionByIndex\",[],\"_validate\",false,\"itemMode\",\"dynamic\",\"labelWidthUnit\",\"%\",\"invalid\",false,\"_tooltipByIndex\",[\"^C\",[]],\"tooltipByIndex\",[],\"optionMode\",\"dynamic\",\"events\",[\"^3\",[]],\"_ids\",[\"^C\",[]],\"selectedLabels\",[],\"disabled\",false,\"labelPosition\",\"top\",\"minCount\",null,\"_labels\",[\"^C\",[]],\"labelWrap\",false,\"disabledValues\",[],\"disabledByIndex\",[],\"maintainSpaceWhenHidden\",false,\"required\",false,\"labels\",\"\"]],\"^9\",[\"^3\",[]],\"^:\",[\"^0\",[\"^ \",\"n\",\"position2\",\"v\",[\"^ \",\"^@\",\"container6\",\"^D\",\"body\",\"^E\",\"\",\"row\",0.8,\"col\",1,\"^F\",0.6,\"^G\",11,\"^H\",0]]],\"^;\",null,\"^<\",null,\"^=\",null,\"^>\",\"~m1639057118430\",\"^?\",\"~m1639323123983\",\"^@\",\"\",\"^A\",\"\",\"^B\",null]]],\"spacer3\",[\"^0\",[\"^ \",\"n\",\"pluginTemplate\",\"v\",[\"^ \",\"id\",\"spacer3\",\"^4\",\"widget\",\"^5\",\"SpacerWidget\",\"^6\",null,\"^7\",null,\"^8\",[\"^3\",[\"hidden\",false,\"maintainSpaceWhenHidden\",false,\"showInEditor\",false]],\"^9\",[\"^3\",[]],\"^:\",[\"^0\",[\"^ \",\"n\",\"position2\",\"v\",[\"^ \",\"^@\",\"listView1\",\"^D\",\"body\",\"^E\",\"\",\"row\",6,\"col\",0,\"^F\",0.4,\"^G\",12,\"^H\",0]]],\"^;\",null,\"^<\",null,\"^=\",null,\"^>\",\"~m1639061881071\",\"^?\",\"~m1639061881071\",\"^@\",\"\",\"^A\",\"\",\"^B\",null]]],\"slackLink\",[\"^0\",[\"^ \",\"n\",\"pluginTemplate\",\"v\",[\"^ \",\"id\",\"slackLink\",\"^4\",\"widget\",\"^5\",\"LinkWidget\",\"^6\",null,\"^7\",null,\"^8\",[\"^3\",[\"horizontalAlign\",\"left\",\"clickable\",false,\"iconAfter\",\"\",\"hidden\",false,\"text\",\"#{{displayedIncidents.value[i].db?.slack_channel_name}}\",\"showUnderline\",\"always\",\"showInEditor\",false,\"tooltipText\",\"\",\"style\",[\"^3\",[\"text\",\"{{COLOR.link_green}}\"]],\"allowWrap\",false,\"iconBefore\",\"\",\"events\",[\"^C\",[[\"^3\",[\"event\",\"click\",\"type\",\"util\",\"method\",\"openUrl\",\"pluginId\",\"\",\"params\",[\"^3\",[\"options\",[\"^3\",[\"newTab\",true]],\"url\",\"{{displayedIncidents.value[i].db?.slack_channel_url}}\"]],\"waitType\",\"debounce\",\"waitMs\",\"0\"]]]],\"loading\",false,\"loaderPosition\",\"auto\",\"disabled\",false,\"maintainSpaceWhenHidden\",false]],\"^9\",[\"^3\",[]],\"^:\",[\"^0\",[\"^ \",\"n\",\"position2\",\"v\",[\"^ \",\"^@\",\"container1\",\"^D\",\"body\",\"^E\",\"\",\"row\",1.9999999999999998,\"col\",8,\"^F\",0.6,\"^G\",4,\"^H\",0]]],\"^;\",null,\"^<\",null,\"^=\",null,\"^>\",\"~m1639065899723\",\"^?\",\"~m1639320677796\",\"^@\",\"\",\"^A\",\"\",\"^B\",null]]],\"pdLink\",[\"^0\",[\"^ \",\"n\",\"pluginTemplate\",\"v\",[\"^ \",\"id\",\"pdLink\",\"^4\",\"widget\",\"^5\",\"LinkWidget\",\"^6\",null,\"^7\",null,\"^8\",[\"^3\",[\"horizontalAlign\",\"left\",\"clickable\",false,\"iconAfter\",\"\",\"hidden\",false,\"text\",\"Pagerduty profile\",\"showUnderline\",\"always\",\"showInEditor\",false,\"tooltipText\",\"\",\"style\",[\"^3\",[\"text\",\"{{COLOR.link_green}}\"]],\"allowWrap\",false,\"iconBefore\",\"\",\"events\",[\"^C\",[[\"^3\",[\"event\",\"click\",\"type\",\"util\",\"method\",\"openUrl\",\"pluginId\",\"\",\"params\",[\"^3\",[\"url\",\"{{displayedIncidents.value[i].db?.pagerduty_url}}\"]],\"waitType\",\"debounce\",\"waitMs\",\"0\"]]]],\"loading\",false,\"loaderPosition\",\"auto\",\"disabled\",false,\"maintainSpaceWhenHidden\",false]],\"^9\",[\"^3\",[]],\"^:\",[\"^0\",[\"^ \",\"n\",\"position2\",\"v\",[\"^ \",\"^@\",\"container1\",\"^D\",\"body\",\"^E\",\"\",\"row\",2.8000000000000007,\"col\",8,\"^F\",0.6,\"^G\",4,\"^H\",0]]],\"^;\",null,\"^<\",null,\"^=\",null,\"^>\",\"~m1639065912486\",\"^?\",\"~m1639321096245\",\"^@\",\"\",\"^A\",\"\",\"^B\",null]]],\"$main\",[\"^0\",[\"^ \",\"n\",\"pluginTemplate\",\"v\",[\"^ \",\"id\",\"$main\",\"^4\",\"frame\",\"^5\",\"Frame\",\"^6\",null,\"^7\",null,\"^8\",[\"^3\",[\"type\",\"main\",\"sticky\",false,\"style\",[\"^3\",[\"canvas\",\"#ffffff\"]]]],\"^9\",[\"^3\",[]],\"^:\",null,\"^;\",null,\"^<\",null,\"^=\",null,\"^>\",\"~m1639236967805\",\"^?\",\"~m1639236971888\",\"^@\",\"\",\"^A\",\"\",\"^B\",null]]],\"divider1\",[\"^0\",[\"^ \",\"n\",\"pluginTemplate\",\"v\",[\"^ \",\"id\",\"divider1\",\"^4\",\"widget\",\"^5\",\"DividerWidget\",\"^6\",null,\"^7\",null,\"^8\",[\"^3\",[\"hidden\",false,\"maintainSpaceWhenHidden\",false,\"showInEditor\",false,\"tooltipText\",\"\",\"horizontalAlign\",\"center\",\"text\",\"\",\"textSize\",\"default\",\"style\",[\"^3\",[\"color\",\"{{COLOR.purple}}\"]]]],\"^9\",[\"^3\",[]],\"^:\",[\"^0\",[\"^ \",\"n\",\"position2\",\"v\",[\"^ \",\"^@\",\"container1\",\"^D\",\"body\",\"^E\",\"\",\"row\",0,\"col\",0,\"^F\",0.2,\"^G\",12,\"^H\",0]]],\"^;\",null,\"^<\",null,\"^=\",null,\"^>\",\"~m1639322649174\",\"^?\",\"~m1639322667169\",\"^@\",\"\",\"^A\",\"\",\"^B\",null]]],\"text12\",[\"^0\",[\"^ \",\"n\",\"pluginTemplate\",\"v\",[\"^ \",\"id\",\"text12\",\"^4\",\"widget\",\"^5\",\"TextWidget2\",\"^6\",null,\"^7\",null,\"^8\",[\"^3\",[\"heightType\",\"auto\",\"horizontalAlign\",\"left\",\"hidden\",false,\"imageWidth\",\"fit\",\"showInEditor\",false,\"verticalAlign\",\"center\",\"tooltipText\",\"\",\"value\",\"###### Status\",\"disableMarkdown\",false,\"overflowType\",\"scroll\",\"maintainSpaceWhenHidden\",false]],\"^9\",[\"^3\",[]],\"^:\",[\"^0\",[\"^ \",\"n\",\"position2\",\"v\",[\"^ \",\"^@\",\"container6\",\"^D\",\"body\",\"^E\",\"\",\"row\",0.2,\"col\",1,\"^F\",0.6,\"^G\",11,\"^H\",0]]],\"^;\",null,\"^<\",null,\"^=\",null,\"^>\",\"~m1639323142313\",\"^?\",\"~m1639323156363\",\"^@\",\"\",\"^A\",\"\",\"^B\",null]]],\"text13\",[\"^0\",[\"^ \",\"n\",\"pluginTemplate\",\"v\",[\"^ \",\"id\",\"text13\",\"^4\",\"widget\",\"^5\",\"TextWidget2\",\"^6\",null,\"^7\",null,\"^8\",[\"^3\",[\"heightType\",\"auto\",\"horizontalAlign\",\"left\",\"hidden\",false,\"imageWidth\",\"fit\",\"showInEditor\",false,\"verticalAlign\",\"center\",\"tooltipText\",\"\",\"value\",\"###### Created\",\"disableMarkdown\",false,\"overflowType\",\"scroll\",\"maintainSpaceWhenHidden\",false]],\"^9\",[\"^3\",[]],\"^:\",[\"^0\",[\"^ \",\"n\",\"position2\",\"v\",[\"^ \",\"^@\",\"container6\",\"^D\",\"body\",\"^E\",\"\",\"row\",2.8000000000000007,\"col\",1,\"^F\",0.6,\"^G\",11,\"^H\",0]]],\"^;\",null,\"^<\",null,\"^=\",null,\"^>\",\"~m1639323167363\",\"^?\",\"~m1639323175923\",\"^@\",\"\",\"^A\",\"\",\"^B\",null]]],\"useCustomDatesToggle\",[\"^0\",[\"^ \",\"n\",\"pluginTemplate\",\"v\",[\"^ \",\"id\",\"useCustomDatesToggle\",\"^4\",\"widget\",\"^5\",\"ToggleLinkWidget\",\"^6\",null,\"^7\",null,\"^8\",[\"^3\",[\"horizontalAlign\",\"left\",\"iconPosition\",\"before\",\"hidden\",false,\"text\",\"{{ self.value ? 'Use presets' : 'Set custom date range' }}\",\"showUnderline\",\"always\",\"showInEditor\",false,\"tooltipText\",\"\",\"value\",false,\"style\",[\"^3\",[\"text\",\"{{COLOR.link_gray}}\"]],\"allowWrap\",false,\"iconType\",\"caret\",\"events\",[\"^3\",[]],\"disabled\",false,\"maintainSpaceWhenHidden\",false]],\"^9\",[\"^3\",[]],\"^:\",[\"^0\",[\"^ \",\"n\",\"position2\",\"v\",[\"^ \",\"^@\",\"container6\",\"^D\",\"body\",\"^E\",\"\",\"row\",4.3999999999999995,\"col\",1,\"^F\",0.6,\"^G\",10,\"^H\",0]]],\"^;\",null,\"^<\",null,\"^=\",null,\"^>\",\"~m1639582639052\",\"^?\",\"~m1639584501920\",\"^@\",\"\",\"^A\",\"\",\"^B\",null]]],\"priorityMultiSelect\",[\"^0\",[\"^ \",\"n\",\"pluginTemplate\",\"v\",[\"^ \",\"id\",\"priorityMultiSelect\",\"^4\",\"widget\",\"^5\",\"MultiselectWidget2\",\"^6\",null,\"^7\",null,\"^8\",[\"^3\",[\"_disabledByIndex\",[\"^C\",[]],\"showSelectionIndicator\",true,\"_values\",[\"^C\",[]],\"values\",\"{{ getPagerDutyIncidentPriorities.data.map(priority => priority.id) }}\",\"readOnly\",false,\"iconAfter\",\"\",\"wrapTags\",true,\"selectedIndexes\",[],\"selectedItems\",[],\"inputValue\",\"\",\"_data\",[\"^C\",[]],\"hidden\",\"{{!useCustomPrioritiesToggle.value}}\",\"customValidation\",\"\",\"data\",[],\"maxCount\",null,\"searchMode\",\"fuzzy\",\"hideValidationMessage\",false,\"textBefore\",\"\",\"validationMessage\",\"\",\"textAfter\",\"\",\"showInEditor\",false,\"showClear\",false,\"tooltipText\",\"\",\"labelAlign\",\"left\",\"formDataKey\",\"{{ self.id }}\",\"value\",\"{{ getPagerDutyIncidentPriorities.data.map(priority => priority.id) }}\",\"hiddenByIndex\",[],\"labelCaption\",\"\",\"hideLabel\",true,\"labelWidth\",\"33\",\"deprecatedLabels\",[],\"_hiddenByIndex\",[\"^C\",[]],\"placeholder\",\"Select multiple options\",\"_captionByIndex\",[\"^C\",[]],\"label\",\"Priority\",\"_hasMigratedNestedItems\",false,\"captionByIndex\",[],\"_validate\",false,\"itemMode\",\"dynamic\",\"labelWidthUnit\",\"%\",\"allowCustomValue\",false,\"invalid\",false,\"_tooltipByIndex\",[\"^C\",[]],\"tooltipByIndex\",[],\"iconBefore\",\"\",\"optionMode\",\"dynamic\",\"events\",[\"^3\",[]],\"_ids\",[\"^C\",[]],\"emptyMessage\",\"\",\"selectedLabels\",[],\"overlayMaxHeight\",null,\"loading\",false,\"disabled\",false,\"labelPosition\",\"top\",\"minCount\",null,\"_labels\",[\"^C\",[]],\"labelWrap\",false,\"disabledValues\",[],\"disabledByIndex\",[],\"maintainSpaceWhenHidden\",false,\"required\",false,\"labels\",\"{{ getPagerDutyIncidentPriorities.data.map(priority => (priority.description ? `${priority.name} - ${priority.description}` : priority.name)) }}\"]],\"^9\",[\"^3\",[]],\"^:\",[\"^0\",[\"^ \",\"n\",\"position2\",\"v\",[\"^ \",\"^@\",\"container6\",\"^D\",\"body\",\"^E\",\"\",\"row\",9.000000000000004,\"col\",1,\"^F\",1,\"^G\",10,\"^H\",0]]],\"^;\",null,\"^<\",null,\"^=\",null,\"^>\",\"~m1639583372179\",\"^?\",\"~m1639584403990\",\"^@\",\"\",\"^A\",\"\",\"^B\",null]]],\"text15\",[\"^0\",[\"^ \",\"n\",\"pluginTemplate\",\"v\",[\"^ \",\"id\",\"text15\",\"^4\",\"widget\",\"^5\",\"TextWidget2\",\"^6\",null,\"^7\",null,\"^8\",[\"^3\",[\"heightType\",\"auto\",\"horizontalAlign\",\"left\",\"hidden\",false,\"imageWidth\",\"fit\",\"showInEditor\",false,\"verticalAlign\",\"center\",\"tooltipText\",\"\",\"value\",\"###### Priority\",\"disableMarkdown\",false,\"overflowType\",\"scroll\",\"maintainSpaceWhenHidden\",false]],\"^9\",[\"^3\",[]],\"^:\",[\"^0\",[\"^ \",\"n\",\"position2\",\"v\",[\"^ \",\"^@\",\"container6\",\"^D\",\"body\",\"^E\",\"\",\"row\",6.799999999999999,\"col\",1,\"^F\",0.6,\"^G\",10,\"^H\",0]]],\"^;\",null,\"^<\",null,\"^=\",null,\"^>\",\"~m1639583388887\",\"^?\",\"~m1639583419304\",\"^@\",\"\",\"^A\",\"\",\"^B\",null]]],\"spacer4\",[\"^0\",[\"^ \",\"n\",\"pluginTemplate\",\"v\",[\"^ \",\"id\",\"spacer4\",\"^4\",\"widget\",\"^5\",\"SpacerWidget\",\"^6\",null,\"^7\",null,\"^8\",[\"^3\",[\"hidden\",false,\"maintainSpaceWhenHidden\",false,\"showInEditor\",false]],\"^9\",[\"^3\",[]],\"^:\",[\"^0\",[\"^ \",\"n\",\"position2\",\"v\",[\"^ \",\"^@\",\"container6\",\"^D\",\"body\",\"^E\",\"\",\"row\",10.000000000000009,\"col\",1,\"^F\",1,\"^G\",10,\"^H\",0]]],\"^;\",null,\"^<\",null,\"^=\",null,\"^>\",\"~m1639583583328\",\"^?\",\"~m1639583583328\",\"^@\",\"\",\"^A\",\"\",\"^B\",null]]],\"prioritySelect\",[\"^0\",[\"^ \",\"n\",\"pluginTemplate\",\"v\",[\"^ \",\"id\",\"prioritySelect\",\"^4\",\"widget\",\"^5\",\"SelectWidget2\",\"^6\",null,\"^7\",null,\"^8\",[\"^3\",[\"_disabledByIndex\",[\"^C\",[]],\"showSelectionIndicator\",true,\"_values\",[\"^C\",[]],\"values\",\"{{ getPagerDutyIncidentPriorities.data.map(priority => priority.id).concat(['Any']) }}\",\"readOnly\",false,\"iconAfter\",\"\",\"inputValue\",\"\",\"_data\",[\"^C\",[]],\"hidden\",false,\"customValidation\",\"\",\"data\",[],\"searchMode\",\"fuzzy\",\"hideValidationMessage\",false,\"textBefore\",\"\",\"selectedItem\",null,\"validationMessage\",\"\",\"textAfter\",\"\",\"showInEditor\",false,\"showClear\",false,\"tooltipText\",\"\",\"labelAlign\",\"left\",\"formDataKey\",\"{{ self.id }}\",\"value\",\"Any\",\"hiddenByIndex\",[],\"labelCaption\",\"\",\"hideLabel\",true,\"labelWidth\",\"33\",\"deprecatedLabels\",[],\"_hiddenByIndex\",[\"^C\",[]],\"placeholder\",\"Select an option\",\"_captionByIndex\",[\"^C\",[]],\"label\",\"Priority\",\"_hasMigratedNestedItems\",false,\"captionByIndex\",[],\"_validate\",false,\"itemMode\",\"dynamic\",\"labelWidthUnit\",\"%\",\"allowCustomValue\",false,\"invalid\",false,\"selectedIndex\",null,\"_tooltipByIndex\",[\"^C\",[]],\"tooltipByIndex\",[],\"iconBefore\",\"\",\"selectedLabel\",\"\",\"events\",[\"^3\",[]],\"_ids\",[\"^C\",[]],\"emptyMessage\",\"\",\"overlayMaxHeight\",null,\"loading\",false,\"disabled\",\"{{useCustomPrioritiesToggle.value}}\",\"labelPosition\",\"left\",\"_labels\",[\"^C\",[]],\"labelWrap\",false,\"disabledValues\",[],\"disabledByIndex\",[],\"maintainSpaceWhenHidden\",false,\"required\",false,\"labels\",\"{{ getPagerDutyIncidentPriorities.data.map(priority => (priority.description ? `${priority.name} - ${priority.description}` : priority.name)) }}\"]],\"^9\",[\"^3\",[]],\"^:\",[\"^0\",[\"^ \",\"n\",\"position2\",\"v\",[\"^ \",\"^@\",\"container6\",\"^D\",\"body\",\"^E\",\"\",\"row\",7.400000000000001,\"col\",1,\"^F\",1,\"^G\",10,\"^H\",0]]],\"^;\",null,\"^<\",null,\"^=\",null,\"^>\",\"~m1639583684566\",\"^?\",\"~m1639602849548\",\"^@\",\"\",\"^A\",\"\",\"^B\",null]]],\"useCustomPrioritiesToggle\",[\"^0\",[\"^ \",\"n\",\"pluginTemplate\",\"v\",[\"^ \",\"id\",\"useCustomPrioritiesToggle\",\"^4\",\"widget\",\"^5\",\"ToggleLinkWidget\",\"^6\",null,\"^7\",null,\"^8\",[\"^3\",[\"horizontalAlign\",\"left\",\"iconPosition\",\"before\",\"hidden\",false,\"text\",\"{{ self.value ? 'Use presets' : 'Set custom filters' }}\",\"showUnderline\",\"always\",\"showInEditor\",false,\"tooltipText\",\"\",\"value\",false,\"style\",[\"^3\",[\"text\",\"{{COLOR.link_gray}}\"]],\"allowWrap\",false,\"iconType\",\"caret\",\"events\",[\"^3\",[]],\"disabled\",false,\"maintainSpaceWhenHidden\",false]],\"^9\",[\"^3\",[]],\"^:\",[\"^0\",[\"^ \",\"n\",\"position2\",\"v\",[\"^ \",\"^@\",\"container6\",\"^D\",\"body\",\"^E\",\"\",\"row\",8.400000000000004,\"col\",1,\"^F\",0.6,\"^G\",10,\"^H\",0]]],\"^;\",null,\"^<\",null,\"^=\",null,\"^>\",\"~m1639583917681\",\"^?\",\"~m1639584510997\",\"^@\",\"\",\"^A\",\"\",\"^B\",null]]],\"getPDIncidents\",[\"^0\",[\"^ \",\"n\",\"pluginTemplate\",\"v\",[\"^ \",\"id\",\"getPDIncidents\",\"^4\",\"datasource\",\"^5\",\"JavascriptQuery\",\"^6\",\"JavascriptQuery\",\"^7\",null,\"^8\",[\"^3\",[\"queryRefreshTime\",\"\",\"lastReceivedFromResourceAt\",null,\"queryDisabledMessage\",\"\",\"successMessage\",\"\",\"queryDisabled\",\"\",\"playgroundQuerySaveId\",\"latest\",\"resourceNameOverride\",\"\",\"runWhenModelUpdates\",false,\"showFailureToaster\",true,\"query\",\"/*\\n * Get incidents from PagerDuty.\\n */\\n// The logic below will fetch 50 incidents at a time from PagerDuty.\\nconst PAGE_SIZE = 50;\\n// This Query won't return more than 1000 incidents. You can change this upper bound.\\nconst MAX = 1000;\\n\\nlet allPDIncidents = [];\\nlet offset = 0;\\nlet hasMore = true;\\n\\nwhile (hasMore && allPDIncidents.length <= MAX - PAGE_SIZE) {\\n\\tconst resp = await helper_getOnePageOfPDIncidents.trigger({\\n additionalScope: {\\n \\toffset: offset,\\n limit: PAGE_SIZE,\\n sinceDate: selectedStart.value,\\n untilDate: selectedEnd.value,\\n \\t}\\n });\\n if (!resp) {\\n hasMore = false;\\n break;\\n }\\n \\n allPDIncidents = allPDIncidents.concat(resp.incidents);\\n \\n offset += PAGE_SIZE;\\n hasMore = resp.more;\\n}\\n\\nreturn allPDIncidents;\",\"playgroundQueryUuid\",\"\",\"playgroundQueryId\",null,\"privateParams\",[\"^C\",[]],\"runWhenPageLoadsDelay\",\"\",\"data\",null,\"importedQueryInputs\",[\"^3\",[]],\"isImported\",false,\"showSuccessToaster\",false,\"cacheKeyTtl\",\"\",\"metadata\",null,\"changesetObject\",\"\",\"errorTransformer\",\"// The variable 'data' allows you to reference the request's data in the transformer. \\n// example: return data.find(element => element.isError)\\nreturn data.error\",\"confirmationMessage\",null,\"isFetching\",false,\"changeset\",\"\",\"rawData\",null,\"queryTriggerDelay\",\"0\",\"watchedParams\",[\"^C\",[]],\"enableErrorTransformer\",false,\"showLatestVersionUpdatedWarning\",false,\"timestamp\",0,\"importedQueryDefaults\",[\"^3\",[]],\"enableTransformer\",false,\"showUpdateSetValueDynamicallyToggle\",true,\"runWhenPageLoads\",false,\"transformer\",\"// type your code here\\n// example: return formatDataAsArray(data).filter(row => row.quantity > 20)\\nreturn data\",\"events\",[\"^C\",[]],\"queryTimeout\",\"10000\",\"requireConfirmation\",false,\"queryFailureConditions\",\"\",\"changesetIsObject\",false,\"enableCaching\",false,\"allowedGroups\",[\"^C\",[]],\"queryThrottleTime\",\"750\",\"updateSetValueDynamically\",false,\"notificationDuration\",\"\"]],\"^9\",null,\"^:\",null,\"^;\",null,\"^<\",null,\"^=\",null,\"^>\",\"~m1638976882574\",\"^?\",\"~m1643594794207\",\"^@\",\"\",\"^A\",\"pagerduty\",\"^B\",null]]],\"dateRangeInput\",[\"^0\",[\"^ \",\"n\",\"pluginTemplate\",\"v\",[\"^ \",\"id\",\"dateRangeInput\",\"^4\",\"widget\",\"^5\",\"DateRangeWidget\",\"^6\",null,\"^7\",null,\"^8\",[\"^3\",[\"minDate\",\"\",\"readOnly\",false,\"iconAfter\",\"\",\"dateFormat\",\"MMM d, yyyy\",\"hidden\",\"{{!useCustomDatesToggle.value}}\",\"customValidation\",\"{{moment(self.value.end).diff(self.value.start, 'd') > 60 ? 'Date range must be at most 60 days' : null}}\",\"hideValidationMessage\",false,\"textBefore\",\"\",\"validationMessage\",\"\",\"textAfter\",\"\",\"showInEditor\",false,\"showClear\",false,\"tooltipText\",\"\",\"labelAlign\",\"left\",\"formDataKey\",\"{{ self.id }}\",\"value\",[\"^3\",[\"start\",\"{{ moment().subtract(30, 'd') }}\",\"end\",\"{{moment()}}\"]],\"startPlaceholder\",\"Start date\",\"labelCaption\",\"\",\"hideLabel\",true,\"maxDate\",\"\",\"labelWidth\",\"33\",\"label\",\"Date range\",\"formattedValue\",\"\",\"_validate\",false,\"labelWidthUnit\",\"%\",\"invalid\",false,\"iconBefore\",\"bold/interface-calendar-remove\",\"endPlaceholder\",\"End date\",\"events\",[\"^3\",[]],\"textBetween\",\"-\",\"disabled\",false,\"labelPosition\",\"left\",\"labelWrap\",false,\"maintainSpaceWhenHidden\",false,\"required\",false]],\"^9\",[\"^3\",[]],\"^:\",[\"^0\",[\"^ \",\"n\",\"position2\",\"v\",[\"^ \",\"^@\",\"container6\",\"^D\",\"body\",\"^E\",\"\",\"row\",4.999999999999998,\"col\",1,\"^F\",1,\"^G\",10,\"^H\",0]]],\"^;\",null,\"^<\",null,\"^=\",null,\"^>\",\"~m1639602605697\",\"^?\",\"~m1643594739379\",\"^@\",\"\",\"^A\",\"\",\"^B\",null]]],\"selectedEnd\",[\"^0\",[\"^ \",\"n\",\"pluginTemplate\",\"v\",[\"^ \",\"id\",\"selectedEnd\",\"^4\",\"function\",\"^5\",\"Function\",\"^6\",null,\"^7\",null,\"^8\",[\"^3\",[\"funcBody\",\"// Returns the end date in YYYY-MM-DD format.\\nconst shouldUseCustomDateRange = {{useCustomDatesToggle.value}};\\nif (shouldUseCustomDateRange) {\\n return {{dateRangeInput.value.end}};\\n} else {\\n return moment();\\n}\",\"value\",\"\"]],\"^9\",null,\"^:\",null,\"^;\",null,\"^<\",null,\"^=\",null,\"^>\",\"~m1639606902007\",\"^?\",\"~m1643594709100\",\"^@\",\"\",\"^A\",\"user_filters\",\"^B\",null]]],\"selectedPriorities\",[\"^0\",[\"^ \",\"n\",\"pluginTemplate\",\"v\",[\"^ \",\"id\",\"selectedPriorities\",\"^4\",\"function\",\"^5\",\"Function\",\"^6\",null,\"^7\",null,\"^8\",[\"^3\",[\"funcBody\",\"// Returns an array of PagerDuty priority IDs\\nconst shouldUseCustomPriorities = {{useCustomPrioritiesToggle.value}};\\nif (shouldUseCustomPriorities) {\\n return {{priorityMultiSelect.value}};\\n} else {\\n const selectedPriority = {{prioritySelect.value}};\\n if (selectedPriority === 'Any') {\\n return {{getPagerDutyIncidentPriorities.data.map(p => p.id)}};\\n }\\n return [selectedPriority];\\n}\",\"value\",\"\"]],\"^9\",null,\"^:\",null,\"^;\",null,\"^<\",null,\"^=\",null,\"^>\",\"~m1639607346737\",\"^?\",\"~m1639625669571\",\"^@\",\"\",\"^A\",\"user_filters\",\"^B\",null]]],\"pdPriorityIdToName\",[\"^0\",[\"^ \",\"n\",\"pluginTemplate\",\"v\",[\"^ \",\"id\",\"pdPriorityIdToName\",\"^4\",\"function\",\"^5\",\"Function\",\"^6\",null,\"^7\",null,\"^8\",[\"^3\",[\"funcBody\",\"const idToName = {};\\nconst priorities = {{getPagerDutyIncidentPriorities.data}};\\npriorities.forEach(p => {\\n idToName[p.id] = p.name;\\n});\\n\\nreturn idToName;\",\"value\",\"\"]],\"^9\",null,\"^:\",null,\"^;\",null,\"^<\",null,\"^=\",null,\"^>\",\"~m1639622080150\",\"^?\",\"~m1639622149776\",\"^@\",\"\",\"^A\",\"pagerduty\",\"^B\",null]]]]],\"^>\",null,\"version\",\"2.84.7\",\"appThemeId\",null,\"preloadedAppJavaScript\",\"window.SLACK = {\\n TEAM_ID: 'T02NCPCBJUS',\\n createDeepLink: (channelId) => {\\n return `slack://channel?team=${window.SLACK.TEAM_ID}&id=${channelId}`\\n },\\n};\\n\\nwindow.PAGERDUTY = {\\n hasActiveStatus: (pdIncident) => {\\n return pdIncident.status != 'resolved';\\n }\\n};\\n\\nconst SALMON = '#EF4E4E';\\nconst SLATE = '#243B53';\\n\\nwindow.TAGS = {\\n // These status names correspond to statuses in PagerDuty.\\n triggered: {\\n color: SALMON,\\n text: 'Triggered'\\n },\\n acknowledged: {\\n color: SALMON,\\n text: 'Acknowledged',\\n },\\n resolved: {\\n color: SLATE,\\n text: 'Resolved',\\n }\\n}\\n\\nwindow.COLOR = {\\n purple: '#0C008C',\\n link_green: '#199473',\\n link_gray: '#627D98',\\n bkgrnd_gray: '#F0F4F8',\\n border_green: '#EFFCF6',\\n};\",\"preloadedAppJSLinks\",[],\"appStyles\",\".retool-canvas {\\n max-width: none !important;\\n}\\n\\n._retool-container-listView1 {\\n margin-top: -30px;\\n}\",\"testEntities\",[],\"tests\",[],\"responsiveLayoutDisabled\",false,\"loadingIndicatorsDisabled\",false,\"urlFragmentDefinitions\",[\"^C\",[]],\"pageLoadValueOverrides\",[\"^C\",[]],\"isGlobalWidget\",false,\"isMobileApp\",false,\"multiScreenMobileApp\",false,\"instrumentationEnabled\",false,\"customDocumentTitleEnabled\",false,\"customDocumentTitle\",\"\",\"customShortcuts\",[],\"folders\",[\"^C\",[\"pagerduty\",\"database\",\"filtered_data\",\"user_filters\"]],\"markdownLinkBehavior\",\"never\",\"inAppRetoolPillAppearance\",\"NO_OVERRIDE\",\"rootScreen\",null]]]"},"changesRecord":[{"type":"DATASOURCE_TYPE_CHANGE","payload":{"newType":"SqlQueryUnified","pluginId":"getDBIncidents","resourceName":"d9ddb8f4-0a90-45a2-aac2-3185c7341752"}},{"type":"WIDGET_TEMPLATE_UPDATE","payload":{"plugin":{"id":"getDBIncidents","type":"datasource","style":null,"folder":"database","screen":null,"subtype":"SqlQueryUnified","tabIndex":null,"template":{"data":null,"query":"","events":[],"rawData":null,"records":"","filterBy":"","metadata":null,"recordId":"","changeset":"","dataArray":[],"tableName":"","timestamp":0,"actionType":"","editorMode":"sql","isFetching":false,"isImported":false,"cacheKeyTtl":"","transformer":"// type your code here\n// example: return formatDataAsArray(data).filter(row => row.quantity > 20)\nreturn data","queryTimeout":"10000","allowedGroups":[],"enableCaching":false,"privateParams":[],"queryDisabled":"","watchedParams":[],"successMessage":"","changesetObject":"","doNotThrowOnNoOp":false,"errorTransformer":"// The variable 'data' allows you to reference the request's data in the transformer. \n// example: return data.find(element => element.isError)\nreturn data.error","queryRefreshTime":"","runWhenPageLoads":false,"changesetIsObject":false,"enableBulkUpdates":false,"enableTransformer":false,"queryThrottleTime":"750","queryTriggerDelay":"0","shouldUseLegacySql":false,"showFailureToaster":true,"showSuccessToaster":true,"importedQueryInputs":{},"playgroundQueryUuid":"","requireConfirmation":false,"runWhenModelUpdates":true,"bulkUpdatePrimaryKey":"","databaseHostOverride":"","databaseNameOverride":"","notificationDuration":"","queryDisabledMessage":"","resourceNameOverride":"","importedQueryDefaults":{},"playgroundQuerySaveId":"latest","runWhenPageLoadsDelay":"","enableErrorTransformer":false,"queryFailureConditions":"","databasePasswordOverride":"","databaseUsernameOverride":"","shouldEnableBatchQuerying":false,"updateSetValueDynamically":false,"showLatestVersionUpdatedWarning":false,"showUpdateSetValueDynamicallyToggle":true},"container":"","createdAt":"2021-12-15T22:10:59.563Z","namespace":null,"position2":null,"updatedAt":"2022-01-31T02:03:37.976Z","resourceName":"d9ddb8f4-0a90-45a2-aac2-3185c7341752","mobilePosition2":null,"mobileAppPosition":null},"update":{"data":null,"query":"select\n\tid,\n\tcreated_at,\n pagerduty_id,\n\tpagerduty_url,\n\tslack_channel_name,\n slack_channel_url,\n slack_channel_id\nfrom incidents\nwhere created_at >= {{selectedStart.value}}\n and created_at <= {{selectedEnd.value}}\norder by created_at desc;","events":[{"type":"datasource","event":"success","method":"trigger","params":{},"waitMs":"0","pluginId":"getPDIncidents","waitType":"debounce"}],"rawData":null,"records":"","filterBy":"","metadata":null,"recordId":"","changeset":"","dataArray":[],"tableName":"","timestamp":0,"actionType":"","editorMode":"sql","isFetching":false,"isImported":false,"cacheKeyTtl":"","transformer":"// type your code here\n// example: return formatDataAsArray(data).filter(row => row.quantity > 20)\nreturn data","queryTimeout":"10000","warningCodes":[],"allowedGroups":[],"enableCaching":false,"privateParams":[],"queryDisabled":"","watchedParams":[],"successMessage":"","changesetObject":"","doNotThrowOnNoOp":false,"errorTransformer":"// The variable 'data' allows you to reference the request's data in the transformer. \n// example: return data.find(element => element.isError)\nreturn data.error","queryRefreshTime":"","runWhenPageLoads":false,"changesetIsObject":false,"enableBulkUpdates":false,"enableTransformer":false,"playgroundQueryId":null,"queryThrottleTime":"750","queryTriggerDelay":"0","shouldUseLegacySql":false,"showFailureToaster":true,"showSuccessToaster":false,"confirmationMessage":null,"importedQueryInputs":{},"playgroundQueryUuid":"","requireConfirmation":false,"runWhenModelUpdates":true,"bulkUpdatePrimaryKey":"","databaseHostOverride":"","databaseNameOverride":"","notificationDuration":"","queryDisabledMessage":"","resourceNameOverride":"","importedQueryDefaults":{},"playgroundQuerySaveId":"latest","runWhenPageLoadsDelay":"","enableErrorTransformer":false,"queryFailureConditions":"","databasePasswordOverride":"","databaseUsernameOverride":"","shouldEnableBatchQuerying":false,"updateSetValueDynamically":false,"lastReceivedFromResourceAt":null,"showLatestVersionUpdatedWarning":false,"showUpdateSetValueDynamicallyToggle":true},"widgetId":"getDBIncidents"},"isUserTriggered":true}],"gitSha":null,"checksum":null,"createdAt":"2022-01-31T02:08:32.362Z","updatedAt":"2022-01-31T02:08:32.362Z","pageId":692536,"userId":66039,"branchId":null},"modules":{"Incident-Navbar":{"moduleSaveId":55923533,"moduleName":"Incident-Navbar","moduleUuid":"cd581076-585d-11ec-a633-936fd80bda50","data":{"appState":"[\"~#iR\",[\"^ \",\"n\",\"appTemplate\",\"v\",[\"^ \",\"isFetching\",false,\"plugins\",[\"~#iOM\",[\"moduleContainer\",[\"^0\",[\"^ \",\"n\",\"pluginTemplate\",\"v\",[\"^ \",\"id\",\"moduleContainer\",\"type\",\"widget\",\"subtype\",\"ModuleContainerWidget\",\"resourceName\",\"\",\"resourceDisplayName\",null,\"template\",[\"^3\",[\"heightType\",\"fixed\",\"spinWhenChildrenAreFetching\",true,\"showBorder\",true,\"showDropShadow\",true,\"isGlobalWidgetContainer\",true,\"style\",[\"^3\",[]],\"backgroundColor\",\"white\",\"title\",\"\",\"overflowType\",\"hidden\",\"disabled\",\"\"]],\"style\",[\"^3\",[]],\"position2\",[\"^0\",[\"^ \",\"n\",\"position2\",\"v\",[\"^ \",\"container\",\"\",\"rowGroup\",\"body\",\"subcontainer\",\"\",\"row\",0,\"col\",0,\"height\",12,\"width\",12,\"tabNum\",0]]],\"mobilePosition2\",[\"^0\",[\"^ \",\"n\",\"position2\",\"v\",[\"^ \",\"^;\",\"\",\"^<\",\"body\",\"^=\",\"\",\"row\",0,\"col\",0,\"^>\",12,\"^?\",12,\"^@\",0]]],\"mobileAppPosition\",null,\"tabIndex\",null,\"createdAt\",\"~m1638992045069\",\"updatedAt\",\"~m1638992045069\",\"^;\",\"\",\"folder\",\"\",\"screen\",null]]],\"navigation1\",[\"^0\",[\"^ \",\"n\",\"pluginTemplate\",\"v\",[\"^ \",\"id\",\"navigation1\",\"^4\",\"widget\",\"^5\",\"NavigationWidget\",\"^6\",null,\"^7\",null,\"^8\",[\"^3\",[\"horizontalAlign\",\"left\",\"valueToCopy\",\"\",\"menuItems\",[\"~#iL\",[[\"^3\",[\"title\",\"Home\",\"hidden\",\"\",\"clickable\",[\"^3\",[\"valueToCopy\",\"\",\"internalUrlHashParams\",\"\",\"exportFileType\",\"csv\",\"buttonType\",\"internal-url\",\"exportTemplate\",null,\"url\",\"\",\"newWindow\",false,\"exportQuery\",\"\",\"action\",\"\",\"internalUrlQuery\",\"\",\"internalUrlPath\",\"d2b883fc-4fc0-11ec-8282-5311b8afe0de\",\"exportFileName\",\"\",\"disabled\",\"\"]],\"menuItems\",[\"^H\",[]]]],[\"^3\",[\"title\",\"Incident Details\",\"clickable\",[\"^3\",[\"valueToCopy\",\"\",\"internalUrlHashParams\",\"\",\"exportFileType\",\"csv\",\"buttonType\",\"internal-url\",\"exportTemplate\",null,\"url\",\"\",\"newWindow\",false,\"exportQuery\",\"\",\"action\",\"\",\"internalUrlQuery\",\"\",\"internalUrlPath\",\"f601490a-5913-11ec-9083-a700d6721666\",\"exportFileName\",\"\",\"disabled\",\"\"]],\"hidden\",\"\",\"menuItems\",[\"^H\",[]]]]]],\"srcFromUrl\",true,\"internalUrlHashParams\",\"\",\"exportFileType\",\"csv\",\"logoImageFile\",\"\",\"buttonType\",\"action\",\"exportTemplate\",null,\"url\",\"\",\"newWindow\",false,\"style\",[\"^3\",[\"primary-surface\",\"\",\"primary-text\",\"rgba(9, 9, 9, 0.7)\",\"accent-background\",\"rgba(54, 60, 244, 1)\"]],\"logoImageSrc\",\"\",\"exportQuery\",\"\",\"action\",\"\",\"internalUrlQuery\",\"\",\"internalUrlPath\",\"\",\"exportFileName\",\"\",\"disabled\",\"\"]],\"^9\",[\"^3\",[]],\"^:\",[\"^0\",[\"^ \",\"n\",\"position2\",\"v\",[\"^ \",\"^;\",\"moduleContainer\",\"^<\",\"body\",\"^=\",\"\",\"row\",0,\"col\",2,\"^>\",1,\"^?\",8,\"^@\",0]]],\"^A\",null,\"^B\",null,\"^C\",null,\"^D\",\"~m1638992064464\",\"^E\",\"~m1643591902637\",\"^;\",\"\",\"^F\",\"\",\"^G\",null]]],\"reportIncidentBtn\",[\"^0\",[\"^ \",\"n\",\"pluginTemplate\",\"v\",[\"^ \",\"id\",\"reportIncidentBtn\",\"^4\",\"widget\",\"^5\",\"ButtonWidget2\",\"^6\",null,\"^7\",null,\"^8\",[\"^3\",[\"horizontalAlign\",\"stretch\",\"clickable\",false,\"iconAfter\",\"\",\"submitTargetId\",null,\"hidden\",false,\"text\",\"Report Incident\",\"showInEditor\",false,\"tooltipText\",\"\",\"style\",[\"^3\",[\"background\",\"{{reportIncidentBtnColor.value}}\",\"border\",\"#ffffff\",\"borderRadius\",\"3px\"]],\"styleVariant\",\"solid\",\"submit\",false,\"iconBefore\",\"\",\"events\",[\"^H\",[[\"^3\",[\"event\",\"click\",\"type\",\"util\",\"method\",\"openApp\",\"pluginId\",\"\",\"params\",[\"^3\",[\"uuid\",\"a961f07e-4bf6-11ec-a646-27b92ee07218\"]],\"waitType\",\"debounce\",\"waitMs\",\"0\"]]]],\"loading\",false,\"loaderPosition\",\"auto\",\"disabled\",false,\"maintainSpaceWhenHidden\",false]],\"^9\",[\"^3\",[]],\"^:\",[\"^0\",[\"^ \",\"n\",\"position2\",\"v\",[\"^ \",\"^;\",\"moduleContainer\",\"^<\",\"body\",\"^=\",\"\",\"row\",0,\"col\",10,\"^>\",1,\"^?\",2,\"^@\",0]]],\"^A\",null,\"^B\",null,\"^C\",null,\"^D\",\"~m1638992292834\",\"^E\",\"~m1640527161639\",\"^;\",\"\",\"^F\",\"\",\"^G\",null]]],\"appTitleText\",[\"^0\",[\"^ \",\"n\",\"pluginTemplate\",\"v\",[\"^ \",\"id\",\"appTitleText\",\"^4\",\"widget\",\"^5\",\"TextWidget2\",\"^6\",null,\"^7\",null,\"^8\",[\"^3\",[\"heightType\",\"fixed\",\"horizontalAlign\",\"center\",\"hidden\",false,\"imageWidth\",\"fit\",\"showInEditor\",false,\"verticalAlign\",\"center\",\"tooltipText\",\"\",\"value\",\"#### Incident Central\",\"style\",[\"^3\",[\"background\",\"\",\"color\",\"rgb(12, 0, 140)\"]],\"disableMarkdown\",false,\"overflowType\",\"scroll\",\"maintainSpaceWhenHidden\",false]],\"^9\",[\"^3\",[]],\"^:\",[\"^0\",[\"^ \",\"n\",\"position2\",\"v\",[\"^ \",\"^;\",\"moduleContainer\",\"^<\",\"body\",\"^=\",\"\",\"row\",5.551115123125783e-17,\"col\",0,\"^>\",1,\"^?\",2,\"^@\",0]]],\"^A\",null,\"^B\",null,\"^C\",null,\"^D\",\"~m1639319524508\",\"^E\",\"~m1639320166341\",\"^;\",\"\",\"^F\",\"\",\"^G\",null]]],\"reportIncidentBtnColor\",[\"^0\",[\"^ \",\"n\",\"pluginTemplate\",\"v\",[\"^ \",\"id\",\"reportIncidentBtnColor\",\"^4\",\"function\",\"^5\",\"Function\",\"^6\",null,\"^7\",null,\"^8\",[\"^3\",[\"funcBody\",\"const SALMON = '#EF4E4E';\\nconst SLATE_GRAY = '#627D98';\\n\\n// IMPORTANT: Edit this if you rename the Report Incident app.\\nconst REPORT_INCIDENT_APP_NAME = 'Report-Incident';\\nconst retoolContext = {{retoolContext}};\\n\\n// The retoolContext.appName is the whole path to the app: folder/app_name\\nconst appPathParts = retoolContext.appName.split('/');\\nconst appNameOnly = appPathParts[appPathParts.length - 1];\\n\\nif (appNameOnly.endsWith(REPORT_INCIDENT_APP_NAME)) {\\n return SLATE_GRAY;\\n} else {\\n return SALMON;\\n}\",\"value\",\"\"]],\"^9\",null,\"^:\",null,\"^A\",null,\"^B\",null,\"^C\",null,\"^D\",\"~m1640526820496\",\"^E\",\"~m1640527474281\",\"^;\",\"\",\"^F\",\"\",\"^G\",null]]]]],\"^D\",null,\"version\",\"2.84.7\",\"appThemeId\",null,\"preloadedAppJavaScript\",\"// Warning: As of Dec 2021, the JS here will not be imported into the app \\n// where this module is used. Instead, in each app where you use this \\n// module, you must define a window.COLOR with a `salmon` field, like \\n// below, in that app's own JavaScript.\\nconst SALMON = '#EF4E4E';\\n\\nwindow.COLOR = {\\n salmon: SALMON,\\n}\",\"preloadedAppJSLinks\",[],\"appStyles\",\"\",\"testEntities\",[],\"tests\",[],\"responsiveLayoutDisabled\",false,\"loadingIndicatorsDisabled\",false,\"urlFragmentDefinitions\",[\"^H\",[]],\"pageLoadValueOverrides\",[\"^H\",[]],\"isGlobalWidget\",true,\"isMobileApp\",false,\"multiScreenMobileApp\",false,\"instrumentationEnabled\",false,\"customDocumentTitleEnabled\",false,\"customDocumentTitle\",\"\",\"customShortcuts\",[],\"folders\",[\"^H\",[]],\"markdownLinkBehavior\",\"never\",\"inAppRetoolPillAppearance\",\"NO_OVERRIDE\",\"rootScreen\",null]]]"}}}}
--------------------------------------------------------------------------------
/incident-central/code/Incident-Navbar.json:
--------------------------------------------------------------------------------
1 | {"uuid":"cd581076-585d-11ec-a633-936fd80bda50","page":{"id":55923533,"data":{"appState":"[\"~#iR\",[\"^ \",\"n\",\"appTemplate\",\"v\",[\"^ \",\"isFetching\",false,\"plugins\",[\"~#iOM\",[\"moduleContainer\",[\"^0\",[\"^ \",\"n\",\"pluginTemplate\",\"v\",[\"^ \",\"id\",\"moduleContainer\",\"type\",\"widget\",\"subtype\",\"ModuleContainerWidget\",\"resourceName\",\"\",\"resourceDisplayName\",null,\"template\",[\"^3\",[\"heightType\",\"fixed\",\"spinWhenChildrenAreFetching\",true,\"showBorder\",true,\"showDropShadow\",true,\"isGlobalWidgetContainer\",true,\"style\",[\"^3\",[]],\"backgroundColor\",\"white\",\"title\",\"\",\"overflowType\",\"hidden\",\"disabled\",\"\"]],\"style\",[\"^3\",[]],\"position2\",[\"^0\",[\"^ \",\"n\",\"position2\",\"v\",[\"^ \",\"container\",\"\",\"rowGroup\",\"body\",\"subcontainer\",\"\",\"row\",0,\"col\",0,\"height\",12,\"width\",12,\"tabNum\",0]]],\"mobilePosition2\",[\"^0\",[\"^ \",\"n\",\"position2\",\"v\",[\"^ \",\"^;\",\"\",\"^<\",\"body\",\"^=\",\"\",\"row\",0,\"col\",0,\"^>\",12,\"^?\",12,\"^@\",0]]],\"mobileAppPosition\",null,\"tabIndex\",null,\"createdAt\",\"~m1638992045069\",\"updatedAt\",\"~m1638992045069\",\"^;\",\"\",\"folder\",\"\",\"screen\",null]]],\"navigation1\",[\"^0\",[\"^ \",\"n\",\"pluginTemplate\",\"v\",[\"^ \",\"id\",\"navigation1\",\"^4\",\"widget\",\"^5\",\"NavigationWidget\",\"^6\",null,\"^7\",null,\"^8\",[\"^3\",[\"horizontalAlign\",\"left\",\"valueToCopy\",\"\",\"menuItems\",[\"~#iL\",[[\"^3\",[\"title\",\"Home\",\"hidden\",\"\",\"clickable\",[\"^3\",[\"valueToCopy\",\"\",\"internalUrlHashParams\",\"\",\"exportFileType\",\"csv\",\"buttonType\",\"internal-url\",\"exportTemplate\",null,\"url\",\"\",\"newWindow\",false,\"exportQuery\",\"\",\"action\",\"\",\"internalUrlQuery\",\"\",\"internalUrlPath\",\"d2b883fc-4fc0-11ec-8282-5311b8afe0de\",\"exportFileName\",\"\",\"disabled\",\"\"]],\"menuItems\",[\"^H\",[]]]],[\"^3\",[\"title\",\"Incident Details\",\"clickable\",[\"^3\",[\"valueToCopy\",\"\",\"internalUrlHashParams\",\"\",\"exportFileType\",\"csv\",\"buttonType\",\"internal-url\",\"exportTemplate\",null,\"url\",\"\",\"newWindow\",false,\"exportQuery\",\"\",\"action\",\"\",\"internalUrlQuery\",\"\",\"internalUrlPath\",\"f601490a-5913-11ec-9083-a700d6721666\",\"exportFileName\",\"\",\"disabled\",\"\"]],\"hidden\",\"\",\"menuItems\",[\"^H\",[]]]]]],\"srcFromUrl\",true,\"internalUrlHashParams\",\"\",\"exportFileType\",\"csv\",\"logoImageFile\",\"\",\"buttonType\",\"action\",\"exportTemplate\",null,\"url\",\"\",\"newWindow\",false,\"style\",[\"^3\",[\"primary-surface\",\"\",\"primary-text\",\"rgba(9, 9, 9, 0.7)\",\"accent-background\",\"rgba(54, 60, 244, 1)\"]],\"logoImageSrc\",\"\",\"exportQuery\",\"\",\"action\",\"\",\"internalUrlQuery\",\"\",\"internalUrlPath\",\"\",\"exportFileName\",\"\",\"disabled\",\"\"]],\"^9\",[\"^3\",[]],\"^:\",[\"^0\",[\"^ \",\"n\",\"position2\",\"v\",[\"^ \",\"^;\",\"moduleContainer\",\"^<\",\"body\",\"^=\",\"\",\"row\",0,\"col\",2,\"^>\",1,\"^?\",8,\"^@\",0]]],\"^A\",null,\"^B\",null,\"^C\",null,\"^D\",\"~m1638992064464\",\"^E\",\"~m1643591902637\",\"^;\",\"\",\"^F\",\"\",\"^G\",null]]],\"reportIncidentBtn\",[\"^0\",[\"^ \",\"n\",\"pluginTemplate\",\"v\",[\"^ \",\"id\",\"reportIncidentBtn\",\"^4\",\"widget\",\"^5\",\"ButtonWidget2\",\"^6\",null,\"^7\",null,\"^8\",[\"^3\",[\"horizontalAlign\",\"stretch\",\"clickable\",false,\"iconAfter\",\"\",\"submitTargetId\",null,\"hidden\",false,\"text\",\"Report Incident\",\"showInEditor\",false,\"tooltipText\",\"\",\"style\",[\"^3\",[\"background\",\"{{reportIncidentBtnColor.value}}\",\"border\",\"#ffffff\",\"borderRadius\",\"3px\"]],\"styleVariant\",\"solid\",\"submit\",false,\"iconBefore\",\"\",\"events\",[\"^H\",[[\"^3\",[\"event\",\"click\",\"type\",\"util\",\"method\",\"openApp\",\"pluginId\",\"\",\"params\",[\"^3\",[\"uuid\",\"a961f07e-4bf6-11ec-a646-27b92ee07218\"]],\"waitType\",\"debounce\",\"waitMs\",\"0\"]]]],\"loading\",false,\"loaderPosition\",\"auto\",\"disabled\",false,\"maintainSpaceWhenHidden\",false]],\"^9\",[\"^3\",[]],\"^:\",[\"^0\",[\"^ \",\"n\",\"position2\",\"v\",[\"^ \",\"^;\",\"moduleContainer\",\"^<\",\"body\",\"^=\",\"\",\"row\",0,\"col\",10,\"^>\",1,\"^?\",2,\"^@\",0]]],\"^A\",null,\"^B\",null,\"^C\",null,\"^D\",\"~m1638992292834\",\"^E\",\"~m1640527161639\",\"^;\",\"\",\"^F\",\"\",\"^G\",null]]],\"appTitleText\",[\"^0\",[\"^ \",\"n\",\"pluginTemplate\",\"v\",[\"^ \",\"id\",\"appTitleText\",\"^4\",\"widget\",\"^5\",\"TextWidget2\",\"^6\",null,\"^7\",null,\"^8\",[\"^3\",[\"heightType\",\"fixed\",\"horizontalAlign\",\"center\",\"hidden\",false,\"imageWidth\",\"fit\",\"showInEditor\",false,\"verticalAlign\",\"center\",\"tooltipText\",\"\",\"value\",\"#### Incident Central\",\"style\",[\"^3\",[\"background\",\"\",\"color\",\"rgb(12, 0, 140)\"]],\"disableMarkdown\",false,\"overflowType\",\"scroll\",\"maintainSpaceWhenHidden\",false]],\"^9\",[\"^3\",[]],\"^:\",[\"^0\",[\"^ \",\"n\",\"position2\",\"v\",[\"^ \",\"^;\",\"moduleContainer\",\"^<\",\"body\",\"^=\",\"\",\"row\",5.551115123125783e-17,\"col\",0,\"^>\",1,\"^?\",2,\"^@\",0]]],\"^A\",null,\"^B\",null,\"^C\",null,\"^D\",\"~m1639319524508\",\"^E\",\"~m1639320166341\",\"^;\",\"\",\"^F\",\"\",\"^G\",null]]],\"reportIncidentBtnColor\",[\"^0\",[\"^ \",\"n\",\"pluginTemplate\",\"v\",[\"^ \",\"id\",\"reportIncidentBtnColor\",\"^4\",\"function\",\"^5\",\"Function\",\"^6\",null,\"^7\",null,\"^8\",[\"^3\",[\"funcBody\",\"const SALMON = '#EF4E4E';\\nconst SLATE_GRAY = '#627D98';\\n\\n// IMPORTANT: Edit this if you rename the Report Incident app.\\nconst REPORT_INCIDENT_APP_NAME = 'Report-Incident';\\nconst retoolContext = {{retoolContext}};\\n\\n// The retoolContext.appName is the whole path to the app: folder/app_name\\nconst appPathParts = retoolContext.appName.split('/');\\nconst appNameOnly = appPathParts[appPathParts.length - 1];\\n\\nif (appNameOnly.endsWith(REPORT_INCIDENT_APP_NAME)) {\\n return SLATE_GRAY;\\n} else {\\n return SALMON;\\n}\",\"value\",\"\"]],\"^9\",null,\"^:\",null,\"^A\",null,\"^B\",null,\"^C\",null,\"^D\",\"~m1640526820496\",\"^E\",\"~m1640527474281\",\"^;\",\"\",\"^F\",\"\",\"^G\",null]]]]],\"^D\",null,\"version\",\"2.84.7\",\"appThemeId\",null,\"preloadedAppJavaScript\",\"// Warning: As of Dec 2021, the JS here will not be imported into the app \\n// where this module is used. Instead, in each app where you use this \\n// module, you must define a window.COLOR with a `salmon` field, like \\n// below, in that app's own JavaScript.\\nconst SALMON = '#EF4E4E';\\n\\nwindow.COLOR = {\\n salmon: SALMON,\\n}\",\"preloadedAppJSLinks\",[],\"appStyles\",\"\",\"testEntities\",[],\"tests\",[],\"responsiveLayoutDisabled\",false,\"loadingIndicatorsDisabled\",false,\"urlFragmentDefinitions\",[\"^H\",[]],\"pageLoadValueOverrides\",[\"^H\",[]],\"isGlobalWidget\",true,\"isMobileApp\",false,\"multiScreenMobileApp\",false,\"instrumentationEnabled\",false,\"customDocumentTitleEnabled\",false,\"customDocumentTitle\",\"\",\"customShortcuts\",[],\"folders\",[\"^H\",[]],\"markdownLinkBehavior\",\"never\",\"inAppRetoolPillAppearance\",\"NO_OVERRIDE\",\"rootScreen\",null]]]"},"changesRecord":[{"type":"WIDGET_TEMPLATE_UPDATE","payload":{"plugin":{"id":"navigation1","type":"widget","style":{},"folder":"","screen":null,"subtype":"NavigationWidget","tabIndex":null,"template":{"url":"","style":{"primary-text":"rgba(9, 9, 9, 0.7)","primary-surface":"","accent-background":"rgba(54, 60, 244, 1)"},"action":"","disabled":"","menuItems":[{"title":"Home","hidden":"","clickable":{"url":"","action":"","disabled":"","newWindow":false,"buttonType":"internal-url","exportQuery":"","valueToCopy":"","exportFileName":"","exportFileType":"csv","exportTemplate":null,"internalUrlPath":"d2b883fc-4fc0-11ec-8282-5311b8afe0de","internalUrlQuery":"","internalUrlHashParams":""},"menuItems":[]},{"title":"Incident Details","hidden":"","clickable":{"url":"","action":"","disabled":"","newWindow":false,"buttonType":"internal-url","exportQuery":"","valueToCopy":"","exportFileName":"","exportFileType":"csv","exportTemplate":null,"internalUrlPath":"f601490a-5913-11ec-9083-a700d6721666","internalUrlQuery":"","internalUrlHashParams":""},"menuItems":[]},{"title":"Statistics","hidden":"","clickable":{"url":"","action":"","disabled":"","newWindow":false,"buttonType":"internal-url","exportQuery":"","valueToCopy":"","exportFileName":"","exportFileType":"csv","exportTemplate":null,"internalUrlPath":"885f8bbc-5929-11ec-b6d0-bbe59e0a4d46","internalUrlQuery":"","internalUrlHashParams":""},"menuItems":[]}],"newWindow":false,"buttonType":"action","srcFromUrl":true,"exportQuery":"","valueToCopy":"","logoImageSrc":"","logoImageFile":"","exportFileName":"","exportFileType":"csv","exportTemplate":null,"horizontalAlign":"left","internalUrlPath":"","internalUrlQuery":"","internalUrlHashParams":""},"container":"","createdAt":"2021-12-08T19:34:24.464Z","namespace":null,"position2":{"col":2,"row":0,"width":8,"height":1,"tabNum":0,"rowGroup":"body","container":"moduleContainer","subcontainer":""},"updatedAt":"2021-12-15T14:29:37.700Z","resourceName":null,"mobilePosition2":null,"mobileAppPosition":null},"update":{"menuItems":[{"title":"Home","hidden":"","clickable":{"url":"","action":"","disabled":"","newWindow":false,"buttonType":"internal-url","exportQuery":"","valueToCopy":"","exportFileName":"","exportFileType":"csv","exportTemplate":null,"internalUrlPath":"d2b883fc-4fc0-11ec-8282-5311b8afe0de","internalUrlQuery":"","internalUrlHashParams":""},"menuItems":[]},{"title":"Incident Details","hidden":"","clickable":{"url":"","action":"","disabled":"","newWindow":false,"buttonType":"internal-url","exportQuery":"","valueToCopy":"","exportFileName":"","exportFileType":"csv","exportTemplate":null,"internalUrlPath":"f601490a-5913-11ec-9083-a700d6721666","internalUrlQuery":"","internalUrlHashParams":""},"menuItems":[]}]},"widgetId":"navigation1"},"isUserTriggered":true}],"gitSha":null,"checksum":null,"createdAt":"2022-01-31T01:18:24.902Z","updatedAt":"2022-01-31T01:18:24.902Z","pageId":719152,"userId":66039,"branchId":null},"modules":{}}
--------------------------------------------------------------------------------
/incident-central/code/extras/Database-Setup.json:
--------------------------------------------------------------------------------
1 | {"uuid":"8b631cb8-56d9-11ec-b334-37497cb0e233","page":{"id":55923151,"data":{"appState":"[\"~#iR\",[\"^ \",\"n\",\"appTemplate\",\"v\",[\"^ \",\"isFetching\",false,\"plugins\",[\"~#iOM\",[\"q1_createUpdateTimestampTrigger\",[\"^0\",[\"^ \",\"n\",\"pluginTemplate\",\"v\",[\"^ \",\"id\",\"q1_createUpdateTimestampTrigger\",\"type\",\"datasource\",\"subtype\",\"SqlQueryUnified\",\"resourceName\",\"d9ddb8f4-0a90-45a2-aac2-3185c7341752\",\"resourceDisplayName\",\"Incident Database\",\"template\",[\"^3\",[\"queryRefreshTime\",\"\",\"records\",\"\",\"lastReceivedFromResourceAt\",null,\"databasePasswordOverride\",\"\",\"queryDisabledMessage\",\"\",\"successMessage\",\"\",\"queryDisabled\",\"\",\"playgroundQuerySaveId\",\"latest\",\"resourceNameOverride\",\"\",\"runWhenModelUpdates\",false,\"showFailureToaster\",true,\"query\",\"CREATE OR REPLACE FUNCTION trigger_set_timestamp()\\nRETURNS TRIGGER AS $$\\nBEGIN\\n NEW.updated_at = NOW();\\n RETURN NEW;\\nEND;\\n$$ LANGUAGE plpgsql;\",\"playgroundQueryUuid\",\"\",\"playgroundQueryId\",null,\"privateParams\",[\"~#iL\",[]],\"runWhenPageLoadsDelay\",\"\",\"warningCodes\",[\"^9\",[]],\"data\",null,\"recordId\",\"\",\"importedQueryInputs\",[\"^3\",[]],\"isImported\",false,\"showSuccessToaster\",true,\"dataArray\",[\"^9\",[]],\"cacheKeyTtl\",\"\",\"filterBy\",\"\",\"databaseHostOverride\",\"\",\"metadata\",null,\"editorMode\",\"sql\",\"actionType\",\"\",\"changesetObject\",\"\",\"shouldUseLegacySql\",false,\"errorTransformer\",\"// The variable 'data' allows you to reference the request's data in the transformer. \\n// example: return data.find(element => element.isError)\\nreturn data.error\",\"databaseNameOverride\",\"\",\"confirmationMessage\",null,\"isFetching\",false,\"changeset\",\"\",\"rawData\",null,\"queryTriggerDelay\",\"0\",\"watchedParams\",[\"^9\",[]],\"enableErrorTransformer\",false,\"enableBulkUpdates\",false,\"showLatestVersionUpdatedWarning\",false,\"timestamp\",0,\"importedQueryDefaults\",[\"^3\",[]],\"enableTransformer\",false,\"showUpdateSetValueDynamicallyToggle\",true,\"bulkUpdatePrimaryKey\",\"\",\"runWhenPageLoads\",false,\"transformer\",\"// type your code here\\n// example: return formatDataAsArray(data).filter(row => row.quantity > 20)\\nreturn data\",\"events\",[\"^9\",[]],\"tableName\",\"\",\"queryTimeout\",\"10001\",\"requireConfirmation\",false,\"queryFailureConditions\",\"\",\"changesetIsObject\",false,\"enableCaching\",false,\"allowedGroups\",[\"^9\",[]],\"databaseUsernameOverride\",\"\",\"shouldEnableBatchQuerying\",false,\"doNotThrowOnNoOp\",false,\"queryThrottleTime\",\"750\",\"updateSetValueDynamically\",false,\"notificationDuration\",\"\"]],\"style\",null,\"position2\",null,\"mobilePosition2\",null,\"mobileAppPosition\",null,\"tabIndex\",null,\"createdAt\",\"~m1638825289256\",\"updatedAt\",\"~m1638825576497\",\"container\",\"\",\"folder\",\"\",\"screen\",null]]],\"q2_createIncidentsTable\",[\"^0\",[\"^ \",\"n\",\"pluginTemplate\",\"v\",[\"^ \",\"id\",\"q2_createIncidentsTable\",\"^4\",\"datasource\",\"^5\",\"SqlQueryUnified\",\"^6\",\"d9ddb8f4-0a90-45a2-aac2-3185c7341752\",\"^7\",\"Incident Database\",\"^8\",[\"^3\",[\"queryRefreshTime\",\"\",\"records\",\"\",\"lastReceivedFromResourceAt\",null,\"databasePasswordOverride\",\"\",\"queryDisabledMessage\",\"\",\"successMessage\",\"\",\"queryDisabled\",\"\",\"playgroundQuerySaveId\",\"latest\",\"resourceNameOverride\",\"\",\"runWhenModelUpdates\",false,\"showFailureToaster\",true,\"query\",\"CREATE TABLE IF NOT EXISTS incidents (\\n id TEXT PRIMARY KEY,\\n created_at TIMESTAMP NOT NULL DEFAULT NOW(),\\n updated_at TIMESTAMP NOT NULL DEFAULT NOW(),\\n \\n pagerduty_id TEXT,\\n pagerduty_url TEXT,\\n \\n slack_channel_id TEXT,\\n slack_channel_name TEXT,\\n slack_channel_url TEXT,\\n \\n reporter_email TEXT,\\n incident_commander_email TEXT\\n);\\n\\nCREATE TRIGGER set_updated_at_on_incidents\\nBEFORE UPDATE ON incidents\\nFOR EACH ROW\\nEXECUTE PROCEDURE trigger_set_timestamp();\",\"playgroundQueryUuid\",\"\",\"playgroundQueryId\",null,\"privateParams\",[\"^9\",[]],\"runWhenPageLoadsDelay\",\"\",\"warningCodes\",[\"^9\",[]],\"data\",null,\"recordId\",\"\",\"importedQueryInputs\",[\"^3\",[]],\"isImported\",false,\"showSuccessToaster\",true,\"dataArray\",[\"^9\",[]],\"cacheKeyTtl\",\"\",\"filterBy\",\"\",\"databaseHostOverride\",\"\",\"metadata\",null,\"editorMode\",\"sql\",\"actionType\",\"\",\"changesetObject\",\"\",\"shouldUseLegacySql\",false,\"errorTransformer\",\"// The variable 'data' allows you to reference the request's data in the transformer. \\n// example: return data.find(element => element.isError)\\nreturn data.error\",\"databaseNameOverride\",\"\",\"confirmationMessage\",null,\"isFetching\",false,\"changeset\",\"\",\"rawData\",null,\"queryTriggerDelay\",\"0\",\"watchedParams\",[\"^9\",[]],\"enableErrorTransformer\",false,\"enableBulkUpdates\",false,\"showLatestVersionUpdatedWarning\",false,\"timestamp\",0,\"importedQueryDefaults\",[\"^3\",[]],\"enableTransformer\",false,\"showUpdateSetValueDynamicallyToggle\",true,\"bulkUpdatePrimaryKey\",\"\",\"runWhenPageLoads\",false,\"transformer\",\"// type your code here\\n// example: return formatDataAsArray(data).filter(row => row.quantity > 20)\\nreturn data\",\"events\",[\"^9\",[]],\"tableName\",\"\",\"queryTimeout\",\"10001\",\"requireConfirmation\",false,\"queryFailureConditions\",\"\",\"changesetIsObject\",false,\"enableCaching\",false,\"allowedGroups\",[\"^9\",[]],\"databaseUsernameOverride\",\"\",\"shouldEnableBatchQuerying\",false,\"doNotThrowOnNoOp\",false,\"queryThrottleTime\",\"750\",\"updateSetValueDynamically\",false,\"notificationDuration\",\"\"]],\"^:\",null,\"^;\",null,\"^<\",null,\"^=\",null,\"^>\",null,\"^?\",\"~m1638825581496\",\"^@\",\"~m1638914697107\",\"^A\",\"\",\"^B\",\"\",\"^C\",null]]]]],\"^?\",null,\"version\",\"2.84.7\",\"appThemeId\",null,\"preloadedAppJavaScript\",null,\"preloadedAppJSLinks\",[],\"appStyles\",\"\",\"testEntities\",[],\"tests\",[],\"responsiveLayoutDisabled\",false,\"loadingIndicatorsDisabled\",false,\"urlFragmentDefinitions\",[\"^9\",[]],\"pageLoadValueOverrides\",[\"^9\",[]],\"isGlobalWidget\",false,\"isMobileApp\",false,\"multiScreenMobileApp\",false,\"instrumentationEnabled\",false,\"customDocumentTitleEnabled\",false,\"customDocumentTitle\",\"\",\"customShortcuts\",[],\"folders\",[\"^9\",[]],\"markdownLinkBehavior\",\"never\",\"inAppRetoolPillAppearance\",\"NO_OVERRIDE\",\"rootScreen\",null]]]"},"changesRecord":[{"type":"PLUGIN_DELETE","payload":["table1"],"hideChangelogEntry":false}],"gitSha":null,"checksum":null,"createdAt":"2022-01-31T01:10:00.193Z","updatedAt":"2022-01-31T01:10:00.193Z","pageId":713828,"userId":66039,"branchId":null},"modules":{}}
--------------------------------------------------------------------------------
/incident-central/images/home-page.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tryretool/retool-app-exchange/ea48efde500e831b0435a0e7ae1e28d1e5021e83/incident-central/images/home-page.png
--------------------------------------------------------------------------------
/incident-central/images/incident-central-demo.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tryretool/retool-app-exchange/ea48efde500e831b0435a0e7ae1e28d1e5021e83/incident-central/images/incident-central-demo.gif
--------------------------------------------------------------------------------
/incident-central/images/incident-details.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tryretool/retool-app-exchange/ea48efde500e831b0435a0e7ae1e28d1e5021e83/incident-central/images/incident-details.png
--------------------------------------------------------------------------------
/incident-central/images/report-incident-created.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tryretool/retool-app-exchange/ea48efde500e831b0435a0e7ae1e28d1e5021e83/incident-central/images/report-incident-created.png
--------------------------------------------------------------------------------
/incident-central/images/report-incident.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tryretool/retool-app-exchange/ea48efde500e831b0435a0e7ae1e28d1e5021e83/incident-central/images/report-incident.png
--------------------------------------------------------------------------------
/incident-central/setup-guides/README.md:
--------------------------------------------------------------------------------
1 | # Incident Central - Setup guide
2 |
3 | This guide helps you set up Incident Central inside your Retool instance. (If you're wondering "What's Incident Central?", see [the main README](../README.md).)
4 |
5 | ## Overview
6 | As an overview, we'll go through the following steps:
7 |
8 | * Set up backend dependencies. (Steps 1-3)
9 | - Configuring your Slack and PagerDuty instances.
10 | - Creating a relational database, if you don't have one already.
11 | * Set up these backends as Resources in Retool. (Step 4)
12 | * Download and configure the Retool app files in Retool. (Step 5)
13 |
14 | ## Important assumptions
15 | Incident Central works off the following assumptions about your setup:
16 | * The email address of people in your PagerDuty account matches their email address within your Slack account.
17 |
18 | ## Step 1 - Set up PagerDuty
19 | [Step-by-step: Set up PagerDuty](./set-up-pagerduty.md)
20 |
21 | ## Step 2 - Set up Slack
22 | [Step-by-step: Set up Slack](./set-up-slack.md)
23 |
24 | ## Step 3 - Set up a relational database for incident data
25 | [Step-by-step: Set up a database](./set-up-database.md)
26 |
27 | ## Step 4 - Set up these backends as Resources in Retool
28 | [Step-by-step: Set up Retool Resources](./set-up-retool-resources.md)
29 |
30 | ## Step 5 - Set up the Retool app files
31 | [Step-by-step: Set up Retool Apps](./set-up-retool-apps.md)
32 |
--------------------------------------------------------------------------------
/incident-central/setup-guides/images/adjust-navbar-link.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tryretool/retool-app-exchange/ea48efde500e831b0435a0e7ae1e28d1e5021e83/incident-central/setup-guides/images/adjust-navbar-link.png
--------------------------------------------------------------------------------
/incident-central/setup-guides/images/adjust-report-incident-link.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tryretool/retool-app-exchange/ea48efde500e831b0435a0e7ae1e28d1e5021e83/incident-central/setup-guides/images/adjust-report-incident-link.png
--------------------------------------------------------------------------------
/incident-central/setup-guides/images/fill-in-slack-team-id.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tryretool/retool-app-exchange/ea48efde500e831b0435a0e7ae1e28d1e5021e83/incident-central/setup-guides/images/fill-in-slack-team-id.png
--------------------------------------------------------------------------------
/incident-central/setup-guides/images/pagerduty-api-resource.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tryretool/retool-app-exchange/ea48efde500e831b0435a0e7ae1e28d1e5021e83/incident-central/setup-guides/images/pagerduty-api-resource.png
--------------------------------------------------------------------------------
/incident-central/setup-guides/images/slack-api-resource.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tryretool/retool-app-exchange/ea48efde500e831b0435a0e7ae1e28d1e5021e83/incident-central/setup-guides/images/slack-api-resource.png
--------------------------------------------------------------------------------
/incident-central/setup-guides/set-up-database.md:
--------------------------------------------------------------------------------
1 | # Incident Central Setup - Step 3: Database
2 |
3 | ## What's this database for?
4 | Incident Central will write each new triggered incident to a database table. This table will be the one place where you keep track of all the different metadata associated with each incident. For example, each incident has a name, a start time, a PagerDuty profile, a Slack channel, and so on.
5 |
6 | In the future, you may choose to add some other information (e.g. a Google doc, or a Jira ticket) to each incident, in which case you can add additional columns to this database table, and modify Incident Central to write this additional information.
7 |
8 | ## Output
9 | The goal of this section is to create:
10 | * A database, if you don't already have one
11 | * A database table called `incidents` with the schema that Incident Central expects
12 |
13 | ## Step 3.1 - Create a database instance
14 | If you already have a database that you want to use for Incident Central, you are all set. (The example code we provide will specifically be suited for PostgreSQL, but you can adapt it to whatever database you are using, including a non-relational database if you want.)
15 |
16 | Otherwise, you can spin up a PostgreSQL database on a variety of platforms. [Heroku's managed PostgreSQL offering](https://www.heroku.com/postgres) is one example, and it has a free tier.
17 |
18 | ## Step 3.2 - Create a database table called `incidents` and define its schema
19 | Run the following command on your database:
20 | ```
21 | CREATE OR REPLACE FUNCTION trigger_set_timestamp()
22 | RETURNS TRIGGER AS $$
23 | BEGIN
24 | NEW.updated_at = NOW();
25 | RETURN NEW;
26 | END;
27 | $$ LANGUAGE plpgsql;
28 | ```
29 |
30 | Then run:
31 | ```
32 | CREATE TABLE IF NOT EXISTS incidents (
33 | id TEXT PRIMARY KEY,
34 | created_at TIMESTAMP NOT NULL DEFAULT NOW(),
35 | updated_at TIMESTAMP NOT NULL DEFAULT NOW(),
36 |
37 | pagerduty_id TEXT,
38 | pagerduty_url TEXT,
39 |
40 | slack_channel_id TEXT,
41 | slack_channel_name TEXT,
42 | slack_channel_url TEXT,
43 |
44 | reporter_email TEXT,
45 | incident_commander_email TEXT
46 | );
47 |
48 | CREATE TRIGGER set_updated_at_on_incidents
49 | BEFORE UPDATE ON incidents
50 | FOR EACH ROW
51 | EXECUTE PROCEDURE trigger_set_timestamp();
52 | ```
53 | ### Optional way to run these commands
54 | An easy way to run these commands on your database is to connect your database to Retool first, and then run the command through Retool.
55 |
56 | If you would like to do this:
57 | * First, go through Step 4: Retool Resources to get this database connected to Retool (as the "Incident Database" resource).
58 | * Upload the `Database-Setup` Retool app provided in [the `code/extras` subdirectory](../code/extras/) into your Retool instance. To do this, download the code, and go to the homepage of Retool and click "Create new" → "Import an app".
59 | * Open the `Database-Setup` app in **_Edit_** mode, and manually click to run the two Queries (shown in the bottom panel of the Editor view) in the app.
60 |
61 | ## Summary
62 | You now have a database with an `incidents` table that Incident Central can write to.
63 |
64 | ## Next step
65 | [Step-by-step: Set up Retool Resources](./set-up-retool-resources.md)
66 |
--------------------------------------------------------------------------------
/incident-central/setup-guides/set-up-pagerduty.md:
--------------------------------------------------------------------------------
1 | # Incident Central Setup - Step 1: PagerDuty
2 |
3 | ## What's PagerDuty?
4 | PagerDuty is the tool that Incident Central will use to trigger incidents for your services. PagerDuty will handle alerting the oncall staff you have configured within it.
5 |
6 | Note, you can define "services" however you like—this is just the terminology in PagerDuty for a thing that an oncall supports.
7 |
8 | ## Output
9 | The goal of this section is to create:
10 | * A PagerDuty account, if you don't have one already. (A free trial works.)
11 | * A PagerDuty API key.
12 | * Inside PagerDuty, you should configure at least 1 service, and configure its "escalation policy" to alert the people who are oncall for that service.
13 |
14 | ## Step 1.1 - PagerDuty account
15 | Head on over to [pagerduty.com](https://pagerduty.com/) and sign up, or sign in.
16 |
17 | ## Step 1.2 - PagerDuty API key
18 | Follow the instructions in the [PagerDuty API Access Keys docs](https://support.pagerduty.com/docs/api-access-keys) to create a "General Access REST API key."
19 |
20 | Note: We want this key to have "write" access so that we can create incidents. So, do not make the key "read-only".
21 |
22 | ## Step 1.3 - Invite oncalls to PagerDuty
23 | If you already have existing oncall people/rotations in PagerDuty, you are all set.
24 |
25 | If this is your first time setting up PagerDuty, you'll want to invite the people you want to be oncall. To do this, follow the instructions in [PagerDuty's Manage Users docs](https://support.pagerduty.com/docs/users), under "Add a User". (You may also consider grouping people onto [Teams](https://support.pagerduty.com/docs/teams).)
26 |
27 | ### Important assumption
28 | Incident Central works off the following assumptions about your setup:
29 | * The email address of people in your PagerDuty account matches their email address within your Slack account.
30 |
31 | ## Step 1.4 - PagerDuty service with escalation policy
32 | If you already have existing services and escalation policies defined in PagerDuty, you are all set.
33 |
34 | If you don't, follow the instructions in the [PagerDuty Services and Integrations docs](https://support.pagerduty.com/docs/services-and-integrations) under "Create a Service". This will take you through creating a service along with its "escalation policy." The escalation policy is the set of rules that defines who to notify, in what order, when the service has an incident.
35 |
36 | If you are feeling fun, create at least 2 services! This will be more realistic.
37 |
38 | Note: It's possible to define an oncall schedules—a rotation that consists of multiple people—in PagerDuty as well. You can then define an escalation policy that triggers a specific oncall schedule. To find out how to set up an oncall schedule, see [PagerDuty's Schedule Basics docs](https://support.pagerduty.com/docs/schedule-basics).
39 |
40 | ## Summary
41 | You now have the following items that you can plug into a later step:
42 | * PagerDuty API key
43 |
44 | You also have a "service" within PagerDuty that you want to be able to trigger an incident on, and this service has an "escalation policy" that defines who will get notified when an incident starts.
45 |
46 | ## Next step
47 | [Step-by-step: Set up Slack](./set-up-slack.md)
48 |
--------------------------------------------------------------------------------
/incident-central/setup-guides/set-up-retool-apps.md:
--------------------------------------------------------------------------------
1 | # Incident Central Setup - Step 5: Apps in Retool
2 |
3 | ## What's an App in Retool?
4 | As the name suggests, an App is a single "view" (or "page") that you can build in Retool. An App is built from a combination of Components to construct the UI, and Queries that define calls to your Resources. Queries can do reads or writes.
5 |
6 | ## Why Incident Central is 3 Retool Apps
7 |
8 | To the end-user, Incident Central looks like a single, unified website. However, under the hood, it is 3 distinct Retool Apps.
9 |
10 | This is because Incident Central consists of three distinct "views", and I have chosen to make these views independent of each other, so that it's easy for anyone (including me!) to mix and match them and refactor them. Decoupling unrelated pieces of functionality is in general a good practice in programming.
11 |
12 | The three distinct Retool Apps are: a home page, an incident details view, and a Report Incident view.
13 |
14 | These three Apps are united by one common Navigation bar (implemented as a reusable [Module](https://docs.retool.com/docs/modules)) that sits in the header. This Navigation bar lets the end-user experience Incident Central as a single, unified website.
15 |
16 | ## Output
17 | The goal of this section is:
18 | * To get Incident Central into your Retool instance. 🚀
19 |
20 |
21 | ## Step 5.1 - Download the app "code"
22 | In this step, you will obtain the code files in the `/code` directory in this Github repo.
23 |
24 | To do this, clone this Github repo:
25 | `git clone https://github.com/tryretool/retool-app-exchange.git`
26 |
27 | (Alternatively, you can manually click-to-download each file in the [`code/`](../code) directory.)
28 |
29 | You will have the following files:
30 |
31 | ```
32 | - Incident-Navbar.json
33 | - Incident-Central-Home.json
34 | - Incident-Central-Details.json
35 | - Report-Incident.json
36 | ```
37 |
38 | ## Step 5.2 - Import the app "code"
39 | First, let's create a folder to put all of this code into, to keep things organized.
40 | 1. On your Retool home page, click "Create new" → "Create a new folder". Give this folder a name you like, e.g. "Incident-Central".
41 |
42 | Now, let's import the Navigation bar Module.
43 | 2. Click "Create new" → "Import an app". Upload the `Incident-Navbar.json` file, and select the folder you just created ("Incident-Central") as the destination.
44 |
45 | Now, let's import each app.
46 | 3. For each of the three app files (`Incident-Central-Home.json`, `Incident-Central-Details.json`, `Report-Incident.json`), do these steps:
47 | * Click "Create new" → "Import an app".
48 | * Upload the file, and select the folder you just created ("Incident-Central") as the destination.
49 |
50 |
51 | ## Step 5.3 - Adjust links in the navigation bar
52 | Finally, let's hook up the links to our apps in the `Incident-Navbar`.
53 | 4. Go to the "Incident-Central" folder. Click the "Edit" button to the right of the `Incident-Navbar` Module to open the editor view.
54 | 5. Within the UI editor canvas, click on the 'navigation' component on the screen. You should see the details of this component appear in the right-hand sidebar.
55 | 6. Look in the right-hand sidebar for the "Menu items" detail, with "Home" and "Incident Details" listed. Click on each of these menu items, and adjust its "App to Open" to be the appropriate app.
56 |
57 |
58 | 7. Now, within the UI editor canvas, click on the "Report Incident" button component.
59 | 8. Look in the right-hand sidebar for the "Event handlers" detail. Click on the one event handler, and adjust the "App" that will be opened.
60 |
61 |
62 | ## Step 5.4 - Fill in your Slack Team ID
63 | There is one place in Incident Central where we use your Slack Team ID as a hardcoded value. This is the simplest way to get this value (which in most cases stays the same).
64 |
65 | 9. Open the "Report-Incident" app in Editor mode.
66 | 10. Click the "..." button in the upper right. Select "Scripts and styles" from the dropdown.
67 | 11. A modal will open. Select the "JavaScript" tab.
68 | 12. At the top of the code here, you'll see a place to fill in your Slack Team ID as a constant.
69 |
70 | 13. Click "Save".
71 |
72 | ## Step 5.5 - Test it out
73 | In this section, we're going to test out the functionality in your new apps. Ideally, put yourself oncall for whichever service you want to send a test "incident" to in PagerDuty, so that you don't surprise someone else! (Or else, give the person who's oncall a heads-up that you are testing.)
74 |
75 | Navigate to your "Incident-Central" folder, and click on the "Report-Incident" app. Choose a service to create a "test" incident for, and fill out the form. This should:
76 | - Page the person who is oncall via PagerDuty.
77 | - Create a Slack channel and add you and the oncall person to it.
78 |
79 | Now, click on the "Home" menu item in the navbar to open the Incident Central "Home" page. You should see the new incident listed.
80 |
81 | Now, click on "View details" for this incident. You should see the details view load for this incident.
82 |
83 |
84 | ## Summary
85 |
86 | You should now have Incident Central within your Retool instance! 🥳
87 |
88 | **Next steps:** You can choose to extend or adjust this app in any way. (For example, maybe you'd like Report Incident to also create a Jira ticket.)
89 |
90 | Please consider contributing back to this Github repo if you make something you think other people would like to use! Get in touch by opening a Github Issue if you have an idea you want to run by us.
91 |
92 | **Questions, comments, feedback?** Please open a Github Issue and let us know!
93 |
--------------------------------------------------------------------------------
/incident-central/setup-guides/set-up-retool-resources.md:
--------------------------------------------------------------------------------
1 | # Incident Central Setup - Step 4: Resources in Retool
2 |
3 | ## What's a Resource in Retool?
4 | A Resource is a core concept in Retool. A Resource is a config that defines a backend data source that your Retool apps can talk to. Retool supports a variety of different kinds of backends, from databases to REST API calls, to special integrations like Stripe.
5 |
6 | You can define a Resource once, then use it again and again in your Retool apps.
7 |
8 | Read more about Resources in [the Retool docs](https://docs.retool.com/docs/integrations-overview).
9 |
10 | ## Output
11 | The goal of this section is to create:
12 | * A Resource called `PagerDuty API for Incident Central`. This will be a reusable way to call the PagerDuty API that you set up in Step 1.
13 | * A Resource called `Slack API for Incident Central`. This will be a reusable way to call the Slack API that you set up in Step 2.
14 | * A Resource called `Incident Database`. This will be a reusable way to query the database that you set up in Step 3.
15 |
16 | Note: The names here are important! The app code we provide expects the Resources to have these names. (You can choose to rename them, after you've finished setting everything up.)
17 |
18 | ## Step 4.1 - Create the "PagerDuty API for Incident Central" Resource
19 | 1. On the Resources page (`/resources`) in Retool, click "Create New" in the upper right.
20 | 2. Select the "REST API" resource type.
21 | 3. Fill out the resource information as follows:
22 |
23 | ```
24 | Name: PagerDuty API for Incident Central
25 | Base URL: https://api.pagerduty.com
26 | ```
27 |
28 | ```
29 | = Headers =
30 | Accept: application/vnd.pagerduty+json;version=2
31 | Authorization: Token token=
32 | Content-Type: application/json
33 | ```
34 | 4. Click "Save changes".
35 |
36 |
37 |
38 |
39 | ## Step 4.2 - Create the "Slack API for Incident Central" Resource
40 | 1. On the Resources page (`/resources`) in Retool, click "Create New" in the upper right.
41 | 2. Select the "REST API" resource type.
42 | 3. Fill out the resource information as follows:
43 |
44 | ```
45 | Name: Slack API for Incident Central
46 | Base URL: https://slack.com/api
47 | ```
48 |
49 | ```
50 | = Headers =
51 | Content-Type: application/json; charset=utf-8
52 | Authorization: Bearer
53 | ```
54 | 4. Click "Save changes".
55 |
56 |
57 |
58 |
59 | ## Step 4.3 - Create the "Incident Database" Resource
60 | 1. On the Resources page (`/resources`) in Retool, click "Create New" in the upper right.
61 | 2. Select the "Postgres" resource type. (Or, if you've used a different type of database, select the appropriate type.)
62 | 3. Fill out the resource information as follows. (For more info, see [our docs on Postgres integration](https://docs.retool.com/docs/postgresql-integration).)
63 |
64 | ```
65 | Name: Incident Database
66 |
67 |
68 | Host:
69 | Port:
70 | Database name:
71 | Database username:
72 | Database password:
73 | ```
74 |
75 | 4. Click "Save changes".
76 |
77 |
78 | ## Summary
79 | You now have three Resources defined in Retool, with the following names:
80 | * `PagerDuty API for Incident Central`
81 | * `Slack API for Incident Central`
82 | * `Incident Database`
83 |
84 | Reminder: The names here are important! The app code we provide expects the Resources to have these names. (You can choose to rename them, after you've finished setting everything up.)
85 |
86 | Now, we are ready to import our apps!
87 |
88 | ## Next step
89 | [Step-by-step: Set up Retool Apps](./set-up-retool-apps.md)
90 |
--------------------------------------------------------------------------------
/incident-central/setup-guides/set-up-slack.md:
--------------------------------------------------------------------------------
1 | # Incident Central Setup - Step 2: Slack
2 |
3 | ## What's Slack?
4 | Slack is the tool that Incident Central will use to create a real-time communication channel when an incident is triggered. The channel is meant to be a central place for people to collaborate to solve the incident.
5 |
6 | ## Output
7 | The goal of this section is to create:
8 | * A Slack account (aka "workspace"), if you don't have one already. (A free trial works.)
9 | * A Slack API key.
10 | * Inside Slack, you should invite (at least) all the people in your organization who may help out in incident response.
11 |
12 | ## Step 2.1 - Slack workspace
13 | Head on over to [slack.com](https://slack.com/) and sign up, or sign in.
14 |
15 | ## Step 2.2 - Slack API key
16 | In order to get a Slack API key, you'll first need to create a Slack app. To do this, follow the instructions in [Slack's Basic app setup docs](https://api.slack.com/authentication/basics): in particular the steps "Creating an app", "Requesting scopes", and "Installing the app to a workspace". (You don't need to do the rest.)
17 |
18 | For the "Requesting scopes" step, you should specify the following scopes:
19 | ```
20 | Bot Token Scopes:
21 | - channels:manage
22 | - users:read
23 | - users:read.email
24 | ```
25 |
26 | After you install the app to your Slack workspace, you will be able to make API requests using the "Bot User OAuth Token" associated with your new Slack app.
27 |
28 | ## Step 2.3 - Invite oncalls to Slack
29 | If you already have everyone in Slack, you are all set.
30 |
31 | If this is your first time setting up Slack, you'll want to invite ideally your whole team to Slack, but definitely anyone who may help out in incident response (whether or not they are oncall first-responders).
32 |
33 | ### Important assumption
34 | Incident Central works off the following assumptions about your setup:
35 | * The email address of people in your PagerDuty account matches their email address within your Slack account.
36 |
37 | ## Summary
38 | You now have the following items that you can plug into a later step:
39 | * Slack API key - this is the "Bot User OAuth Token" associated with your Slack app
40 |
41 | You also have a Slack workspace in which a new Slack channel will be opened whenever an incident is triggered through Incident Central. Incident Central will autogenerate a unique, human-readable name for the incident, and give the Slack channel the same name. Incident Central will also add the oncall person (as defined by PagerDuty) and incident reporter to this new channel.
42 |
43 | ## Next step
44 | [Step-by-step: Set up a database](./set-up-database.md)
45 |
--------------------------------------------------------------------------------
/snowflake-resource-optimization/README.md:
--------------------------------------------------------------------------------
1 | # Snowflake Resource Optimization: Setup & Configuration
2 |
3 |
4 |
5 |
6 |
7 | ## Why you need this
8 | This Retool app extends [Snowflake's Resource Optimization: Setup & Configuration Quickstart Guide](https://quickstarts.snowflake.com/guide/resource_optimization_setup/index.html?index=..%2F..index#0). If you use Snowflake, it will help you better monitor and manage your credit consumption, and avoid spending more than you need to.
9 |
10 | This Retool app makes it easy to put the Quickstart Guide into practice, by enabling you to take action from right within an easy-to-use UI. Without having to remember or type any commands, you can set auto-suspend or auto-resume for your warehouses, suspend or drop idle warehouses, disable or drop idle users, etc.
11 |
12 | ## What does Snowflake Resource Optimization: Setup & Configuration do?
13 | The Snowflake Resource Optimization: Setup & Configuration app lets you do the following things with the click of a button:
14 |
15 | ### Warehouses
16 | - Identify warehouses without auto-resume enabled and enable it
17 | - Identify warehouses without auto-suspend enabled and enable it for your chosen duration
18 | - Identify warehouses with long suspension and modify the auto-suspend setting
19 | - Identify warehouses without resource monitors and assign them where needed
20 | - Identify idle warehouses and suspend or drop them
21 | ### Users
22 | - Review warehouses used by multiple roles and investigate relevant query metrics
23 | - Identify idle users and disable or drop them
24 | - Identify users who have never logged in and disable or drop them
25 | - Identify idle roles and drop them
26 | ### Tasks
27 | - Identify failed tasks and show query history or drop them
28 | - Identify long running tasks and show query history or drop them
29 | ### Else
30 | - Review account, warehouse, or user statement timeouts and modify them
31 | - Identify stale table streams to potentially recreate
32 |
33 |
34 | ## Screenshots
35 |
36 |
37 |  |
38 |  |
39 |
40 |
41 |  |
42 |  |
43 |
44 |
45 |
46 | ## Who is this tool for?
47 | This tool will enable users to make irreversible changes, such as dropping warehouses, users, roles, or tasks. It is set up to leverage the [ACCOUNTADMIN](https://docs.snowflake.com/en/user-guide/security-access-control-considerations.html#using-the-accountadmin-role) role, and as such should only be used by Snowflake administrators who are trusted to make such changes.
48 |
49 | ## Technical Details and Setup
50 | ### Dependencies - backends
51 | The Snowflake Resource Optimization: Setup & Configuration app depends on the following backend services:
52 | - Snowflake
53 |
54 | ### How to set up Snowflake Resource Optimization: Setup & Configuration in your Retool instance
55 | See the detailed setup guides in the [Setup Guides folder](./setup-guides).
56 |
57 | As an overview, these guides will take you through:
58 |
59 | 1. Setting up Snowflake.
60 | 2. Setting up your Snowflake database as a Resource in Retool.
61 | 3. Downloading and configuring the Retool app file.
62 |
63 | ### Application notes
64 | Please find some relevant considerations below:
65 | - In [Snowflake's Resource Optimization: Setup & Configuration Quickstart Guide](https://quickstarts.snowflake.com/guide/resource_optimization_setup/index.html?index=..%2F..index#0), the Idle Users, Users Never Logged In, and Idle Roles queries leverage the [ACCOUNT_USAGE](https://docs.snowflake.com/en/sql-reference/account-usage.html) schema. The Retool app, however, uses Snowflake DDL (e.g. SHOW USERS) to avoid the latency associated with the ACCOUNT_USAGE schema. This means that you are able to review and update users and roles as soon as they are created in Snowflake. It also means that these queries do not show deleted users or roles.
66 | - The Failed Tasks and Long Running Tasks queries can take a long time to run. Feel free to modify them using [Snowflake's recommendations](https://docs.snowflake.com/en/sql-reference/account-usage/task_history.html#usage-notes) as needed.
67 |
68 |
69 | ## What's next?
70 | ### How to contribute
71 | Please open a Github Issue on this repo, and let us know about your interest in contributing! We encourage you to reach out before you get started building to get early feedback.
72 |
--------------------------------------------------------------------------------
/snowflake-resource-optimization/images/home-page.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tryretool/retool-app-exchange/ea48efde500e831b0435a0e7ae1e28d1e5021e83/snowflake-resource-optimization/images/home-page.png
--------------------------------------------------------------------------------
/snowflake-resource-optimization/images/setup-config-demo.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tryretool/retool-app-exchange/ea48efde500e831b0435a0e7ae1e28d1e5021e83/snowflake-resource-optimization/images/setup-config-demo.gif
--------------------------------------------------------------------------------
/snowflake-resource-optimization/images/statement-timeouts.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tryretool/retool-app-exchange/ea48efde500e831b0435a0e7ae1e28d1e5021e83/snowflake-resource-optimization/images/statement-timeouts.png
--------------------------------------------------------------------------------
/snowflake-resource-optimization/images/users.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tryretool/retool-app-exchange/ea48efde500e831b0435a0e7ae1e28d1e5021e83/snowflake-resource-optimization/images/users.png
--------------------------------------------------------------------------------
/snowflake-resource-optimization/images/warehouses.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tryretool/retool-app-exchange/ea48efde500e831b0435a0e7ae1e28d1e5021e83/snowflake-resource-optimization/images/warehouses.png
--------------------------------------------------------------------------------
/snowflake-resource-optimization/setup-guides/README.md:
--------------------------------------------------------------------------------
1 | # App Setup guide
2 |
3 | This guide helps you set up Snowflake Resource Optimization: Setup & Configuration inside your Retool instance. (If you're wondering "What's Snowflake Resource Optimization: Setup & Configuration?", see [the main README](../README.md).)
4 |
5 | ## Overview
6 | As an overview, we'll go through the following steps:
7 |
8 | * Set up Snowflake. (Step 1)
9 | * Set up your Snowflake database as a Resource in Retool. (Step 2)
10 | * Download and configure the Retool app file in Retool. (Step 3)
11 |
12 | ## Step 1 - Set up Snowflake
13 | [Step-by-step: Set up Snowflake](./set-up-snowflake.md)
14 |
15 | ## Step 2 - Set up your Snowflake database as a Resource in Retool
16 | [Step-by-step: Set up Retool Resource](./set-up-retool-resource.md)
17 |
18 | ## Step 3 - Set up the Retool app file
19 | [Step-by-step: Set up Retool App](./set-up-retool-app.md)
20 |
--------------------------------------------------------------------------------
/snowflake-resource-optimization/setup-guides/images/snowflake-resource.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tryretool/retool-app-exchange/ea48efde500e831b0435a0e7ae1e28d1e5021e83/snowflake-resource-optimization/setup-guides/images/snowflake-resource.png
--------------------------------------------------------------------------------
/snowflake-resource-optimization/setup-guides/set-up-retool-app.md:
--------------------------------------------------------------------------------
1 | # App Setup - Step 3: Apps in Retool
2 |
3 | ## What's an App in Retool?
4 | As the name suggests, an App is a single "view" (or "page") that you can build in Retool. An App is built from a combination of Components to construct the UI, and Queries that define calls to your Resources. Queries can do reads or writes.
5 |
6 | ## Output
7 | The goal of this section is:
8 | * To get Snowflake Resource Optimization: Setup & Configuration into your Retool instance. 🚀
9 |
10 |
11 | ## Step 3.1 - Download the app "code"
12 | In this step, you will obtain the code file in the `/code` directory in this Github repo.
13 |
14 | To do this, clone this Github repo:
15 | `git clone https://github.com/tryretool/retool-app-exchange.git`
16 |
17 | (Alternatively, you can manually click-to-download the file in the [`/code`](../code) directory.)
18 |
19 | You will have the following file: `Snowflake-Resource-Optimization-Setup-Configuration.json`.
20 |
21 | ## Step 3.2 - Import the app "code"
22 | First, let's create a folder to put this code into, to keep things organized.
23 | 1. On your Retool home page, click "Create new" → "Create a new folder". Give this folder a name you like, e.g. "Snowflake Resource Optimization".
24 |
25 | 2. Now, let's import the app.
26 | * Click "Create new" → "Import an app".
27 | * Upload the `Snowflake-Resource-Optimization-Setup-Configuration.json` file, and select the folder you just created ("Snowflake Resource Optimization") as the destination.
28 |
29 | ## Step 3.3 - Test it out
30 | In this section, we're going to test out the functionality in your new app. Please be aware that you can use this app to make irreversible changes (e.g. drop a warehouse or user).
31 |
32 | Navigate to your "Snowflake Resource Optimization" folder, and click on the "Snowflake Resource Optimization: Setup & Configuration" app. Toggle through the application tabs (Warehouses, Users, Tasks, Else), review query results, and try out a couple of actions, e.g.:
33 | - Set auto-resume for a warehouse without it
34 | - Suspend an idle warehouse
35 | - Disable a user who has never logged in
36 | - etc.
37 |
38 | ## Summary
39 |
40 | You should now have Snowflake Resource Optimization: Setup & Configuration within your Retool instance! 🥳
41 |
42 | **Next steps:** You can choose to extend or adjust this app in any way.
43 |
44 | Please consider contributing back to this Github repo if you make something you think other people would like to use! Get in touch by opening a Github Issue if you have an idea you want to run by us.
45 |
46 | **Questions, comments, feedback?** Please open a Github Issue and let us know!
47 |
--------------------------------------------------------------------------------
/snowflake-resource-optimization/setup-guides/set-up-retool-resource.md:
--------------------------------------------------------------------------------
1 | # App Setup - Step 2: Resources in Retool
2 |
3 | ## What's a Resource in Retool?
4 | A Resource is a core concept in Retool. A Resource is a config that defines a backend data source that your Retool apps can talk to. Retool supports a variety of different kinds of backends, from databases to REST API calls, to special integrations like Stripe.
5 |
6 | You can define a Resource once, then use it again and again in your Retool apps.
7 |
8 | Read more about Resources in [the Retool docs](https://docs.retool.com/docs/integrations-overview).
9 |
10 | ## Output
11 | The goal of this section is to create:
12 | * A Resource called `Snowflake DB - Account Admin`. This will be a reusable way to hit Snowflake as the user from Step 1.
13 |
14 | Note: The name here is important! The app code we provide expects the Resource to have this name. (You can choose to rename it, after you've finished setting everything up.)
15 |
16 | ## Step 3.1 - Create the "Snowflake DB - Account Admin" Resource
17 | 1. On the Resources page (`/resources`) in Retool, click "Create New" in the upper right.
18 | 2. Select the "Snowflake" resource type.
19 | 3. Fill out the resource information as follows. (For more info, see [our docs on Snowflake integration](https://docs.retool.com/docs/snowflake-integration).)
20 |
21 | ```
22 | Name: Snowflake DB - Account Admin
23 |
24 | Account name:
25 | Database name: SNOWFLAKE
26 | Database schema: [leave blank]
27 | Database warehouse: [leave blank]
28 | User role: ACCOUNTADMIN (this role is required)
29 |
30 | Authentication: Password (you must use password authentication)
31 | Database username:
32 | Database password:
33 |
34 | [X] Disable converting queries to prepared statements (you must check this box)
35 | ```
36 |
37 | Note: You must check the 'Disable converting queries to prepared statements' box (see more info [here](https://docs.retool.com/docs/sql-in-retool#prepared-statements)).
38 |
39 | 4. Click "Save changes".
40 |
41 |
42 |
43 |
44 | ## Summary
45 | You now have a Snowflake resource defined in Retool, named `Snowflake DB - Account Admin`.
46 |
47 | Reminder: The name here is important! The app code we provide expects the Resource to have this name. (You can choose to rename it, after you've finished setting everything up.)
48 |
49 | Now, we are ready to import our app!
50 |
51 | ## Next step
52 | [Step-by-step: Set up Retool App](./set-up-retool-app.md)
53 |
--------------------------------------------------------------------------------
/snowflake-resource-optimization/setup-guides/set-up-snowflake.md:
--------------------------------------------------------------------------------
1 | # App Setup - Step 1: Snowflake
2 |
3 | ## What's Snowflake?
4 | Snowflake provides cloud-based data storage and analytics services, and if you're here, you're likely already using it! The Snowflake Resource Optimization: Setup & Configuration app is designed to help you manage your Snowflake resources.
5 |
6 | ## Output
7 | The goal of this section is to:
8 | * Ensure you have a Snowflake user with the ACCOUNTADMIN role
9 |
10 | ## Step 1.1 - Snowflake user
11 | The Snowflake Resource Optimization: Setup & Configuration app requires a Snowflake user with the ACCOUNTADMIN role.
12 |
13 | If you would like to create a new Snowflake user for Retool, please follow Snowflake's instructions on [creating a user](https://docs.snowflake.com/en/user-guide/admin-user-management.html#creating-users). Again, please ensure that this user has the [ACCOUNTADMIN](https://docs.snowflake.com/en/user-guide/security-access-control-considerations.html#using-the-accountadmin-role) role.
14 |
15 | ## Summary
16 | You now have a Snowflake user with the ACCOUNTADMIN role, which you can plug into the next step.
17 |
18 | ## Next step
19 | [Step-by-step: Set up Retool Resource](./set-up-retool-resource.md)
20 |
--------------------------------------------------------------------------------
/snowflake-uar-reviews/README.md:
--------------------------------------------------------------------------------
1 | # User Access Reviews by Snowflake
2 |
3 | ## Why you need this
4 | This Retool app makes it easy to perform periodic reviews of employee access rights. Navigate the process of preparing data, reviewing access rights, and remediating within an easy-to-use GUI.
5 |
6 |
7 |
8 | ## What does the User Access Review app do?
9 | Provides an interface to re-evaluate user roles, access privileges, and credentials to mitigate insider threats, prevent insider mistakes, reduce excessive access, and meet compliance requirements such as HIPAA, FedRAMP, SOX, and more. Take actions such as approving or revoking privileges and enter your reasons for doing so. Submit individual changes or update multiple records in bulk.
10 |
11 | This repo includes all code needed to source user roles within your Snowflake instance and generate the table schemas that the application attaches to. Once you load this application, you have the ability to modify the source code as you need to align to your own organization’s processes. This repo:
12 |
13 | * Provides a schema to use on Snowflake
14 | * Provides a manager review application that implements filtering at the manager level
15 | * An admin application to get started with user and role entry
16 | * A review application to audit manager submissions for super-users and compliance teams
17 |
18 | ## Caveats
19 | * If you need row-level security reads and writes, talk to your Retool account team
20 | * The template assumes a simple hierarchy (reviewer to employee), however you can customize this by modifying the schema
21 | * These applications are meant to be configurable to meet your needs
22 | ## Screenshots
23 | Manager Review Portal
24 |
25 |
26 |
27 | Compliance Review Portal
28 |
29 |
30 |
31 | Create Review Cycle Admin Application
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 | ## Technical Details and Setup
43 | ### Dependencies - backends
44 | The Snowflake User Access Roles Review app depends on the following backend services:
45 | - Snowflake
46 |
47 | ### How to set up Snowflake User Access Roles Review in your Retool instance
48 | See the detailed setup guides in the [Setup Guides folder](./setup-guides).
49 |
50 | As an overview, these guides will take you through:
51 |
52 | 1. Set up Snowflake
53 | [Step-by-step: Set up Snowflake](./setup-guides/set-up-snowflake.md)
54 |
55 | 2. Set up your Snowflake database as a Resource in Retool
56 | [Step-by-step: Set up Retool Resource](./setup-guides/set-up-retool-resource.md)
57 |
58 | 3. Set up the Retool app file
59 | [Step-by-step: Set up Retool App](./setup-guides/set-up-retool-app.md)
60 |
61 |
62 |
63 | ## How to contribute
64 | Please open a Github Issue on this repo, and let us know about your interest in contributing! We encourage you to reach out before you get started building to get early feedback.
65 |
--------------------------------------------------------------------------------
/snowflake-uar-reviews/images/compliance_review.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tryretool/retool-app-exchange/ea48efde500e831b0435a0e7ae1e28d1e5021e83/snowflake-uar-reviews/images/compliance_review.png
--------------------------------------------------------------------------------
/snowflake-uar-reviews/images/create_cycle_employees.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tryretool/retool-app-exchange/ea48efde500e831b0435a0e7ae1e28d1e5021e83/snowflake-uar-reviews/images/create_cycle_employees.png
--------------------------------------------------------------------------------
/snowflake-uar-reviews/images/create_cycle_name_cycle.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tryretool/retool-app-exchange/ea48efde500e831b0435a0e7ae1e28d1e5021e83/snowflake-uar-reviews/images/create_cycle_name_cycle.png
--------------------------------------------------------------------------------
/snowflake-uar-reviews/images/create_cycle_system_roles.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tryretool/retool-app-exchange/ea48efde500e831b0435a0e7ae1e28d1e5021e83/snowflake-uar-reviews/images/create_cycle_system_roles.png
--------------------------------------------------------------------------------
/snowflake-uar-reviews/images/manager_review_bulk_approve.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tryretool/retool-app-exchange/ea48efde500e831b0435a0e7ae1e28d1e5021e83/snowflake-uar-reviews/images/manager_review_bulk_approve.png
--------------------------------------------------------------------------------
/snowflake-uar-reviews/images/manager_review_bulk_revoke.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tryretool/retool-app-exchange/ea48efde500e831b0435a0e7ae1e28d1e5021e83/snowflake-uar-reviews/images/manager_review_bulk_revoke.png
--------------------------------------------------------------------------------
/snowflake-uar-reviews/images/manager_review_landing.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tryretool/retool-app-exchange/ea48efde500e831b0435a0e7ae1e28d1e5021e83/snowflake-uar-reviews/images/manager_review_landing.png
--------------------------------------------------------------------------------
/snowflake-uar-reviews/setup-guides/images/snowflake_resource.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tryretool/retool-app-exchange/ea48efde500e831b0435a0e7ae1e28d1e5021e83/snowflake-uar-reviews/setup-guides/images/snowflake_resource.png
--------------------------------------------------------------------------------
/snowflake-uar-reviews/setup-guides/set-up-retool-app.md:
--------------------------------------------------------------------------------
1 | # App Setup - Step 3: Apps in Retool
2 |
3 | ## What's an App in Retool?
4 | As the name suggests, an App is a single "view" (or "page") that you can build in Retool. An App is built from a combination of Components to construct the UI, and Queries that define calls to your Resources. Queries can do reads or writes.
5 |
6 | ## Output
7 | The goal of this section is:
8 | * To get the 3 applications that make up Snowflake User Access Roles Review into your Retool instance. 🚀
9 |
10 |
11 | ## Step 3.1 - Download the app "code"
12 | In this step, you will obtain the code file in the `/code` directory in this Github repo.
13 |
14 | To do this, clone this Github repo:
15 | `git clone https://github.com/tryretool/retool-app-exchange.git`
16 |
17 | (Alternatively, you can manually click-to-download the file in the [`/code`](../code) directory.)
18 |
19 | You will have the following 3 files:
20 | - `Compliance_Review_Portal.json`
21 | - `Create_Review_Cycle.json`
22 | - `Manager_Review_Portal.json`
23 |
24 | ## Step 3.2 - Import the app "code"
25 | First, let's create a folder to put this code into, to keep things organized.
26 | 1. On your Retool home page, click "Create new" → "Create a new folder". Give this folder a name you like, e.g. "Snowflake User Access Roles Review".
27 |
28 | 2. Now, let's import the app.
29 | * Click "Create new" → "Import an app".
30 | * Upload the `Compliance_Review_Portal.json`, `Create_Review_Cycle.json`, `Manager_Review_Portal.json` files, and select the folder you just created ("Snowflake User Access Roles Review") as the destination.
31 |
32 | ## Step 3.3 - Test it out
33 | In this section, we're going to test out the functionality in your new app.
34 |
35 | 1. Identify the employees and system roles for which we want to review:
36 | * The Create_Review_Cycle Retool application will allow you to manually input all the _EMPLOYEE_ and _SYSTEM_ROLES_ data required to generate the _ACCESS_REVIEWS_ table
37 | * If this is your first time using the app, create a couple example employees, system roles, and then create a review cycle. Make sure that you're email address used to login to Retool is one of the employees and you are listed as the reviewer email in the employees tab of the Create_Review_Cycle application.
38 | * Alternatively, you can replace the _EMPLOYEE_ and _SYSTEM_ROLE_ tables with records from your existing ETL or database processes. Simply modify the createAccessReviews SQL in the Create_Review_Cycle application to point to the correct tables.
39 | 2. In the Create_Review_Cycle admin application, define a new review cycle
40 | * This will first save a new record in the _REVIEW_CYCLE_ table and then create the _ACCESS_REVIEWS_ table that will be used in the Manager_Review_Portal and Compliance_Review_Portal applications.
41 |
42 |
43 | ## Summary
44 |
45 | You should now have Snowflake User Access Roles Review app suite within your Retool instance! 🥳
46 |
47 | **Next steps:** You can choose to customize or extend the application any way you like. Introduce any necessary automations to the Compliance_Review_Portal application. Examples include:
48 | * Making an API request to create/update JIRA tickets
49 | * Making an API request to send a Slack notification in the compliance channel that the data is ready to be reviewed.
50 |
51 | Please consider contributing back to this Github repo if you make something you think other people would like to use! Get in touch by opening a Github Issue if you have an idea you want to run by us.
52 |
53 | **Questions, comments, feedback?** Please open a Github Issue and let us know!
54 |
--------------------------------------------------------------------------------
/snowflake-uar-reviews/setup-guides/set-up-retool-resource.md:
--------------------------------------------------------------------------------
1 | # App Setup - Step 2: Resources in Retool
2 |
3 | ## What's a Resource in Retool?
4 | A Resource is a core concept in Retool. A Resource is a config that defines a backend data source that your Retool apps can talk to. Retool supports a variety of different kinds of backends, from databases to REST API calls, to special integrations like Stripe.
5 |
6 | You can define a Resource once, then use it again and again in your Retool apps.
7 |
8 | Read more about Resources in [the Retool docs](https://docs.retool.com/docs/integrations-overview).
9 |
10 | ## Output
11 | The goal of this section is to create:
12 | * A Resource called `Snowflake UAR`. This will be a reusable way to hit Snowflake as the user from Step 1.
13 |
14 | Note: The name here is important! The app code we provide expects the Resource to have this name. (You can choose to rename it, after you've finished setting everything up.)
15 |
16 | ## Step 3.1 - Create the "Snowflake UAR" Resource
17 | 1. On the Resources page (`/resources`) in Retool, click "Create New" in the upper right.
18 | 2. Select the "Snowflake" resource type.
19 | 3. Fill out the resource information as follows. (For more info, see [our docs on Snowflake integration](https://docs.retool.com/docs/snowflake-integration).)
20 |
21 | ```
22 | Name: Snowflake UAR
23 |
24 | Account name:
25 | Database name: SNOWFLAKE
26 | Database schema:
27 | Database warehouse:
28 | User role: ACCOUNTADMIN (this role is required)
29 |
30 | Authentication: Password (you must use password authentication)
31 | Database username:
32 | Database password:
33 | ```
34 |
35 | 4. Click "Save changes".
36 |
37 |
38 |
39 |
40 | ## Summary
41 | You now have a Snowflake resource defined in Retool, named `Snowflake UAR`.
42 |
43 | Reminder: The name here is important! The app code we provide expects the Resource to have this name. (You can choose to rename it, after you've finished setting everything up.)
44 |
45 | Now, we are ready to import our app!
46 |
47 | ## Next step
48 | [Step-by-step: Set up Retool App](./set-up-retool-app.md)
49 |
--------------------------------------------------------------------------------
/snowflake-uar-reviews/setup-guides/set-up-snowflake.md:
--------------------------------------------------------------------------------
1 | # App Setup - Step 1: Snowflake
2 |
3 | ## What's Snowflake?
4 | Snowflake provides cloud-based data storage and analytics services, and if you're here, you're likely already using it! The Snowflake User Access Roles Review app is designed to help you conduct regular UAR reviews defined in your Snowflake resources.
5 |
6 | ## Output
7 | The goal of this section is to:
8 | * Ensure you have a Snowflake user with the ACCOUNTADMIN role
9 | * Ensure you create the necessary tables used in the Create Review Cycle, Manager Review Portal, and Compliance Review Portal Retool applications
10 |
11 | ## Step 1 - Snowflake user
12 | The Snowflake User Access Roles Review app requires a Snowflake user with the ACCOUNTADMIN role.
13 |
14 | If you would like to create a new Snowflake user for Retool, please follow Snowflake's instructions on [creating a user](https://docs.snowflake.com/en/user-guide/admin-user-management.html#creating-users). Again, please ensure that this user has the [ACCOUNTADMIN](https://docs.snowflake.com/en/user-guide/security-access-control-considerations.html#using-the-accountadmin-role) role.
15 |
16 |
17 | ## Step 2 - Create Schema
18 |
19 | Create the 4 tables used across the applications in your database:
20 | - _ACCESS_REVIEWS_
21 | - _EMPLOYEES_
22 | - _SYSTEM_ROLES_
23 | - _REVIEW_CYCLES_
24 |
25 | Please follow Snowflake's instructions on [creating a table](https://docs.snowflake.com/en/sql-reference/sql/create-table.html).
26 |
27 | ### Access_Reviews
28 | ```
29 | create or replace TABLE ..ACCESS_REVIEWS (
30 | ID NUMBER(38,0) NOT NULL DEFAULT ..ACCESS_REVIEWS_SEQ.NEXTVAL,
31 | STATUS VARCHAR(16777216),
32 | EMPLOYEE_EMAIL VARCHAR(16777216),
33 | REVIEW_CYCLE_ID NUMBER(38,0),
34 | SYSTEMROLE_ID VARCHAR(16777216),
35 | INITIALDECISIONREASON VARCHAR(16777216),
36 | UPDATED_AT TIMESTAMP_NTZ(9),
37 | SUBMITTED_AT TIMESTAMP_NTZ(9),
38 | ACTION VARCHAR(16777216),
39 | REASON VARCHAR(16777216),
40 | CLOSED_BY_EMAIL VARCHAR(16777216),
41 | CLOSED_AT TIMESTAMP_NTZ(9)
42 | );
43 | ```
44 | ### Employees
45 |
46 | ```
47 | create or replace TABLE ..EMPLOYEES (
48 | EMPLOYEE_ID NUMBER(38,0),
49 | EMAIL VARCHAR(16777216),
50 | NAME VARCHAR(16777216),
51 | REVIEWER_EMAIL VARCHAR(16777216)
52 | );
53 | ```
54 |
55 | ### System Roles
56 | ```
57 | create or replace TABLE ..SYSTEM_ROLES (
58 | ID NUMBER(38,0) NOT NULL DEFAULT ..SYSTEM_ROLES_SEQ.NEXTVAL,
59 | NAME VARCHAR(16777216)
60 | );
61 | ```
62 | ### Review Cycles
63 | ```
64 | create or replace TABLE ..REVIEW_CYCLES (
65 | ID NUMBER(38,0) NOT NULL DEFAULT ..REVIEW_CYCLES_SEQ.NEXTVAL,
66 | NAME VARCHAR(16777216),
67 | START_DATE DATE,
68 | END_DATE DATE,
69 | PREVIOUS_CYCLE_ID NUMBER(38,0)
70 | );
71 | ```
72 | ## Summary
73 | You now have a Snowflake user with the ACCOUNTADMIN role, which you can plug into the next step. You have also created the tables that will be used in the Retool applications.
74 |
75 | ## Next step
76 | [Step-by-step: Set up Retool Resource](./set-up-retool-resource.md)
--------------------------------------------------------------------------------
/usage-viewer/README.md:
--------------------------------------------------------------------------------
1 | # Importable Usage Analytics Retool App
2 |
3 | If you're self-hosting Retool, we’ve created a Retool app you can import to surface usage and adoption metrics for your users and apps.
4 |
5 | This data will help you properly allocate developer resources toward app-building and understand how your teams are using Retool.
6 |
7 | **Note**: This app surfaces *directional* usage metrics – not the exact # of users you'll be invoiced for next month.
8 |
9 |
10 |
11 | ## Setup
12 | This app needs read access to [Retool's storage database](https://docs.retool.com/docs/configuring-retools-storage-database). Therefore, it's only suitable for use with self-hosted instances of Retool.
13 |
14 | > :warning: **For a smooth app import experience, we recommend upgrading to Retool v2.116 or later. If you are using [Spaces](https://docs.retool.com/org-users/guides/configure-spaces), you must be on version 3.18 or above.**
15 |
16 | ### 0. Make sure you have access!
17 |
18 | You should have the right approvals and know where to look to find the `POSTGRES_HOST`, `POSTGRES_PORT`, `POSTGRES_DB`, `POSTGRES_USER`, `POSTGRES_PASSWORD` environment variables in your deployment configuration.
19 |
20 | ### 1. Create a read-only database user
21 |
22 | Since you'll need to connect to Retool's storage database, which contains all your apps, users, and settings, we recommend creating a read-only user, with access to the following tables in the Retool storage database:
23 |
24 | - `page_saves`
25 | - `pages`
26 | - `folders`
27 | - `users`
28 | - `audit_trail_events`
29 | - `groups`
30 | - `resources`
31 | - `workflow_save`
32 |
33 | To create a user and set a password, use the following command when running `psql` in your `postgres` container.
34 |
35 | ```CREATE USER WITH PASSWORD ;```
36 |
37 | To assign the required permissions, connect to the Retool storage database and use the following commands:
38 |
39 | ```
40 | GRANT CONNECT ON DATABASE `` TO ;
41 | GRANT USAGE ON SCHEMA public TO ;
42 | GRANT SELECT ON page_saves TO ;
43 | GRANT SELECT ON pages TO ;
44 | GRANT SELECT ON folders TO ;
45 | GRANT SELECT ON users TO ;
46 | GRANT SELECT ON audit_trail_events TO ;
47 | GRANT SELECT ON groups to ;
48 | GRANT SELECT ON resources to ;
49 | GRANT SELECT ON groups to ;
50 | GRANT SELECT ON resources to ;
51 | GRANT SELECT ON organizations to ;
52 | GRANT SELECT ON workflow_save to ;
53 | GRANT SELECT ON workflow to ;
54 | GRANT SELECT ON workflow_run to ;
55 | ```
56 |
57 | ## 2. Connect the Retool Postgres DB as a Resource
58 |
59 | 1. Navigate to the `/resources` page in your instance and click "Create a new resource"
60 | 2. Select Postgres as the resource type
61 | 3. Name the resource “Retool Audit Log”
62 | 4. To fill out the following details, depending on your deployment, check your secrets file in your deployment configuration. The relevant environment variables are:
63 | - Host: `POSTGRES_HOST`
64 | - Port: `POSTGRES_PORT`
65 | - Database name: `POSTGRES_DB`
66 | - Database username: ``
67 | - Database password: ``
68 |
69 | Save the resource, and it should be available for use by your apps.
70 |
71 | **Note**: If you have multiple instances, you’ll have a Postgres DB for each instance, which means you can only view usage analytics for that instance only.
72 |
73 | ### 3. Download the app code
74 | Download the app code from the `/code` directory in this repository.
75 |
76 | To do this, clone this Github repo: `git clone https://github.com/tryretool/retool-app-exchange.git`
77 |
78 | **Note**: If you are using Spaces and are on version v3.18 and above, download `usage_analytics_spaces.json` from the ['code/](..code) directory.
79 |
80 | ### 4. Import the app code
81 | On the [Retool main page](https://docs.retool.com/docs/protected-applications-getting-started#importing-the-application), click `Create new` and select `Import an app`. Upload the JSON file containing the app code, and name the app.
82 |
83 | ### 5. Rewire the Resource for each query
84 | If the Usage Analytics app doesn't populate with data, it's likely you'll need to update the Resource for each query.
85 |
86 | To update the queries to use the correct Resource, load the Usage Analytics app in editor mode and open the bottom panel to view the queries. Edit each query and select your Retool Postgres database from the Resource dropdown. (Don't forget to save your queries after editing them!)
87 |
88 | ## How to contribute
89 | Please open a Github Issue on this repo, and let us know about your interest in contributing! We encourage you to reach out before you get started building to get early feedback.
90 |
--------------------------------------------------------------------------------
/usage-viewer/images/usage_analytics.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tryretool/retool-app-exchange/ea48efde500e831b0435a0e7ae1e28d1e5021e83/usage-viewer/images/usage_analytics.png
--------------------------------------------------------------------------------