Repository: openops-cloud/openops
Pull request #589: Support Slack actions via URLs
Author: login: rita-gorokhod
Created: 2025-05-14T11:35:57Z | Merged: 2025-05-20T09:28:36Z

Fixes OPS-1727


Diff:
diff --git a/.env.template b/.env.template
index e9bbefa910..275dcaf9ab 100644
--- a/.env.template
+++ b/.env.template
@@ -31,6 +31,7 @@ OPS_JWT_SECRET=secret
 
 # SLACK MESSAGES
 OPS_SLACK_APP_SIGNING_SECRET=
+OPS_DISABLE_SLACK_INTERACTIONS=false
 
 # OPENOPS ADMIN USER
 OPS_OPENOPS_ADMIN_EMAIL=local-admin@openops.com
diff --git a/deploy/docker-compose/.env.defaults b/deploy/docker-compose/.env.defaults
index beb4308cd5..afc45e4b8a 100644
--- a/deploy/docker-compose/.env.defaults
+++ b/deploy/docker-compose/.env.defaults
@@ -112,3 +112,4 @@ HOST_CLOUDSDK_CONFIG=
 # ---------------------------------------------------------
 
 OPS_CODE_BLOCK_MEMORY_LIMIT_IN_MB=256
+DISABLE_SLACK_INTERACTIONS=false
diff --git a/packages/blocks/slack/src/lib/actions/request-action-message.ts b/packages/blocks/slack/src/lib/actions/request-action-message.ts
index 5f37abac2b..9793d9ed07 100644
--- a/packages/blocks/slack/src/lib/actions/request-action-message.ts
+++ b/packages/blocks/slack/src/lib/actions/request-action-message.ts
@@ -1,4 +1,5 @@
 import { createAction, StoreScope } from '@openops/blocks-framework';
