Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Slack migration from v1 upload #655

Merged
merged 4 commits into from
Mar 6, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
73 changes: 46 additions & 27 deletions lib/actions/slack/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,7 @@ const handleExecute = async (request, slack) => {
const fileName = request.formParams.filename ? request.completeFilename() : request.suggestedFilename();
try {
const isUserToken = request.formParams.channel.startsWith("U") || request.formParams.channel.startsWith("W");
const forceV1Upload = process.env.FORCE_V1_UPLOAD;
const channel = request.formParams.channel;
if (!request.empty()) {
const buffs = [];
await request.stream(async (readable) => {
Expand All @@ -148,39 +148,58 @@ const handleExecute = async (request, slack) => {
const buffer = Buffer.concat(buffs);
const comment = request.formParams.initial_comment ? request.formParams.initial_comment : "";
winston.info(`${LOG_PREFIX} Attempting to send ${buffer.byteLength} bytes to Slack`, { webhookId });
// Unfortunately UploadV2 does not provide a way to upload files
// to user tokens which are common in Looker schedules
// (UXXXXXXX)
if (isUserToken || forceV1Upload) {
winston.info(`${LOG_PREFIX} V1 Upload of file`, { webhookId });
await slack.files.upload({
file: buffer,
filename: fileName,
channels: request.formParams.channel,
initial_comment: comment,
winston.info(`${LOG_PREFIX} V2 Upload of file`, { webhookId });
const res = await slack.files.getUploadURLExternal({
filename: fileName,
length: buffer.byteLength,
});
const upload_url = res.upload_url;
// Upload file to Slack
await gaxios.request({
method: "POST",
url: upload_url,
data: buffer,
});
// Finalize upload and give metadata for channel, title and
// comment for the file to be posted.
if (isUserToken) {
// If we have a user token, we need to convert the
// UXXXXX token into a DXXXXX token. Conversations.open()
// requires channels:manage and extra *:write scopes
// which will break existing schedules. Posting a message
// first only requires chat:write which we already have.
// the response returns a DXXXX token which we can use
// to upload to the channel
const postResponse = await slack.chat.postMessage({
channel,
text: comment.length !== 0 ? comment : "Looker Data",
}).catch((e) => {
winston.error("Issue posting message", { webhookId });
reject(e);
});
const directChannel = postResponse === null || postResponse === void 0 ? void 0 : postResponse.channel;
const resp2 = await slack.files.completeUploadExternal({
files: [{
id: res.file_id ? res.file_id : "",
title: fileName,
}],
channel_id: directChannel,
}).catch((e) => {
winston.error(`${LOG_PREFIX} ${e.message}`, { webhookId });
reject(e);
});
winston.info(`response complete : ${JSON.stringify(resp2)}`);
// Workaround for regression in V2 upload, the initial
// comment does not support markdown formatting, breaking
// customer links
}
else {
winston.info(`${LOG_PREFIX} V2 Upload of file`, { webhookId });
const res = await slack.files.getUploadURLExternal({
filename: fileName,
length: buffer.byteLength,
});
const upload_url = res.upload_url;
// Upload file to Slack
await gaxios.request({
method: "POST",
url: upload_url,
data: buffer,
});
// Finalize upload and give metadata for channel, title and
// comment for the file to be posted.
await slack.files.completeUploadExternal({
files: [{
id: res.file_id ? res.file_id : "",
title: fileName,
}],
channel_id: request.formParams.channel,
channel_id: channel,
}).catch((e) => {
winston.error(`${LOG_PREFIX} ${e.message}`, { webhookId });
reject(e);
Expand All @@ -190,7 +209,7 @@ const handleExecute = async (request, slack) => {
// customer links
if (comment !== "") {
await slack.chat.postMessage({
channel: request.formParams.channel,
channel,
text: comment,
}).catch((e) => {
winston.error(`${LOG_PREFIX} ${e.message}`, { webhookId });
Expand Down
46 changes: 3 additions & 43 deletions src/actions/slack/test_utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,25 +8,6 @@ import { getDisplayedFormFields, handleExecute } from "./utils"

const stubFileName = "stubSuggestedFilename"

function expectSlackMatchV1(request: Hub.ActionRequest, optionsMatch: FilesUploadArguments) {
const slackClient = new WebClient("someToken")
const expectedBuffer = optionsMatch.file as Buffer
delete optionsMatch.file
const filesUploadSpy = sinon.spy(async (params: any) => {
chai.expect(params.file.toString()).to.equal(expectedBuffer.toString())
return {}
})
const stubClient = sinon.stub(slackClient.files, "upload")
.callsFake(filesUploadSpy)
const stubSuggestedFilename = sinon.stub(request as any, "suggestedFilename")
.callsFake(() => stubFileName)
return chai.expect(handleExecute(request, slackClient)).to.be.fulfilled.then(() => {
chai.expect(filesUploadSpy).to.have.been.calledWithMatch(optionsMatch)
stubClient.restore()
stubSuggestedFilename.restore()
})
}

function expectSlackMatch(request: Hub.ActionRequest, optionsMatch: FilesUploadArguments) {

const slackClient = new WebClient("someToken")
Expand Down Expand Up @@ -366,28 +347,7 @@ describe(`slack/utils unit tests`, () => {
})
})

it("uses V1 if channel if FORCE_V1_UPLOAD is on", () => {
const request = new Hub.ActionRequest()
request.type = Hub.ActionType.Query
request.formParams = {
channel: "mychannel",
initial_comment: "mycomment",
}
request.attachment = {
dataBuffer: Buffer.from("1,2,3,4", "utf8"),
fileExtension: "csv",
}
process.env.FORCE_V1_UPLOAD = "on"
const results = expectSlackMatchV1(request, {
channels: request.formParams.channel,
initial_comment: request.formParams.initial_comment,
file: Buffer.from("1,2,3,4", "utf8"),
})
process.env.FORCE_V1_UPLOAD = ""
return results
})

it("uses V1 if channel is a User token", () => {
it("uses convertUM if channel is a User token", () => {
const request = new Hub.ActionRequest()
request.type = Hub.ActionType.Query
request.formParams = {
Expand All @@ -398,7 +358,7 @@ describe(`slack/utils unit tests`, () => {
dataBuffer: Buffer.from("1,2,3,4", "utf8"),
fileExtension: "csv",
}
return expectSlackMatchV1(request, {
return expectSlackMatch(request, {
channels: request.formParams.channel,
initial_comment: request.formParams.initial_comment,
file: Buffer.from("1,2,3,4", "utf8"),
Expand All @@ -416,7 +376,7 @@ describe(`slack/utils unit tests`, () => {
dataBuffer: Buffer.from("1,2,3,4", "utf8"),
fileExtension: "csv",
}
return expectSlackMatchV1(request, {
return expectSlackMatch(request, {
channels: request.formParams.channel,
initial_comment: request.formParams.initial_comment,
file: Buffer.from("1,2,3,4", "utf8"),
Expand Down
78 changes: 48 additions & 30 deletions src/actions/slack/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,6 @@ export const getDisplayedFormFields = async (slack: WebClient, channelType: stri

return response
}

export const handleExecute = async (request: Hub.ActionRequest, slack: WebClient): Promise<Hub.ActionResponse> => {
const response = new Hub.ActionResponse({success: true})
if (!request.formParams.channel) {
Expand All @@ -146,7 +145,7 @@ export const handleExecute = async (request: Hub.ActionRequest, slack: WebClient

try {
const isUserToken = request.formParams.channel.startsWith("U") || request.formParams.channel.startsWith("W")
const forceV1Upload = process.env.FORCE_V1_UPLOAD
const channel = request.formParams.channel
if (!request.empty()) {
const buffs: any[] = []
await request.stream(async (readable) => {
Expand All @@ -167,40 +166,59 @@ export const handleExecute = async (request: Hub.ActionRequest, slack: WebClient
{webhookId},
)

// Unfortunately UploadV2 does not provide a way to upload files
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this comment now defunct? Do we have a way to upload files to user tokens with UploadV2 now?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We don't, we have a way to convert the tokens though

// to user tokens which are common in Looker schedules
// (UXXXXXXX)
if (isUserToken || forceV1Upload) {
winston.info(`${LOG_PREFIX} V1 Upload of file`, {webhookId})
await slack.files.upload({
file: buffer,
filename: fileName,
channels: request.formParams.channel,
initial_comment: comment,
})
} else {
winston.info(`${LOG_PREFIX} V2 Upload of file`, {webhookId})
const res = await slack.files.getUploadURLExternal({
filename: fileName,
length: buffer.byteLength,
})
const upload_url = res.upload_url
winston.info(`${LOG_PREFIX} V2 Upload of file`, {webhookId})
const res = await slack.files.getUploadURLExternal({
filename: fileName,
length: buffer.byteLength,
})
const upload_url = res.upload_url

// Upload file to Slack
await gaxios.request({
method: "POST",
url: upload_url,
data: buffer,
})
// Upload file to Slack
await gaxios.request({
method: "POST",
url: upload_url,
data: buffer,
})

// Finalize upload and give metadata for channel, title and
// comment for the file to be posted.
// Finalize upload and give metadata for channel, title and
// comment for the file to be posted.
if (isUserToken) {
// If we have a user token, we need to convert the
// UXXXXX token into a DXXXXX token. Conversations.open()
// requires channels:manage and extra *:write scopes
// which will break existing schedules. Posting a message
// first only requires chat:write which we already have.
// the response returns a DXXXX token which we can use
// to upload to the channel
const postResponse = await slack.chat.postMessage({
channel,
text: comment.length !== 0 ? comment : "Looker Data",
}).catch((e: any) => {
winston.error("Issue posting message", {webhookId})
reject(e)
})
const directChannel = postResponse?.channel
const resp2 = await slack.files.completeUploadExternal({
files: [{
id: res.file_id ? res.file_id : "",
title: fileName,
}],
channel_id: directChannel,
}).catch((e: any) => {
winston.error(`${LOG_PREFIX} ${e.message}`, {webhookId})
reject(e)
})
winston.info(`response complete : ${JSON.stringify(resp2)}`)
// Workaround for regression in V2 upload, the initial
// comment does not support markdown formatting, breaking
// customer links
} else {
await slack.files.completeUploadExternal({
files: [{
id: res.file_id ? res.file_id : "",
title: fileName,
}],
channel_id: request.formParams.channel,
channel_id: channel,
}).catch((e: any) => {
winston.error(`${LOG_PREFIX} ${e.message}`, {webhookId})
reject(e)
Expand All @@ -210,7 +228,7 @@ export const handleExecute = async (request: Hub.ActionRequest, slack: WebClient
// customer links
if (comment !== "") {
await slack.chat.postMessage({
channel: request.formParams.channel!,
channel,
text: comment,
}).catch((e: any) => {
winston.error(`${LOG_PREFIX} ${e.message}`, {webhookId})
Expand Down