Skip to content
Open
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
59 changes: 0 additions & 59 deletions package-lock.json

Large diffs are not rendered by default.

33 changes: 29 additions & 4 deletions src/controllers/llmo/llmo-onboarding.js
Original file line number Diff line number Diff line change
Expand Up @@ -170,7 +170,7 @@ async function publishToAdminHlx(filename, outputLocation, log) {
* @param {Function} say - Optional function to send messages (e.g., Slack say function)
* @returns {Promise<void>}
*/
export async function copyFilesToSharepoint(dataFolder, context, say = () => {}) {
export async function copyFilesToSharepoint(dataFolder, context, say = () => { }) {
const { log, env } = context;

const sharepointClient = await createSharePointClient(env);
Expand Down Expand Up @@ -209,7 +209,7 @@ export async function copyFilesToSharepoint(dataFolder, context, say = () => {})
* @param {Function} say - Optional function to send messages (e.g., Slack say function)
* @returns {Promise<void>}
*/
export async function updateIndexConfig(dataFolder, context, say = () => {}) {
export async function updateIndexConfig(dataFolder, context, say = () => { }) {
const { log, env } = context;

log.debug('Starting Git modification of helix query config');
Expand Down Expand Up @@ -260,7 +260,7 @@ export async function updateIndexConfig(dataFolder, context, say = () => {}) {
* @param {object} slackContext - Slack context (optional, for Slack operations)
* @returns {Promise<object>} The organization object
*/
export async function createOrFindOrganization(imsOrgId, context, say = () => {}) {
export async function createOrFindOrganization(imsOrgId, context, say = () => { }) {
const { dataAccess, log } = context;
const { Organization } = dataAccess;

Expand Down Expand Up @@ -320,7 +320,7 @@ export async function createOrFindSite(baseURL, organizationId, context) {
* @param {Function} say - Optional function to send messages (e.g., Slack say function)
* @returns {Promise<object>} The entitlement and enrollment objects
*/
export async function createEntitlementAndEnrollment(site, context, say = () => {}) {
export async function createEntitlementAndEnrollment(site, context, say = () => { }) {
const { log } = context;

try {
Expand All @@ -339,6 +339,31 @@ export async function createEntitlementAndEnrollment(site, context, say = () =>
}
}

export async function hasActiveLlmoEnrollment(site, context) {
try {
const tierClient = await TierClient.createForSite(context, site, LLMO_PRODUCT_CODE);
const { siteEnrollment } = await tierClient.checkValidEntitlement();
return !!siteEnrollment;
} catch (error) {
return false;
}
}

export async function removeEnrollment(site, context, say = () => { }) {
const { log } = context;

try {
const tierClient = await TierClient.createForSite(context, site, LLMO_PRODUCT_CODE);
await tierClient.revokeSiteEnrollment();
log.info(`Successfully revoked LLMO enrollment for site ${site.getId()}`);
await say(`✅ Successfully revoked LLMO enrollment for site ${site.getId()}`);
} catch (error) {
log.error(`Removing LLMO enrollment failed: ${error.message}`);
await say('❌ Removing LLMO enrollment failed');
throw error;
}
}

export async function enableAudits(site, context, audits = []) {
const { dataAccess } = context;
const { Configuration } = dataAccess;
Expand Down
1 change: 1 addition & 0 deletions src/controllers/slack.js
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ export function initSlackBot(lambdaContext, App) {
app.view('preflight_config_modal', actions.preflight_config_modal(lambdaContext));
app.view('onboard_llmo_modal', actions.onboardLLMOModal(lambdaContext));
app.view('update_ims_org_modal', actions.updateIMSOrgModal(lambdaContext));
app.view('confirm_remove_llmo_enrollment', actions.confirmRemoveLlmoEnrollment(lambdaContext));

return app;
}
Expand Down
4 changes: 4 additions & 0 deletions src/support/slack/actions/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ import {
addEntitlementsAction,
updateOrgAction,
updateIMSOrgModal,
removeLlmoEnrollment,
confirmRemoveLlmoEnrollment,
} from './onboard-llmo-modal.js';
import { onboardSiteModal, startOnboarding } from './onboard-modal.js';
import { preflightConfigModal } from './preflight-config-modal.js';
Expand All @@ -35,12 +37,14 @@ const actions = {
onboardSiteModal,
onboardLLMOModal,
updateIMSOrgModal,
confirmRemoveLlmoEnrollment,
start_onboarding: startOnboarding,
start_llmo_onboarding: startLLMOOnboarding,
preflight_config_modal: preflightConfigModal,
open_preflight_config: openPreflightConfig,
add_entitlements_action: addEntitlementsAction,
update_org_action: updateOrgAction,
remove_llmo_enrollment: removeLlmoEnrollment,
};

export default actions;
206 changes: 206 additions & 0 deletions src/support/slack/actions/onboard-llmo-modal.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import {
copyFilesToSharepoint,
updateIndexConfig,
enableAudits,
removeEnrollment,
} from '../../../controllers/llmo/llmo-onboarding.js';

const REFERRAL_TRAFFIC_AUDIT = 'llmo-referral-traffic';
Expand Down Expand Up @@ -791,3 +792,208 @@ export function updateIMSOrgModal(lambdaContext) {
}
};
}

export function removeLlmoEnrollment(lambdaContext) {
const { log } = lambdaContext;

return async ({ ack, body, client }) => {
try {
await ack();

const metadata = JSON.parse(body.actions[0].value);
const {
brandURL,
siteId,
existingBrand,
originalThreadTs,
} = metadata;

const originalChannel = body.channel?.id;
const { user } = body;

log.info(`User ${user.id} initiated LLMO enrollment removal for site ${siteId} (${brandURL})`);

// Update the original message to show user's action
await client.chat.update({
channel: originalChannel,
ts: body.message.ts,
text: `:warning: ${user.name} is removing LLMO enrollment for ${brandURL}...`,
blocks: [
{
type: 'section',
text: {
type: 'mrkdwn',
text: `:warning: ${user.name} is removing LLMO enrollment for ${brandURL}...`,
},
},
],
});

// Show confirmation modal
await client.views.open({
trigger_id: body.trigger_id,
view: {
type: 'modal',
callback_id: 'confirm_remove_llmo_enrollment',
private_metadata: JSON.stringify({
brandURL,
siteId,
existingBrand,
originalChannel,
originalThreadTs,
originalMessageTs: body.message.ts,
}),
title: {
type: 'plain_text',
text: 'Confirm Removal',
},
submit: {
type: 'plain_text',
text: 'Remove Enrollment',
},
close: {
type: 'plain_text',
text: 'Cancel',
},
blocks: [
{
type: 'section',
text: {
type: 'mrkdwn',
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.*`,
},
},
],
},
});
} catch (error) {
log.error('Error handling remove LLMO enrollment action:', error);
const metadata = JSON.parse(body.actions[0].value);
await client.chat.postMessage({
channel: body.channel?.id,
text: `:x: Failed to initiate enrollment removal: ${error.message}`,
thread_ts: metadata.originalThreadTs,
});
}
};
}

export function confirmRemoveLlmoEnrollment(lambdaContext) {
const { log, dataAccess } = lambdaContext;

return async ({ ack, body, client }) => {
try {
log.debug('Processing LLMO enrollment removal confirmation...');

const { view, user } = body;
const metadata = JSON.parse(view.private_metadata);
const {
brandURL,
siteId,
existingBrand,
originalChannel,
originalThreadTs,
originalMessageTs,
} = metadata;

// Acknowledge the modal submission
await ack();

// Post initial message to the thread
const responseChannel = originalChannel || body.user.id;
const responseThreadTs = originalChannel ? originalThreadTs : undefined;

await client.chat.postMessage({
channel: responseChannel,
text: `:gear: Removing LLMO enrollment for ${brandURL}...`,
thread_ts: responseThreadTs,
});

try {
// Find the site
const { Site } = dataAccess;
const site = await Site.findById(siteId);

if (!site) {
throw new Error(`Site not found: ${siteId}`);
}

// Use the reusable removeEnrollment function from the LLMO controller
await removeEnrollment(site, lambdaContext);

log.info(`Successfully revoked LLMO enrollment for site ${siteId} (${brandURL})`);

// Update the original message to show completion
if (originalMessageTs) {
await client.chat.update({
channel: responseChannel,
ts: originalMessageTs,
text: `:white_check_mark: LLMO enrollment removed for ${brandURL}`,
blocks: [
{
type: 'section',
text: {
type: 'mrkdwn',
text: `:white_check_mark: *LLMO Enrollment Removed*\n\nThe LLMO enrollment for *${brandURL}* (brand: *${existingBrand}*) has been successfully removed by ${user.name}.`,
},
},
],
});
}

// Post success message to the thread
const successMessage = `:white_check_mark: *LLMO enrollment removed successfully!*

:link: *Site:* ${brandURL}
:identification_card: *Site ID:* ${siteId}
:label: *Brand:* ${existingBrand}
:bust_in_silhouette: *Removed by:* ${user.name}

The site enrollment has been revoked. The site can be re-onboarded at any time using the \`onboard-llmo\` command.`;

await client.chat.postMessage({
channel: responseChannel,
text: successMessage,
thread_ts: responseThreadTs,
});
} catch (error) {
log.error(`Error removing LLMO enrollment for site ${siteId}:`, error);

// Update the original message to show error
if (originalMessageTs) {
await client.chat.update({
channel: responseChannel,
ts: originalMessageTs,
text: `:x: Failed to remove LLMO enrollment for ${brandURL}`,
blocks: [
{
type: 'section',
text: {
type: 'mrkdwn',
text: `:x: *Failed to remove LLMO enrollment*\n\nThere was an error removing the enrollment for *${brandURL}*.`,
},
},
],
});
}

// Post error message to the thread
await client.chat.postMessage({
channel: responseChannel,
text: `:x: Failed to remove LLMO enrollment: ${error.message}`,
thread_ts: responseThreadTs,
});
}

log.debug(`LLMO enrollment removal processed for user ${user.id}, site ${brandURL}`);
} catch (error) {
log.error('Error handling confirm remove LLMO enrollment modal:', error);
await ack({
response_action: 'errors',
errors: {
general: 'There was an error processing the removal request.',
},
});
}
};
}
Loading