Skip to content

Commit bc89c1d

Browse files
committed
feat(slack/onboard-llmo): add new modal for reonboarding
1 parent 015e77a commit bc89c1d

File tree

4 files changed

+162
-11
lines changed

4 files changed

+162
-11
lines changed

src/controllers/slack.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,7 @@ export function initSlackBot(lambdaContext, App) {
8080
app.view('onboard_site_modal', actions.onboardSiteModal(lambdaContext));
8181
app.view('preflight_config_modal', actions.preflight_config_modal(lambdaContext));
8282
app.view('onboard_llmo_modal', actions.onboardLLMOModal(lambdaContext));
83+
app.view('reonboard_llmo_modal', actions.reonboardLLMOModal(lambdaContext));
8384

8485
return app;
8586
}
@@ -102,7 +103,7 @@ function parsePayload(data) {
102103
*/
103104
function SlackController(SlackApp) {
104105
// Acknowledge function for Slack events (no operation)
105-
const ack = () => {};
106+
const ack = () => { };
106107

107108
/**
108109
* Handles incoming events from Slack.

src/support/slack/actions/index.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ import approveOrg from './approve-org.js';
1515
import approveSiteCandidate from './approve-site-candidate.js';
1616
import ignoreSiteCandidate from './ignore-site-candidate.js';
1717
import rejectOrg from './reject-org.js';
18-
import { startLLMOOnboarding, onboardLLMOModal } from './onboard-llmo-modal.js';
18+
import { startLLMOOnboarding, onboardLLMOModal, reonboardLLMOModal } from './onboard-llmo-modal.js';
1919
import { onboardSiteModal, startOnboarding } from './onboard-modal.js';
2020
import { preflightConfigModal } from './preflight-config-modal.js';
2121
import openPreflightConfig from './open-preflight-config.js';
@@ -28,6 +28,7 @@ const actions = {
2828
rejectOrg,
2929
onboardSiteModal,
3030
onboardLLMOModal,
31+
reonboardLLMOModal,
3132
start_onboarding: startOnboarding,
3233
start_llmo_onboarding: startLLMOOnboarding,
3334
preflight_config_modal: preflightConfigModal,

src/support/slack/actions/onboard-llmo-modal.js

Lines changed: 157 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -237,6 +237,70 @@ async function elmoOnboardingModal(body, client, respond, brandURL) {
237237
});
238238
}
239239

240+
async function elmoReonboardingModal(body, client, respond, brandURL) {
241+
const { user } = body;
242+
243+
// Update the original message to show user's choice
244+
await respond({
245+
text: `:gear: ${user.name} started the reonboarding process... Missing configurations and entitlements will be added automatically.`,
246+
replace_original: true,
247+
});
248+
249+
// Capture original channel and thread context
250+
const originalChannel = body.channel?.id;
251+
const originalThreadTs = body.message?.thread_ts || body.message?.ts;
252+
253+
await client.views.open({
254+
trigger_id: body.trigger_id,
255+
view: {
256+
type: 'modal',
257+
callback_id: 'reonboard_llmo_modal',
258+
private_metadata: JSON.stringify({
259+
originalChannel,
260+
originalThreadTs,
261+
brandURL,
262+
}),
263+
title: {
264+
type: 'plain_text',
265+
text: 'Reonboard Site',
266+
},
267+
submit: {
268+
type: 'plain_text',
269+
text: 'Update IMS Org ID',
270+
},
271+
close: {
272+
type: 'plain_text',
273+
text: 'Cancel',
274+
},
275+
blocks: [
276+
{
277+
type: 'section',
278+
text: {
279+
type: 'mrkdwn',
280+
text: ':rocket: *Site Reonboarding*\n\nIf necessary, provide details to update the IMS organization ID.',
281+
},
282+
},
283+
{
284+
type: 'input',
285+
block_id: 'ims_org_input',
286+
element: {
287+
type: 'plain_text_input',
288+
action_id: 'ims_org_id',
289+
placeholder: {
290+
type: 'plain_text',
291+
text: 'ABC123@AdobeOrg',
292+
},
293+
},
294+
label: {
295+
type: 'plain_text',
296+
text: 'IMS Organization ID',
297+
},
298+
},
299+
],
300+
},
301+
});
302+
}
303+
240304
async function createOrg(imsOrgId, lambdaCtx, slackCtx) {
241305
const { log, imsClient, dataAccess } = lambdaCtx;
242306
const { say } = slackCtx;
@@ -321,11 +385,9 @@ export function startLLMOOnboarding(lambdaContext) {
321385
const brand = config.getLlmoBrand();
322386

323387
if (brand) {
324-
await respond({
325-
text: `:update-progress: Brand ${brand} of ${brandURL} already onboarded; initiating reonboarding process`,
326-
replace_original: true,
327-
});
328388
log.debug(`Brand ${brand} of ${brandURL} already onboarded; initiating reonboarding process`);
389+
await elmoReonboardingModal(body, client, respond, brandURL);
390+
return;
329391
}
330392

331393
await elmoOnboardingModal(body, client, respond, brandURL);
@@ -774,3 +836,94 @@ export function onboardLLMOModal(lambdaContext) {
774836
}
775837
};
776838
}
839+
840+
/* c8 ignore start */
841+
/* Handles submission */
842+
export function reonboardLLMOModal(lambdaContext) {
843+
const { dataAccess, log } = lambdaContext;
844+
const { Site } = dataAccess;
845+
846+
return async ({ ack, body, client }) => {
847+
try {
848+
log.debug('Starting onboarding process...');
849+
const { view, user } = body;
850+
const { values } = view.state;
851+
852+
// Extract original channel and thread context from private metadata
853+
let originalChannel;
854+
let originalThreadTs;
855+
let brandURL;
856+
try {
857+
/* c8 ignore next */
858+
const metadata = JSON.parse(view.private_metadata || '{}');
859+
originalChannel = metadata.originalChannel;
860+
originalThreadTs = metadata.originalThreadTs;
861+
brandURL = metadata.brandURL;
862+
} catch (error) {
863+
log.warn('Failed to parse private metadata:', error);
864+
}
865+
866+
const imsOrgId = values.ims_org_input.ims_org_id.value;
867+
868+
if (!imsOrgId) {
869+
await ack({
870+
response_action: 'errors',
871+
errors: {
872+
ims_org_input: !imsOrgId ? 'IMS Org ID is required' : undefined,
873+
},
874+
});
875+
return;
876+
}
877+
878+
log.info('Onboarding request with parameters:', {
879+
imsOrgId,
880+
brandURL,
881+
originalChannel,
882+
originalThreadTs,
883+
});
884+
885+
// eslint-disable-next-line max-statements-per-line
886+
await new Promise((resolve) => { setTimeout(resolve, 500); });
887+
await ack();
888+
889+
// Create a slack context for the onboarding process
890+
// Use original channel/thread if available, otherwise fall back to DM
891+
const responseChannel = originalChannel || body.user.id;
892+
const responseThreadTs = originalChannel ? originalThreadTs : undefined;
893+
894+
const slackContext = {
895+
say: async (message) => {
896+
await client.chat.postMessage({
897+
channel: responseChannel,
898+
text: message,
899+
thread_ts: responseThreadTs,
900+
});
901+
},
902+
client,
903+
channelId: responseChannel,
904+
threadTs: responseThreadTs,
905+
};
906+
907+
const site = await Site.findByBaseURL(brandURL);
908+
909+
await checkOrg(imsOrgId, site, lambdaContext, slackContext);
910+
911+
// create entitlement
912+
await createEntitlementAndEnrollment(site, lambdaContext, slackContext);
913+
914+
// create OrganizationIdentiyProvider
915+
await createOrganizationIdentityProvider(site, lambdaContext, slackContext);
916+
917+
log.debug(`Reonboard LLMO modal processed for user ${user.id}, site ${brandURL}`);
918+
} catch (e) {
919+
log.error('Error handling reonboard site modal:', e);
920+
await ack({
921+
response_action: 'errors',
922+
errors: {
923+
brand_name_input: 'There was an error processing the reonboarding request.',
924+
},
925+
});
926+
}
927+
};
928+
}
929+
/* c8 ignore end */

test/support/slack/actions/onboard-llmo-modal.test.js

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1620,7 +1620,7 @@ example-com:
16201620
expect(lambdaCtx.log.debug).to.have.been.calledWith('User user123 started full onboarding process for https://example.com.');
16211621
});
16221622

1623-
it('should show show reonboarding message when site is found but already has brand configured', async () => {
1623+
it('should show reonboarding message when site is found but already has brand configured', async () => {
16241624
const mockBody = {
16251625
user: { id: 'user123' },
16261626
actions: [{ value: 'https://example.com' }],
@@ -1661,10 +1661,6 @@ example-com:
16611661

16621662
expect(mockAck).to.have.been.called;
16631663
expect(lambdaCtx.dataAccess.Site.findByBaseURL).to.have.been.calledWith('https://example.com');
1664-
expect(mockRespond).to.have.been.calledWith({
1665-
text: ':update-progress: Brand Existing Brand of https://example.com already onboarded; initiating reonboarding process',
1666-
replace_original: true,
1667-
});
16681664
expect(lambdaCtx.log.debug).to.have.been.calledWith('Brand Existing Brand of https://example.com already onboarded; initiating reonboarding process');
16691665
});
16701666

0 commit comments

Comments
 (0)