+import { networkUtls, SharedSystemProp, system } from '@openops/server-shared';
 import {
   assertNotNullOrUndefined,
   ExecutionType,
@@ -66,10 +67,7 @@ export const requestActionMessageAction = createAction({
       );
     }
 
-    const actions = context.propsValue.actions as {
-      buttonText: string;
-      buttonStyle: string;
-    }[];
+    const actions = context.propsValue.actions as SlackActionDefinition[];
     const actionLabels = actions.map((action) => {
       return action.buttonText;
     });
@@ -106,6 +104,27 @@ const sendMessageAskingForAction = async (
     conversationId,
   );
 
+  const disableSlackInteractions =
+    system.getBoolean(SharedSystemProp.DISABLE_SLACK_INTERACTIONS) || false;
+
+  if (disableSlackInteractions) {
+    const baseUrl = await networkUtls.getPublicUrl();
+
+    actions.forEach((action: SlackActionDefinition) => {
+      action.url = context.run.isTest
+        ? 'https://static.openops.com/test_slack_interactions.txt'
+        : context.generateResumeUrl(
+            {
+              queryParams: {
+                executionCorrelationId: context.run.pauseId,
+                actionClicked: action.buttonText,
+              },
+            },
+            baseUrl,
+          );
+    });
+  }
+
   const blocks = createMessageBlocks(headerText, text, actions);
 
   return await slackSendMessage({
@@ -115,6 +134,7 @@ const sendMessageAskingForAction = async (
     conversationId: userOrChannelId,
     blocks: blocks,
     eventPayload: {
+      interactionsDisabled: disableSlackInteractions,
       domain: context.server.publicUrl,
       isTest: context.run.isTest,
       resumeUrl: context.generateResumeUrl({
@@ -131,6 +151,7 @@ interface SlackElement {
   text: SlackText;
   style?: string;
   confirm?: ConfirmationPrompt;
+  url?: string;
 }
 
 interface SlackText {
@@ -145,17 +166,21 @@ interface ConfirmationPrompt {
   confirm: SlackText;
 }
 
-function createButton(action: {
+interface SlackActionDefinition {
   buttonText: string;
   buttonStyle: string;
   confirmationPrompt: boolean;
   confirmationPromptText: string;
-}): SlackElement {
+  url?: string;
+}
+
+function createButton(action: SlackActionDefinition): SlackElement {
   const {
     buttonText,
     buttonStyle,
     confirmationPrompt,
     confirmationPromptText,
+    url,
   } = action;
 
   const button: SlackElement = {
@@ -164,6 +189,7 @@ function createButton(action: {
       type: 'plain_text',
       text: buttonText,
     },
+    url,
   };
 
   if (buttonStyle === 'danger' || buttonStyle === 'primary') {
diff --git a/packages/blocks/slack/src/lib/common/message-interactions.ts b/packages/blocks/slack/src/lib/common/message-interactions.ts
index 82818a1da3..184cdb0079 100644
--- a/packages/blocks/slack/src/lib/common/message-interactions.ts
+++ b/packages/blocks/slack/src/lib/common/message-interactions.ts
@@ -1,3 +1,5 @@
+import { isEmpty } from '@openops/shared';
+
 export interface InteractionPayload {
   userName: string;
   actionClicked: string;
@@ -26,13 +28,17 @@ export function removeActionBlocks(blocks: any): any[] {
 }
 
 export function buildActionBlock(user: string, button: string) {
+  const whoClickedText = !isEmpty(user)
+    ? `user @${user} clicked on '${button}'`
+    : `clicked on '${button}'`;
+
   const modifiedBlocks = [
     { type: 'divider' },
     {
       type: 'section',
       text: {
         type: 'mrkdwn',
-        text: `*:white_check_mark: Action received, user @${user} clicked on '${button}'*`,
+        text: `*:white_check_mark: Action received: ${whoClickedText}*`,
       },
     },
   ];
diff --git a/packages/blocks/slack/test/request-action-message.test.ts b/packages/blocks/slack/test/request-action-message.test.ts
index 2d3d33e2cd..cebafdfc3a 100644
--- a/packages/blocks/slack/test/request-action-message.test.ts
+++ b/packages/blocks/slack/test/request-action-message.test.ts
@@ -1,3 +1,21 @@
+const getBooleanMock = jest.fn();
+jest.mock('@openops/server-shared', () => {
+  const actual = jest.requireActual('@openops/server-shared');
+  return {
+    ...actual,
+    system: {
+      ...actual.system,
+      getBoolean: getBooleanMock,
+    },
+    networkUtls: {
+      ...actual.networkUtls,
+      getPublicUrl: jest
+        .fn()
+        .mockResolvedValue('https://mocked-public-url.com'),
+    },
+  };
+});
+
 const slackSendMessageMock = jest.fn();
 
 jest.mock('../src/lib/common/utils', () => ({
@@ -164,7 +182,7 @@ describe('requestActionMessageAction', () => {
 
   describe('BEGIN execution', () => {
     test('should send a message and return placeholder response values', async () => {
-      waitForInteractionMock.mockResolvedValue({
+      waitForInteractionMock.mockResolvedValueOnce({
         user: 'a user',
         action: 'an action',
         isExpired: undefined,
@@ -195,6 +213,7 @@ describe('requestActionMessageAction', () => {
         eventPayload: {
           domain: mockContextWithHeader.server.publicUrl,
           resumeUrl: undefined,
+          interactionsDisabled: false,
         },
       });
 
@@ -221,6 +240,63 @@ describe('requestActionMessageAction', () => {
         StoreScope.FLOW_RUN,
       );
     });
+
+    test('should assign resumeUrl to actions when slack interactions are disabled', async () => {
+      getBooleanMock.mockReturnValueOnce(true);
+      waitForInteractionMock.mockImplementation(async (messageObj: any) =>
+        Promise.resolve({ ...messageObj }),
+      );
+      slackSendMessageMock.mockImplementation(async (message: any) =>
+        Promise.resolve(message),
+      );
+
+      const mockContext = buildMockContext('Header Text', true);
+      mockContext.run.isTest = false;
+      mockContext.generateResumeUrl.mockImplementation(
+        ({ queryParams }: any) => {
+          const query = new URLSearchParams(queryParams).toString();
+          return `https://example.com/resume?${query}`;
+        },
+      );
+
+      const result = (await requestActionMessageAction.run(mockContext)) as any;
+
+      const actionBlock = result.blocks.find((b: any) => b.type === 'actions');
+      expect(actionBlock).toBeDefined();
+      const action = actionBlock.elements[0];
+
+      expect(action.url).toBeDefined();
+      expect(action.url).toContain('https://example.com/resume?');
+      expect(action.url).toContain('actionClicked=Approve');
+
+      expect(result.eventPayload.resumeUrl).toContain(
+        'executionCorrelationId=',
+      );
+      expect(result.eventPayload.interactionsDisabled).toBe(true);
+    });
+
+    test('should assign static test url to actions in test mode', async () => {
+      getBooleanMock.mockReturnValueOnce(true);
+      waitForInteractionMock.mockImplementation(async (messageObj: any) =>
+        Promise.resolve({ ...messageObj }),
+      );
+      slackSendMessageMock.mockImplementation(async (message: any) =>
+        Promise.resolve(message),
+      );
+      const mockContext = buildMockContext('Header Text', true);
+      mockContext.run.isTest = true;
+
+      const result = (await requestActionMessageAction.run(mockContext)) as any;
+
+      const actionBlock = result.blocks.find((b: any) => b.type === 'actions');
+      expect(actionBlock).toBeDefined();
+      const action = actionBlock.elements[0];
+
+      expect(action.url).toBe(
+        'https://static.openops.com/test_slack_interactions.txt',
+      );
+      expect(result.eventPayload.interactionsDisabled).toBe(true);
+    });
   });
 
   describe('RESUME execution', () => {
diff --git a/packages/blocks/slack/test/wait-for-action.test.ts b/packages/blocks/slack/test/wait-for-action.test.ts
index 1fb4d9e7d2..992654554c 100644
--- a/packages/blocks/slack/test/wait-for-action.test.ts
+++ b/packages/blocks/slack/test/wait-for-action.test.ts
@@ -109,7 +109,7 @@ describe('waitForAction', () => {
     );
   });
 
-  test('should update message when resume is triggered with a selected user action', async () => {
+  test('should update message when resume is triggered with a selected user action and username exists', async () => {
     slackUpdateMessageMock.mockResolvedValue('some updated message');
 
     const context = createContext({
@@ -149,12 +149,61 @@ describe('waitForAction', () => {
     const lastBlock = blocksArray[blocksArray.length - 1];
 
     expect(lastBlock.text.text).toBe(
-      `*:white_check_mark: Action received, user @some_user clicked on 'some action'*`,
+      `*:white_check_mark: Action received: user @some_user clicked on 'some action'*`,
     );
     expect(context.store.get).not.toHaveBeenCalled();
     expect(context.store.put).not.toHaveBeenCalled();
   });
 
+  test.each([null, undefined, ''])(
+    'should update message when resume is triggered with a selected user action and no username',
+    async (username) => {
+      slackUpdateMessageMock.mockResolvedValue('some updated message');
+
+      const context = createContext({
+        executionType: ExecutionType.RESUME,
+        currentExecutionPath: 'some step',
+        actions: ['some action'],
+        resumePayload: {
+          queryParams: {
+            userName: username,
+            actionClicked: 'some action',
+            path: 'step_1',
+          },
+        },
+      });
+
+      const result = (await waitForAction.run(context)) as any;
+
+      expect(result).toStrictEqual({
+        action: 'some action',
+        user: username,
+        isExpired: false,
+        message: 'some updated message',
+      });
+
+      expect(slackUpdateMessageMock).toHaveBeenCalledTimes(1);
+      expect(slackUpdateMessageMock).toHaveBeenCalledWith(
+        expect.objectContaining({
+          blocks: expect.arrayContaining([expect.anything()]),
+        }),
+      );
+
+      const lastCallArgs =
+        slackUpdateMessageMock.mock.calls[
+          slackUpdateMessageMock.mock.calls.length - 1
+        ][0];
+      const blocksArray = lastCallArgs.blocks;
+      const lastBlock = blocksArray[blocksArray.length - 1];
+
+      expect(lastBlock.text.text).toBe(
+        `*:white_check_mark: Action received: clicked on 'some action'*`,
+      );
+      expect(context.store.get).not.toHaveBeenCalled();
+      expect(context.store.put).not.toHaveBeenCalled();
+    },
+  );
+
   test('should pause flow again if resume is triggered for another step', async () => {
     const context = createContext({
       executionType: ExecutionType.RESUME,
diff --git a/packages/server/api/src/app/slack/slack-interaction-module.ts b/packages/server/api/src/app/slack/slack-interaction-module.ts
index 59224efae5..acbbd11952 100644
--- a/packages/server/api/src/app/slack/slack-interaction-module.ts
+++ b/packages/server/api/src/app/slack/slack-interaction-module.ts
@@ -90,6 +90,14 @@ const slackInteractionController: FastifyPluginCallbackTypebox = (
 
 // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/explicit-function-return-type
 async function evaluateUserInteraction(payload: any, reply: FastifyReply) {
+  const interactionsDisabled: boolean =
+    payload.message.metadata.event_payload.interactionsDisabled;
+
+  if (interactionsDisabled) {
+    logger.debug('Ignoring a Slack interaction: interactions are disabled');
+    return reply.code(200).send({ text: 'Interactions are disabled' });
+  }
+
   const messageDisabled: string =
     payload.message.metadata.event_payload.messageDisabled;
 
@@ -115,7 +123,7 @@ async function evaluateUserInteraction(payload: any, reply: FastifyReply) {
     const userId = payload.user.id;
     const responseUrl = payload.response_url;
     const ephemeralText =
-      'Slack interactions are only available when running the entire workflow.';
+      'Test succeeded. Slack interactions are disabled in test mode and are only available when running the entire workflow.';
     await sendEphemeralMessage({ responseUrl, ephemeralText, userId });
 
     return reply.code(200).send({ text: 'Finished sending ephemeral' });
diff --git a/packages/server/api/test/integration/cloud/slack-integration/slack-interaction.test.ts b/packages/server/api/test/integration/cloud/slack-integration/slack-interaction.test.ts
index 185c13d34f..80c0e5aaa6 100644
--- a/packages/server/api/test/integration/cloud/slack-integration/slack-interaction.test.ts
+++ b/packages/server/api/test/integration/cloud/slack-integration/slack-interaction.test.ts
@@ -106,6 +106,32 @@ describe('Slack API', () => {
       });
     });
 
+    test('should return 200 Ok if interactions are disabled', async () => {
+      verifySignatureMock.mockReturnValueOnce(true);
+
+      const payload = JSON.stringify({
+        actions: [
+          {
+            type: 'some type',
+          },
+        ],
+        message: {
+          metadata: {
+            event_payload: {
+              interactionsDisabled: true,
+            },
+          },
+        },
+      });
+
+      const response = await makeRequest(payload);
+
+      expect(response?.statusCode).toBe(StatusCodes.OK);
+      expect(response?.json()).toEqual({
+        text: 'Interactions are disabled',
+      });
+    });
+
     test('should return 200 Ok if message is disabled', async () => {
       verifySignatureMock.mockReturnValueOnce(true);
 
@@ -188,7 +214,7 @@ describe('Slack API', () => {
       expect(sendEphemeralMessageMock).toHaveBeenCalledTimes(1);
       expect(sendEphemeralMessageMock).toHaveBeenCalledWith({
         ephemeralText:
-          'Slack interactions are only available when running the entire workflow.',
+          'Test succeeded. Slack interactions are disabled in test mode and are only available when running the entire workflow.',
         responseUrl:
           'https://hooks.slack.com/actions/XXXXXXXX/XXXXXXXXX/XXXXXXXXX',
         userId: 'some_user_id',
diff --git a/packages/server/shared/src/lib/system/system-prop.ts b/packages/server/shared/src/lib/system/system-prop.ts
index a22548f59a..4242f16695 100644
--- a/packages/server/shared/src/lib/system/system-prop.ts
+++ b/packages/server/shared/src/lib/system/system-prop.ts
@@ -136,6 +136,8 @@ export enum SharedSystemProp {
   INTERNAL_BACKEND_PUBLIC_URL = 'INTERNAL_BACKEND_PUBLIC_URL',
 
   INTERNAL_PARALLEL_LOOP_ITERATIONS_LIMIT = 'INTERNAL_PARALLEL_LOOP_ITERATIONS_LIMIT',
+
+  DISABLE_SLACK_INTERACTIONS = 'DISABLE_SLACK_INTERACTIONS',
 }
 
 export enum WorkerSystemProps {


Reviews:
user: login: copilot-pull-request-reviewer[bot]
body: ## Pull Request Overview

This PR adds support for Slack interactions via URLs by introducing a feature flag and updating logic to distinguish between test mode and production environments.  
- Added the USE_SLACK_INTERACTIONS system property.  
- Updated Slack interaction controller to conditionally handle interactions based on the provided flag.  
- Modified the construction of action button messages and URL generation in Slack-related message and action modules.

### Reviewed Changes

Copilot reviewed 4 out of 6 changed files in this pull request and generated no comments.

| File | Description |
| ---- | ----------- |
| packages/server/shared/src/lib/system/system-prop.ts | Added new system property for enabling Slack interactions. |
| packages/server/api/src/app/slack/slack-interaction-module.ts | Implemented early return if Slack interactions are disabled. |
| packages/blocks/slack/src/lib/common/message-interactions.ts | Improved action block text clarity based on the clicking user. |
| packages/blocks/slack/src/lib/actions/request-action-message.ts | Updated action URL generation logic and SlackActionDefinition interface for Slack interactions. |

<details>
<summary>Files not reviewed (2)</summary>

* **.env.template**: Language not supported
* **deploy/docker-compose/.env.defaults**: Language not supported
</details>

<details>
<summary>Comments suppressed due to low confidence (2)</summary>

**packages/blocks/slack/src/lib/actions/request-action-message.ts:107**
* Using '|| true' causes useSlackInteractions to always evaluate to true, which negates the purpose of a feature flag. Consider replacing it with a nullish coalescing operator (e.g., '?? true') or a proper conditional check to correctly handle false values.
```
const useSlackInteractions = system.getBoolean(SharedSystemProp.USE_SLACK_INTERACTIONS) || true;
```
**packages/blocks/slack/src/lib/actions/request-action-message.ts:2**
* [nitpick] The identifier 'networkUtls' appears to be a typo; if this is intended to reference network utilities, consider renaming it to 'networkUtils' for clarity.
```
import { networkUtls, SharedSystemProp, system } from '@openops/server-shared';
```
</details>


state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-14T11:36:16Z
user: login: rita-gorokhod
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-14T11:36:36Z
user: login: rita-gorokhod
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-14T11:36:48Z
user: login: rita-gorokhod
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-14T11:48:25Z
user: login: rita-gorokhod
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-14T11:54:19Z
user: login: rita-gorokhod
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-14T12:29:22Z
user: login: copilot-pull-request-reviewer[bot]
body: ## Pull Request Overview

This PR supports Slack interactions via URLs by introducing a new DISABLE_SLACK_INTERACTIONS flag and updating relevant endpoints and tests.  
- Adds a new system property to enable toggling Slack interactions.  
- Updates API logic to return alternative responses when interactions are disabled.  
- Enhances and adds tests to cover both enabled and disabled interaction scenarios.

### Reviewed Changes

Copilot reviewed 7 out of 9 changed files in this pull request and generated no comments.

<details>
<summary>Show a summary per file</summary>

| File | Description |
| ---- | ----------- |
| packages/server/shared/src/lib/system/system-prop.ts | Added DISABLE_SLACK_INTERACTIONS enum value. |
| packages/server/api/test/integration/cloud/slack-integration/slack-interaction.test.ts | Added tests to verify 200 responses when interactions are disabled. |
| packages/server/api/src/app/slack/slack-interaction-module.ts | Updated evaluation to short-circuit processing when interactions are disabled. |
| packages/blocks/slack/test/wait-for-action.test.ts | Updated test names and assertions to reflect changes in message formatting. |
| packages/blocks/slack/test/request-action-message.test.ts | Introduced new tests to validate resumeUrl assignment with interactions disabled. |
| packages/blocks/slack/src/lib/common/message-interactions.ts | Updated button message formatting based on username presence. |
| packages/blocks/slack/src/lib/actions/request-action-message.ts | Modified logic to assign action URLs based on the interactions-disabled flag and refined type usage. |
</details>


<details>
<summary>Files not reviewed (2)</summary>

* **.env.template**: Language not supported
* **deploy/docker-compose/.env.defaults**: Language not supported
</details>

<details>
<summary>Comments suppressed due to low confidence (2)</summary>

**packages/blocks/slack/src/lib/actions/request-action-message.ts:107**
* [nitpick] Consider extracting the test URL ('https://static.openops.com/test_slack_interactions.txt') and similar message strings into constants to improve maintainability and consistency across the codebase.
```
const disableSlackInteractions = system.getBoolean(SharedSystemProp.DISABLE_SLACK_INTERACTIONS) || false;
```
**packages/server/api/src/app/slack/slack-interaction-module.ts:93**
* [nitpick] Consider adding a brief inline comment explaining the purpose of the interactionsDisabled flag and clarifying the varying response messages between test and production modes to aid future maintainers.
```
const interactionsDisabled: boolean = payload.message.metadata.event_payload.interactionsDisabled;
```
</details>


state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-19T13:02:35Z
user: login: bigfluffycookie
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-05-19T15:02:22Z
user: login: maor-rozenfeld
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-20T09:31:23Z

Review comments:
diff_hunk: @@ -115,7 +123,7 @@ async function evaluateUserInteraction(payload: any, reply: FastifyReply) {
     const userId = payload.user.id;
     const responseUrl = payload.response_url;
     const ephemeralText =
-      'Slack interactions are only available when running the entire workflow.';
+      'Test succeeded. Slack interactions are disabled in test mode and are only available when running the entire workflow.';
path: packages/server/api/src/app/slack/slack-interaction-module.ts
user: login: rita-gorokhod
body: <img width="909" alt="image" src="https://github.com/user-attachments/assets/b3e22434-0b8b-4143-b098-66509627399b" />

created_at: 2025-05-14T11:36:35Z
author_association: CONTRIBUTOR
diff_hunk: @@ -90,6 +90,14 @@ const slackInteractionController: FastifyPluginCallbackTypebox = (
 
 // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/explicit-function-return-type
 async function evaluateUserInteraction(payload: any, reply: FastifyReply) {
+  const interactionsEnabled: boolean =
path: packages/server/api/src/app/slack/slack-interaction-module.ts
user: login: rita-gorokhod
body: I will open a PR to do the same on the lambda
created_at: 2025-05-14T11:36:48Z
author_association: CONTRIBUTOR
diff_hunk: @@ -106,6 +104,27 @@ const sendMessageAskingForAction = async (
     conversationId,
   );
 
+  const useSlackInteractions =
+    system.getBoolean(SharedSystemProp.USE_SLACK_INTERACTIONS) || false;
+
+  if (!useSlackInteractions) {
+    const baseUrl = await networkUtls.getPublicUrl();
+
+    actions.forEach((action: SlackActionDefinition) => {
+      action.url = context.run.isTest
+        ? 'https://static.openops.com/test_slack_interactions.txt'
path: packages/blocks/slack/src/lib/actions/request-action-message.ts
user: login: rita-gorokhod
body: <img width="1018" alt="image" src="https://github.com/user-attachments/assets/8e2361b4-2cf0-44c2-90b6-adeee20f9b75" />

created_at: 2025-05-14T11:48:25Z
author_association: CONTRIBUTOR
diff_hunk: @@ -26,13 +28,17 @@ export function removeActionBlocks(blocks: any): any[] {
 }
 
 export function buildActionBlock(user: string, button: string) {
+  const whoClickedText = !isEmpty(user)
+    ? `User @${user} clicked on '${button}'`
+    : `Clicked on '${button}'`;
+
   const modifiedBlocks = [
     { type: 'divider' },
     {
       type: 'section',
       text: {
         type: 'mrkdwn',
-        text: `*:white_check_mark: Action received, user @${user} clicked on '${button}'*`,
+        text: `*:white_check_mark: Action received: ${whoClickedText}*`,
path: packages/blocks/slack/src/lib/common/message-interactions.ts
user: login: rita-gorokhod
body: <img width="375" alt="image" src="https://github.com/user-attachments/assets/b7901528-623c-46e2-a69d-4c9dc499d826" />

created_at: 2025-05-14T11:54:19Z
author_association: CONTRIBUTOR
diff_hunk: @@ -106,6 +104,27 @@ const sendMessageAskingForAction = async (
     conversationId,
   );
 
+  const disableSlackInteractions =
+    system.getBoolean(SharedSystemProp.DISABLE_SLACK_INTERACTIONS) || false;
path: packages/blocks/slack/src/lib/actions/request-action-message.ts
user: login: rita-gorokhod
body: for those that already have an old installation, they won't have it in their .env file, so by default I'm keeping the old behavior
created_at: 2025-05-14T12:29:22Z
author_association: CONTRIBUTOR
diff_hunk: @@ -106,6 +104,27 @@ const sendMessageAskingForAction = async (
     conversationId,
   );
 
+  const disableSlackInteractions =
+    system.getBoolean(SharedSystemProp.DISABLE_SLACK_INTERACTIONS) || false;
path: packages/blocks/slack/src/lib/actions/request-action-message.ts
user: login: maor-rozenfeld
body: A very confusing variable name.

We now have two slack actions mode - interactions and URL. So we can just call this variable SLACK_ACTIONS_MODE which can be either URL or interactions.

If it must stay as a boolean, it should be `ENABLE_SLACK_INTERACTION_MODE` or `ENABLE_SLACK_ACTIONS_BY_URL`, definitely not a "DISABLE" variable.
created_at: 2025-05-20T09:21:48Z
author_association: CONTRIBUTOR
diff_hunk: @@ -112,3 +112,4 @@ HOST_CLOUDSDK_CONFIG=
 # ---------------------------------------------------------
 
 OPS_CODE_BLOCK_MEMORY_LIMIT_IN_MB=256
+DISABLE_SLACK_INTERACTIONS=false
path: deploy/docker-compose/.env.defaults
user: login: maor-rozenfeld
body: You forgot the OPS prefix. It can join the slack connection variable a few lines above.
created_at: 2025-05-20T09:22:18Z
author_association: CONTRIBUTOR

PR comments:
user: login: linear[bot]
created_at: 2025-05-14T11:36:01Z
body: <p><a href="https://linear.app/openops/issue/OPS-1727/support-executing-slack-request-action-without-interactions">OPS-1727 Support executing slack Request Action without interactions</a></p>
author_association: NONE
user: login: sonarqubecloud[bot]
created_at: 2025-05-19T15:38:35Z
body: ## [![Quality Gate Passed](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/QualityGateBadge/qg-passed-20px.png 'Quality Gate Passed')](https://sonarcloud.io/dashboard?id=openops-cloud_openops&pullRequest=589) **Quality Gate passed**  
Issues  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0 New issues](https://sonarcloud.io/project/issues?id=openops-cloud_openops&pullRequest=589&issueStatuses=OPEN,CONFIRMED&sinceLeakPeriod=true)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/accepted-16px.png '') [0 Accepted issues](https://sonarcloud.io/project/issues?id=openops-cloud_openops&pullRequest=589&issueStatuses=ACCEPTED)

Measures  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0 Security Hotspots](https://sonarcloud.io/project/security_hotspots?id=openops-cloud_openops&pullRequest=589&issueStatuses=OPEN,CONFIRMED&sinceLeakPeriod=true)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0.0% Coverage on New Code](https://sonarcloud.io/component_measures?id=openops-cloud_openops&pullRequest=589&metric=new_coverage&view=list)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0.0% Duplication on New Code](https://sonarcloud.io/component_measures?id=openops-cloud_openops&pullRequest=589&metric=new_duplicated_lines_density&view=list)  
  
[See analysis details on SonarQube Cloud](https://sonarcloud.io/dashboard?id=openops-cloud_openops&pullRequest=589)


author_association: NONE