Skip to content

Commit 51e7d91

Browse files
committed
feat(slack/onboard-llmo): add option to remove site enrollment
1 parent 6d2012b commit 51e7d91

File tree

7 files changed

+847
-62
lines changed

7 files changed

+847
-62
lines changed

package-lock.json

Lines changed: 0 additions & 59 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/controllers/slack.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,7 @@ export function initSlackBot(lambdaContext, App) {
8181
app.view('preflight_config_modal', actions.preflight_config_modal(lambdaContext));
8282
app.view('onboard_llmo_modal', actions.onboardLLMOModal(lambdaContext));
8383
app.view('update_ims_org_modal', actions.updateIMSOrgModal(lambdaContext));
84+
app.view('confirm_remove_llmo_enrollment', actions.confirmRemoveLlmoEnrollment(lambdaContext));
8485

8586
return app;
8687
}

src/support/slack/actions/index.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ import {
2525
import { onboardSiteModal, startOnboarding } from './onboard-modal.js';
2626
import { preflightConfigModal } from './preflight-config-modal.js';
2727
import openPreflightConfig from './open-preflight-config.js';
28+
import removeLlmoEnrollment, { confirmRemoveLlmoEnrollment } from './remove-llmo-enrollment.js';
2829

2930
const actions = {
3031
approveFriendsFamily,
@@ -35,12 +36,14 @@ const actions = {
3536
onboardSiteModal,
3637
onboardLLMOModal,
3738
updateIMSOrgModal,
39+
confirmRemoveLlmoEnrollment,
3840
start_onboarding: startOnboarding,
3941
start_llmo_onboarding: startLLMOOnboarding,
4042
preflight_config_modal: preflightConfigModal,
4143
open_preflight_config: openPreflightConfig,
4244
add_entitlements_action: addEntitlementsAction,
4345
update_org_action: updateOrgAction,
46+
remove_llmo_enrollment: removeLlmoEnrollment,
4447
};
4548

4649
export default actions;
Lines changed: 235 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,235 @@
1+
/*
2+
* Copyright 2025 Adobe. All rights reserved.
3+
* This file is licensed to you under the Apache License, Version 2.0 (the "License");
4+
* you may not use this file except in compliance with the License. You may obtain a copy
5+
* of the License at http://www.apache.org/licenses/LICENSE-2.0
6+
*
7+
* Unless required by applicable law or agreed to in writing, software distributed under
8+
* the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
9+
* OF ANY KIND, either express or implied. See the License for the specific language
10+
* governing permissions and limitations under the License.
11+
*/
12+
13+
import { Entitlement as EntitlementModel } from '@adobe/spacecat-shared-data-access/src/models/entitlement/index.js';
14+
import TierClient from '@adobe/spacecat-shared-tier-client';
15+
16+
const LLMO_PRODUCT_CODE = EntitlementModel.PRODUCT_CODES.LLMO;
17+
18+
/**
19+
* Handles the removal of LLMO enrollment for a site.
20+
* Shows a confirmation dialog before proceeding with the removal.
21+
*
22+
* @param {object} lambdaContext - The lambda context object.
23+
* @returns {Function} The action handler function.
24+
*/
25+
export default function removeLlmoEnrollment(lambdaContext) {
26+
const { log } = lambdaContext;
27+
28+
return async ({ ack, body, client }) => {
29+
try {
30+
await ack();
31+
32+
const metadata = JSON.parse(body.actions[0].value);
33+
const {
34+
brandURL,
35+
siteId,
36+
existingBrand,
37+
originalThreadTs,
38+
} = metadata;
39+
40+
const originalChannel = body.channel?.id;
41+
const { user } = body;
42+
43+
log.info(`User ${user.id} initiated LLMO enrollment removal for site ${siteId} (${brandURL})`);
44+
45+
// Update the original message to show user's action
46+
await client.chat.update({
47+
channel: originalChannel,
48+
ts: body.message.ts,
49+
text: `:warning: ${user.name} is removing LLMO enrollment for ${brandURL}...`,
50+
blocks: [
51+
{
52+
type: 'section',
53+
text: {
54+
type: 'mrkdwn',
55+
text: `:warning: ${user.name} is removing LLMO enrollment for ${brandURL}...`,
56+
},
57+
},
58+
],
59+
});
60+
61+
// Show confirmation modal
62+
await client.views.open({
63+
trigger_id: body.trigger_id,
64+
view: {
65+
type: 'modal',
66+
callback_id: 'confirm_remove_llmo_enrollment',
67+
private_metadata: JSON.stringify({
68+
brandURL,
69+
siteId,
70+
existingBrand,
71+
originalChannel,
72+
originalThreadTs,
73+
originalMessageTs: body.message.ts,
74+
}),
75+
title: {
76+
type: 'plain_text',
77+
text: 'Confirm Removal',
78+
},
79+
submit: {
80+
type: 'plain_text',
81+
text: 'Remove Enrollment',
82+
},
83+
close: {
84+
type: 'plain_text',
85+
text: 'Cancel',
86+
},
87+
blocks: [
88+
{
89+
type: 'section',
90+
text: {
91+
type: 'mrkdwn',
92+
text: `:warning: *Are you sure you want to remove LLMO enrollment?*\n\n*Site:* ${brandURL}\n*Brand:* ${existingBrand}\n\nThis action will:\n• Revoke the site's LLMO enrollment\n• Remove access to LLMO features for this site\n\n*This action cannot be undone.*`,
93+
},
94+
},
95+
],
96+
},
97+
});
98+
} catch (error) {
99+
log.error('Error handling remove LLMO enrollment action:', error);
100+
const metadata = JSON.parse(body.actions[0].value);
101+
await client.chat.postMessage({
102+
channel: body.channel?.id,
103+
text: `:x: Failed to initiate enrollment removal: ${error.message}`,
104+
thread_ts: metadata.originalThreadTs,
105+
});
106+
}
107+
};
108+
}
109+
110+
/**
111+
* Handles the confirmation modal submission for removing LLMO enrollment.
112+
*
113+
* @param {object} lambdaContext - The lambda context object.
114+
* @returns {Function} The modal submission handler function.
115+
*/
116+
export function confirmRemoveLlmoEnrollment(lambdaContext) {
117+
const { log, dataAccess } = lambdaContext;
118+
119+
return async ({ ack, body, client }) => {
120+
try {
121+
log.debug('Processing LLMO enrollment removal confirmation...');
122+
123+
const { view, user } = body;
124+
const metadata = JSON.parse(view.private_metadata);
125+
const {
126+
brandURL,
127+
siteId,
128+
existingBrand,
129+
originalChannel,
130+
originalThreadTs,
131+
originalMessageTs,
132+
} = metadata;
133+
134+
// Acknowledge the modal submission
135+
await ack();
136+
137+
// Post initial message to the thread
138+
const responseChannel = originalChannel || body.user.id;
139+
const responseThreadTs = originalChannel ? originalThreadTs : undefined;
140+
141+
await client.chat.postMessage({
142+
channel: responseChannel,
143+
text: `:gear: Removing LLMO enrollment for ${brandURL}...`,
144+
thread_ts: responseThreadTs,
145+
});
146+
147+
try {
148+
// Find the site
149+
const { Site } = dataAccess;
150+
const site = await Site.findById(siteId);
151+
152+
if (!site) {
153+
throw new Error(`Site not found: ${siteId}`);
154+
}
155+
156+
// Create TierClient and revoke the site enrollment
157+
const tierClient = await TierClient.createForSite(lambdaContext, site, LLMO_PRODUCT_CODE);
158+
await tierClient.revokeSiteEnrollment();
159+
160+
log.info(`Successfully revoked LLMO enrollment for site ${siteId} (${brandURL})`);
161+
162+
// Update the original message to show completion
163+
if (originalMessageTs) {
164+
await client.chat.update({
165+
channel: responseChannel,
166+
ts: originalMessageTs,
167+
text: `:white_check_mark: LLMO enrollment removed for ${brandURL}`,
168+
blocks: [
169+
{
170+
type: 'section',
171+
text: {
172+
type: 'mrkdwn',
173+
text: `:white_check_mark: *LLMO Enrollment Removed*\n\nThe LLMO enrollment for *${brandURL}* (brand: *${existingBrand}*) has been successfully removed by ${user.name}.`,
174+
},
175+
},
176+
],
177+
});
178+
}
179+
180+
// Post success message to the thread
181+
const successMessage = `:white_check_mark: *LLMO enrollment removed successfully!*
182+
183+
:link: *Site:* ${brandURL}
184+
:identification_card: *Site ID:* ${siteId}
185+
:label: *Brand:* ${existingBrand}
186+
:bust_in_silhouette: *Removed by:* ${user.name}
187+
188+
The site enrollment has been revoked. The site can be re-onboarded at any time using the \`onboard-llmo\` command.`;
189+
190+
await client.chat.postMessage({
191+
channel: responseChannel,
192+
text: successMessage,
193+
thread_ts: responseThreadTs,
194+
});
195+
} catch (error) {
196+
log.error(`Error removing LLMO enrollment for site ${siteId}:`, error);
197+
198+
// Update the original message to show error
199+
if (originalMessageTs) {
200+
await client.chat.update({
201+
channel: responseChannel,
202+
ts: originalMessageTs,
203+
text: `:x: Failed to remove LLMO enrollment for ${brandURL}`,
204+
blocks: [
205+
{
206+
type: 'section',
207+
text: {
208+
type: 'mrkdwn',
209+
text: `:x: *Failed to remove LLMO enrollment*\n\nThere was an error removing the enrollment for *${brandURL}*.`,
210+
},
211+
},
212+
],
213+
});
214+
}
215+
216+
// Post error message to the thread
217+
await client.chat.postMessage({
218+
channel: responseChannel,
219+
text: `:x: Failed to remove LLMO enrollment: ${error.message}`,
220+
thread_ts: responseThreadTs,
221+
});
222+
}
223+
224+
log.debug(`LLMO enrollment removal processed for user ${user.id}, site ${brandURL}`);
225+
} catch (error) {
226+
log.error('Error handling confirm remove LLMO enrollment modal:', error);
227+
await ack({
228+
response_action: 'errors',
229+
errors: {
230+
general: 'There was an error processing the removal request.',
231+
},
232+
});
233+
}
234+
};
235+
}

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

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ 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 for LLMO (Large Language Model Optimizer) through a modal interface. For already-onboarded sites, provides options to add entitlements, update IMS org, or remove enrollment.',
3434
phrases: PHRASES,
3535
usageText: `${PHRASES[0]} <site url>`,
3636
});
@@ -112,6 +112,22 @@ function LlmoOnboardCommand(context) {
112112
}),
113113
action_id: 'update_org_action',
114114
},
115+
{
116+
type: 'button',
117+
text: {
118+
type: 'plain_text',
119+
text: 'Remove Enrollment',
120+
},
121+
value: JSON.stringify({
122+
brandURL: normalizedSite,
123+
siteId: existingSite.getId(),
124+
existingBrand: brand,
125+
originalChannel: 'current',
126+
originalThreadTs: threadTs,
127+
}),
128+
action_id: 'remove_llmo_enrollment',
129+
style: 'danger',
130+
},
115131
],
116132
},
117133
],

0 commit comments

Comments
 (0)