Skip to content

Commit 7703ec4

Browse files
committed
feat(slack/onboard-llmo): add IMS org onboarding option
1 parent 6d2012b commit 7703ec4

File tree

6 files changed

+635
-7
lines changed

6 files changed

+635
-7
lines changed

src/controllers/slack.js

Lines changed: 1 addition & 0 deletions
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('onboard_llmo_org_modal', actions.onboardLLMOOrgModal(lambdaContext));
8384
app.view('update_ims_org_modal', actions.updateIMSOrgModal(lambdaContext));
8485

8586
return app;

src/support/slack/actions/index.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@ import {
2121
addEntitlementsAction,
2222
updateOrgAction,
2323
updateIMSOrgModal,
24+
startLLMOOrgOnboarding,
25+
onboardLLMOOrgModal,
2426
} from './onboard-llmo-modal.js';
2527
import { onboardSiteModal, startOnboarding } from './onboard-modal.js';
2628
import { preflightConfigModal } from './preflight-config-modal.js';
@@ -34,9 +36,11 @@ const actions = {
3436
rejectOrg,
3537
onboardSiteModal,
3638
onboardLLMOModal,
39+
onboardLLMOOrgModal,
3740
updateIMSOrgModal,
3841
start_onboarding: startOnboarding,
3942
start_llmo_onboarding: startLLMOOnboarding,
43+
start_llmo_org_onboarding: startLLMOOrgOnboarding,
4044
preflight_config_modal: preflightConfigModal,
4145
open_preflight_config: openPreflightConfig,
4246
add_entitlements_action: addEntitlementsAction,

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

Lines changed: 194 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -791,3 +791,197 @@ export function updateIMSOrgModal(lambdaContext) {
791791
}
792792
};
793793
}
794+
795+
/* Handles "Start Onboarding" button click for IMS org onboarding */
796+
export function startLLMOOrgOnboarding(lambdaContext) {
797+
const { log } = lambdaContext;
798+
799+
return async ({
800+
ack, body, client, respond,
801+
}) => {
802+
try {
803+
await ack();
804+
805+
const { user } = body;
806+
807+
await respond({
808+
text: `:gear: ${user.name} started the IMS org onboarding process...`,
809+
replace_original: true,
810+
});
811+
812+
const originalChannel = body.channel?.id;
813+
const originalThreadTs = body.message?.thread_ts || body.message?.ts;
814+
815+
await client.views.open({
816+
trigger_id: body.trigger_id,
817+
view: {
818+
type: 'modal',
819+
callback_id: 'onboard_llmo_org_modal',
820+
private_metadata: JSON.stringify({
821+
originalChannel,
822+
originalThreadTs,
823+
}),
824+
title: {
825+
type: 'plain_text',
826+
text: 'Onboard IMS Org',
827+
},
828+
submit: {
829+
type: 'plain_text',
830+
text: 'Start Onboarding',
831+
},
832+
close: {
833+
type: 'plain_text',
834+
text: 'Cancel',
835+
},
836+
blocks: [
837+
{
838+
type: 'section',
839+
text: {
840+
type: 'mrkdwn',
841+
text: ':rocket: *LLMO IMS Org Onboarding*\n\nProvide the IMS Organization ID to onboard for LLMO.',
842+
},
843+
},
844+
{
845+
type: 'input',
846+
block_id: 'ims_org_input',
847+
element: {
848+
type: 'plain_text_input',
849+
action_id: 'ims_org_id',
850+
placeholder: {
851+
type: 'plain_text',
852+
text: 'ABC123@AdobeOrg',
853+
},
854+
},
855+
label: {
856+
type: 'plain_text',
857+
text: 'IMS Organization ID',
858+
},
859+
},
860+
],
861+
},
862+
});
863+
864+
log.debug(`User ${user.id} started IMS org onboarding process.`);
865+
} catch (error) {
866+
log.error('Error starting IMS org onboarding:', error);
867+
await postErrorMessage(respond, error);
868+
}
869+
};
870+
}
871+
872+
/* Handles IMS org onboarding modal submission */
873+
export function onboardLLMOOrgModal(lambdaContext) {
874+
const { log, dataAccess, imsClient } = lambdaContext;
875+
876+
return async ({ ack, body, client }) => {
877+
try {
878+
const { user, view } = body;
879+
const { values } = view.state;
880+
881+
const imsOrgId = values.ims_org_input?.ims_org_id?.value?.trim();
882+
const metadata = JSON.parse(view.private_metadata || '{}');
883+
const { originalChannel, originalThreadTs } = metadata;
884+
885+
if (!imsOrgId) {
886+
await ack({
887+
response_action: 'errors',
888+
errors: {
889+
ims_org_input: 'IMS Organization ID is required',
890+
},
891+
});
892+
return;
893+
}
894+
895+
await ack();
896+
const responseChannel = originalChannel || body.user.id;
897+
const responseThreadTs = originalChannel ? originalThreadTs : undefined;
898+
899+
const slackContext = {
900+
say: async (message) => {
901+
await client.chat.postMessage({
902+
channel: responseChannel,
903+
text: message,
904+
thread_ts: responseThreadTs,
905+
});
906+
},
907+
client,
908+
channelId: responseChannel,
909+
threadTs: responseThreadTs,
910+
};
911+
912+
await slackContext.say(`:gear: Starting LLMO IMS org onboarding for *${imsOrgId}*...`);
913+
914+
// Create or find organization
915+
const { Organization } = dataAccess;
916+
let organization = await Organization.findByImsOrgId(imsOrgId);
917+
918+
if (!organization) {
919+
log.info(`Creating new organization for IMS Org ID: ${imsOrgId}`);
920+
await slackContext.say(`Creating organization for IMS Org ID: ${imsOrgId}`);
921+
922+
// Fetch IMS org details
923+
let imsOrgDetails;
924+
try {
925+
imsOrgDetails = await imsClient.getImsOrganizationDetails(imsOrgId);
926+
} catch (error) {
927+
log.error(`Error retrieving IMS Org details: ${error.message}`);
928+
await slackContext.say(`:x: Could not find an IMS org with the ID *${imsOrgId}*.`);
929+
return;
930+
}
931+
932+
if (!imsOrgDetails) {
933+
await slackContext.say(`:x: Could not find an IMS org with the ID *${imsOrgId}*.`);
934+
return;
935+
}
936+
937+
organization = await Organization.create({
938+
name: imsOrgDetails.orgName,
939+
imsOrgId,
940+
});
941+
await organization.save();
942+
log.info(`Created organization ${organization.getId()} for IMS Org ID: ${imsOrgId}`);
943+
} else {
944+
log.info(`Found existing organization ${organization.getId()} for IMS Org ID: ${imsOrgId}`);
945+
await slackContext.say(`Found existing organization for IMS Org ID: ${imsOrgId}`);
946+
}
947+
948+
try {
949+
const tierClient = TierClient.createForOrg(lambdaContext, organization, LLMO_PRODUCT_CODE);
950+
const { entitlement: existingEntitlement } = await tierClient.checkValidEntitlement();
951+
const { entitlement } = await tierClient.createEntitlement(LLMO_TIER);
952+
const wasNewlyCreated = !existingEntitlement
953+
|| existingEntitlement.getId() !== entitlement.getId();
954+
955+
if (wasNewlyCreated) {
956+
log.info(`Successfully created LLMO entitlement ${entitlement.getId()} for organization ${organization.getId()}`);
957+
} else {
958+
log.info(`Found existing LLMO entitlement ${entitlement.getId()} for organization ${organization.getId()}`);
959+
}
960+
961+
await slackContext.say(`:white_check_mark: *LLMO IMS org onboarding completed successfully!*
962+
963+
*Organization:* ${organization.getName()}
964+
*IMS Org ID:* ${imsOrgId}
965+
*Entitlement ID:* ${entitlement.getId()}
966+
*Tier:* ${LLMO_TIER}
967+
*Status:* ${wasNewlyCreated ? 'New entitlement created' : 'Existing entitlement found and verified'}
968+
969+
The organization has been onboarded for LLMO.`);
970+
} catch (error) {
971+
log.error(`Error creating entitlement for organization: ${error.message}`);
972+
await slackContext.say(`:x: Failed to create LLMO entitlement for organization: ${error.message}`);
973+
return;
974+
}
975+
976+
log.debug(`IMS org onboarding completed for ${imsOrgId} by user ${user.id}`);
977+
} catch (error) {
978+
log.error('Error handling IMS org onboarding modal:', error);
979+
await ack({
980+
response_action: 'errors',
981+
errors: {
982+
ims_org_input: 'There was an error processing the onboarding request.',
983+
},
984+
});
985+
}
986+
};
987+
}

