feat: include an 'api' option to customize the slack api method url (#409)

This commit is contained in:
Eden Zimbelman
2025-03-14 15:13:15 -07:00
committed by GitHub
parent 21be76f4cc
commit f841ea518d
8 changed files with 47 additions and 16 deletions
+21
View File
@@ -452,6 +452,26 @@ Behind the scenes, [automatic retries][retries] are handled with the
[`@slack/web-api`][slack-web-api] package for Slack API methods, and
[`axios-retry`][axios-retry] when sending with a webhook.
### Sending to a custom API URL
In certain circumstances, such as testing the sent payload, a
[custom API URL][custom-api-url] can be used to change where `method` requests
are sent:
```yaml
- name: Send to a custom API URL
uses: slackapi/[email protected]
with:
api: http://localhost:8080
method: chat.postMessage
token: ${{ secrets.SLACK_BOT_TOKEN }}
payload: |
channel: ${{ secrets.SLACK_CHANNEL_ID }}
text: "What's happening on localhost?"
```
The default value of `api` is "https://slack.com/api/" for steps using `method`.
## Expected outputs
Each technique above [outputs values][github-outputs] that can be used as inputs
@@ -517,6 +537,7 @@ All contributions are encouraged! Check out the
[config-tokens]: https://api.slack.com/reference/manifests#config-tokens
[contributing]: .github/contributing.md
[conversation]: https://api.slack.com/types/conversation
[custom-api-url]: https://tools.slack.dev/node-slack-sdk/web-api/#custom-api-url
[epoch]: https://en.wikipedia.org/wiki/Unix_time
[event-context]: https://github.com/actions/toolkit/blob/main/packages/github/src/context.ts#L6
[event-payload]: https://docs.github.com/en/webhooks/webhook-events-and-payloads
+3
View File
@@ -2,6 +2,9 @@ name: "Slack: Send to Slack"
author: "slackapi"
description: "Send data to Slack to start a Slack workflow in Workflow Builder, call a Slack API method, or post a message into a channel"
inputs:
api:
description: "A custom API URL to send Slack API method requests to."
required: false
errors:
default: "false"
description: "If the step exits with an error on errors or continues."
+1
View File
@@ -26,6 +26,7 @@ export default class Client {
agent: this.proxies(config)?.httpsAgent,
logger: config.logger,
retryConfig: this.retries(config.inputs.retries),
slackApiUrl: config.inputs.api || undefined,
});
try {
/**
+2
View File
@@ -40,6 +40,7 @@ export default class Config {
/**
* @typedef Inputs - Values provided to this job.
* @property {string?} api - A custom API URL to send method requests to.
* @property {boolean} errors - If the job should exit after errors or succeed.
* @property {string?} method - The Slack API method to call.
* @property {string?} payload - Request contents from the provided input.
@@ -101,6 +102,7 @@ export default class Config {
this.logger = new Logger(core).logger;
this.webapi = webapi;
this.inputs = {
api: core.getInput("api"),
errors: core.getBooleanInput("errors"),
method: core.getInput("method"),
payload: core.getInput("payload"),
+15 -13
View File
@@ -73,6 +73,7 @@ describe("client", () => {
core: core,
logger: new Logger(core).logger,
inputs: {
api: "http://localhost:8080/api",
method: "pins.add",
retries: "10",
token: "xoxb-example-002",
@@ -86,6 +87,7 @@ describe("client", () => {
agent: undefined,
logger: config.logger,
retryConfig: webapi.retryPolicies.tenRetriesInAboutThirtyMinutes,
slackApiUrl: "http://localhost:8080/api",
}),
);
});
@@ -110,10 +112,10 @@ describe("client", () => {
mocks.core.getInput.withArgs("method").returns("chat.postMessage");
mocks.core.getInput.withArgs("token").returns("xoxb-example");
mocks.core.getInput.withArgs("payload").returns(JSON.stringify(args));
mocks.api.resolves(response);
mocks.calls.resolves(response);
await send(mocks.core);
assert.deepEqual(mocks.api.getCall(0).firstArg, "chat.postMessage");
assert.deepEqual(mocks.api.getCall(0).lastArg, args);
assert.deepEqual(mocks.calls.getCall(0).firstArg, "chat.postMessage");
assert.deepEqual(mocks.calls.getCall(0).lastArg, args);
assert.equal(mocks.core.setOutput.getCall(0).firstArg, "ok");
assert.equal(mocks.core.setOutput.getCall(0).lastArg, true);
assert.equal(mocks.core.setOutput.getCall(1).firstArg, "response");
@@ -158,10 +160,10 @@ describe("client", () => {
mocks.core.getInput.withArgs("method").returns("chat.postMessage");
mocks.core.getInput.withArgs("token").returns("xoxb-example");
mocks.core.getInput.withArgs("payload").returns(JSON.stringify(args));
mocks.api.resolves(response);
mocks.calls.resolves(response);
await send(mocks.core);
assert.deepEqual(mocks.api.getCall(0).firstArg, "chat.postMessage");
assert.deepEqual(mocks.api.getCall(0).lastArg, args);
assert.deepEqual(mocks.calls.getCall(0).firstArg, "chat.postMessage");
assert.deepEqual(mocks.calls.getCall(0).lastArg, args);
assert.equal(mocks.core.setOutput.getCall(0).firstArg, "ok");
assert.equal(mocks.core.setOutput.getCall(0).lastArg, true);
assert.equal(mocks.core.setOutput.getCall(1).firstArg, "response");
@@ -194,9 +196,9 @@ describe("client", () => {
mocks.core.getInput.withArgs("method").returns("files.uploadV2");
mocks.core.getInput.withArgs("token").returns("xoxp-example");
mocks.core.getInput.withArgs("payload").returns(JSON.stringify(args));
mocks.api.resolves(response);
mocks.calls.resolves(response);
await send(mocks.core);
assert.deepEqual(mocks.api.getCall(0).lastArg, args);
assert.deepEqual(mocks.calls.getCall(0).lastArg, args);
assert.equal(mocks.core.setOutput.getCall(0).firstArg, "ok");
assert.equal(mocks.core.setOutput.getCall(0).lastArg, true);
assert.equal(mocks.core.setOutput.getCall(1).firstArg, "response");
@@ -231,7 +233,7 @@ describe("client", () => {
mocks.core.getInput.withArgs("method").returns("chat.postMessage");
mocks.core.getInput.withArgs("token").returns("xoxb-example");
mocks.core.getInput.withArgs("payload").returns(`"text": "hello"`);
mocks.api.rejects(errors.requestErrorWithOriginal(response, true));
mocks.calls.rejects(errors.requestErrorWithOriginal(response, true));
await send(mocks.core);
assert.fail("Expected an error but none was found");
} catch (error) {
@@ -266,7 +268,7 @@ describe("client", () => {
mocks.core.getInput.withArgs("method").returns("chat.postMessage");
mocks.core.getInput.withArgs("token").returns("xoxb-example");
mocks.core.getInput.withArgs("payload").returns(`"text": "hello"`);
mocks.api.rejects(errors.httpErrorFromResponse(response));
mocks.calls.rejects(errors.httpErrorFromResponse(response));
await send(mocks.core);
assert.fail("Expected an error but none was found");
} catch (error) {
@@ -302,7 +304,7 @@ describe("client", () => {
mocks.core.getInput.withArgs("method").returns("chat.postMessage");
mocks.core.getInput.withArgs("token").returns("xoxb-example");
mocks.core.getInput.withArgs("payload").returns(`"text": "hello"`);
mocks.api.rejects(errors.platformErrorFromResult(response));
mocks.calls.rejects(errors.platformErrorFromResult(response));
await send(mocks.core);
assert.fail("Expected an error but none was found");
} catch (error) {
@@ -331,7 +333,7 @@ describe("client", () => {
mocks.core.getInput.withArgs("method").returns("chat.postMessage");
mocks.core.getInput.withArgs("token").returns("xoxb-example");
mocks.core.getInput.withArgs("payload").returns(`"text": "hello"`);
mocks.api.rejects(errors.platformErrorFromResult(response));
mocks.calls.rejects(errors.platformErrorFromResult(response));
await send(mocks.core);
assert.fail("Expected an error but none was found");
} catch (error) {
@@ -357,7 +359,7 @@ describe("client", () => {
mocks.core.getInput.withArgs("method").returns("chat.postMessage");
mocks.core.getInput.withArgs("token").returns("xoxb-example");
mocks.core.getInput.withArgs("payload").returns(`"text": "hello"`);
mocks.api.rejects(errors.rateLimitedErrorWithDelay(12));
mocks.calls.rejects(errors.rateLimitedErrorWithDelay(12));
await send(mocks.core);
assert.fail("Expected an error but none was found");
} catch (error) {
+2
View File
@@ -19,6 +19,7 @@ describe("config", () => {
describe("inputs", () => {
it("valid values are collected from the action inputs", async () => {
mocks.core.getInput.withArgs("api").returns("http://localhost:8080");
mocks.core.getBooleanInput.withArgs("errors").returns(true);
mocks.core.getInput.withArgs("method").returns("chat.postMessage");
mocks.core.getInput.withArgs("payload").returns('"hello": "world"');
@@ -26,6 +27,7 @@ describe("config", () => {
mocks.core.getInput.withArgs("retries").returns("0");
mocks.core.getInput.withArgs("token").returns("xoxb-example");
const config = new Config(mocks.core);
assert.equal(config.inputs.api, "http://localhost:8080");
assert.equal(config.inputs.errors, true);
assert.equal(config.inputs.method, "chat.postMessage");
assert.equal(config.inputs.payload, '"hello": "world"');
+2 -2
View File
@@ -43,8 +43,8 @@ export class Mock {
*/
constructor() {
this.sandbox = sinon.createSandbox();
this.api = sinon.stub(webapi.WebClient.prototype, "apiCall");
this.axios = this.sandbox.stub(axios);
this.calls = sinon.stub(webapi.WebClient.prototype, "apiCall");
this.core = this.sandbox.stub(core);
this.fs = this.sandbox.stub(fs);
this.webapi = {
@@ -63,8 +63,8 @@ export class Mock {
*/
reset() {
this.sandbox.reset();
this.api.resetHistory();
this.axios.post.resetHistory();
this.calls.resetHistory();
this.core.getInput.reset();
this.core.getInput.withArgs("errors").returns("false");
this.core.getInput.withArgs("retries").returns("5");
+1 -1
View File
@@ -40,7 +40,7 @@ describe("send", () => {
it("token", async () => {
process.env.SLACK_WEBHOOK_URL = "https://example.com"; // https://github.com/slackapi/slack-github-action/issues/373
mocks.api.resolves({ ok: true });
mocks.calls.resolves({ ok: true });
mocks.core.getInput.withArgs("method").returns("chat.postMessage");
mocks.core.getInput.withArgs("token").returns("xoxb-example");
mocks.core.getInput.withArgs("payload").returns('"text": "hello"');