src/support/slack/commands/llmo-onboard.js

Lines changed: 35 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,9 +30,9 @@ function LlmoOnboardCommand(context) {
3030
const baseCommand = BaseCommand({
3131
id: 'onboard-llmo',
3232
name: 'Onboard LLMO',
33-
description: 'Onboards a site for LLMO (Large Language Model Optimizer) through a modal interface.',
33+
description: 'Onboards a site or IMS org for LLMO (Large Language Model Optimizer) through a modal interface.',
3434
phrases: PHRASES,
35-
usageText: `${PHRASES[0]} <site url>`,
35+
usageText: `${PHRASES[0]} [site url]`,
3636
});
3737

3838
const { log } = context;
@@ -51,6 +51,39 @@ function LlmoOnboardCommand(context) {
5151

5252
const [site] = args;
5353

54+
// If no site parameter provided, trigger IMS org onboarding flow
55+
if (!site) {
56+
const message = {
57+
blocks: [
58+
{
59+
type: 'section',
60+
text: {
61+
type: 'mrkdwn',
62+
text: ':rocket: *LLMO IMS Org Onboarding*\n\nClick the button below to start the IMS organization onboarding process.',
63+
},
64+
},
65+
{
66+
type: 'actions',
67+
elements: [
68+
{
69+
type: 'button',
70+
text: {
71+
type: 'plain_text',
72+
text: 'Start Onboarding',
73+
},
74+
value: 'org_onboarding',
75+
action_id: 'start_llmo_org_onboarding',
76+
style: 'primary',
77+
},
78+
],
79+
},
80+
],
81+
thread_ts: threadTs,
82+
};
83+
await say(message);
84+
return;
85+
}
86+
5487
const normalizedSite = extractURLFromSlackInput(site);
5588

5689
if (!normalizedSite) {

0 commit comments

Comments
 (0)