Skip to content

Instantly share code, notes, and snippets.

@ipcrm
Last active March 4, 2019 22:56
Show Gist options
  • Save ipcrm/69ad4a40b280afc0d380339157f96552 to your computer and use it in GitHub Desktop.
Save ipcrm/69ad4a40b280afc0d380339157f96552 to your computer and use it in GitHub Desktop.
Custom list Skills
/*
* Copyright © 2019 Atomist, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import {
Configuration,
configurationValue,
HandlerResult,
HttpMethod,
logger,
NoParameters,
} from "@atomist/automation-client";
import {
CommandHandlerRegistration,
CommandListenerInvocation,
slackErrorMessage,
} from "@atomist/sdm";
import {Attachment, Field} from "@atomist/slack-messages";
import * as slack from "@atomist/slack-messages";
export interface Ingester {
root_type: string;
url: string;
}
export interface Command {
name: string;
description: string;
intent: string[];
}
export interface Registration {
name: string;
commands: Command[];
ingesters: Ingester[];
groups?: string[];
version: string;
team_ids: string[];
}
export interface RegObject {
registration: Registration;
}
export interface ListSkill {
sdm: string;
intent: string;
description: string;
}
export interface SdmSkills {
name: string;
version: string;
skills: ListSkill[];
}
export interface ListSkillsConfig {
explicitSdmAllow?: string[]; // If populated, only show intents from these SDMs
filterIntents?: string[]; // If populated, for each intent, only display if it includes one of the strings supplied
excludeIntentsFilter?: string[]; // If populated, for each intent, only display intent if not excluded
}
/**
* Based on the supplied config, filter intents and return updated SdmSkills
* @param {SdmSkills[]} skills
* @param {ListSkillsConfig} config
* @returns {SdmSkills[]} Array of SdmSkills objects
*/
export function applyIntentFilter(
skills: SdmSkills[], config: ListSkillsConfig, exclude: boolean = false): SdmSkills[] {
const newSkills = skills.map(s => {
const tmpSkills = s.skills.filter(i => {
if (exclude) {
return !(config.excludeIntentsFilter.filter(fi => i.intent.includes(fi)).length > 0);
} else {
return config.filterIntents.filter(fi => i.intent.includes(fi)).length > 0;
}
});
return {
name: s.name,
version: s.version,
skills: tmpSkills,
};
});
return newSkills.filter(nS => nS.skills.length > 0);
}
/**
* Take a SdmSkills object and filter it based on the configuration supplied ListSkillsConfig
* @param {SdmSkills[]} skills
* @returns {SdmSkills[]} Filtered SDM Skills
*/
export function filterSkills(skills: SdmSkills[]): SdmSkills[] {
const config = configurationValue<ListSkillsConfig>("sdm.listSkills", {});
let returnSkills: SdmSkills[] = skills;
// Apply SDM Filter
if (config.explicitSdmAllow && config.explicitSdmAllow.length > 0) {
const lowerExplicitSdmAllow = config.explicitSdmAllow.map(e => e.toLowerCase());
returnSkills = returnSkills.filter(s => lowerExplicitSdmAllow.includes(s.name.toLowerCase()));
}
// Apply intent filters
if (config.filterIntents && config.filterIntents.length > 0) {
returnSkills = applyIntentFilter(returnSkills, config);
}
if (config.excludeIntentsFilter && config.excludeIntentsFilter.length > 0) {
returnSkills = applyIntentFilter(returnSkills, config, true);
}
return returnSkills;
}
/**
* Listener for displaying the list of skills known to Atomist, filtered based on your configuration
* @param {CommandListenerInvocation<NoParameters>} cli
* @returns {Promise<HandlerResult>} Handler Result
*/
export async function listSkillsListener(cli: CommandListenerInvocation<NoParameters>): Promise<HandlerResult> {
return new Promise<HandlerResult>(async (resolve, reject) => {
interface TeamInfo {
id: string;
}
interface ChatTeamInfo {
team: TeamInfo;
provider: string;
}
interface TeamInfo {
ChatTeam: ChatTeamInfo[];
}
try {
const teamInfo = await cli.context.graphClient.query<TeamInfo, {}>(
{query: `{ChatTeam{team {id} provider}}`});
let inProgressMsg: any;
if (teamInfo.ChatTeam[0].provider === "slack") {
inProgressMsg = slack.emoji("hourglass") + "Searching...";
} else if (teamInfo.ChatTeam[0].provider === "msteams") {
inProgressMsg = "**Searching...**";
}
// In-progress message
await cli.addressChannels({
attachments: [
{
pretext: `Here are the skills known to *Atomist*:`,
text: inProgressMsg,
fallback: `Here are the skills known to *Atomist*:`,
},
],
}, {
id: `list/skills/${cli.configuration.name}`,
ttl: 60 * 120,
});
const allSkills = await buildSkillsList(cli.configuration, teamInfo.ChatTeam[0].team.id);
const skills = filterSkills(allSkills);
if (teamInfo.ChatTeam[0].provider === "slack") {
const msgBody: any = [];
skills.forEach(s => {
msgBody.push(`\n*${s.name}:${s.version}*`);
s.skills.forEach(i => {
msgBody.push(`${slack.codeLine(i.intent)} ${i.description}`);
});
});
await cli.addressChannels({
attachments: [
{
pretext: `Here are the skills known to *Atomist*:`,
text: msgBody.join("\n"),
fallback: `Here are the skills known to *Atomist*:`,
},
],
}, {
id: `list/skills/${cli.configuration.name}`,
ttl: 60 * 120,
});
}
if (teamInfo.ChatTeam[0].provider === "msteams") {
const attachments: Attachment[] = [];
skills.forEach(s => {
const fields: Field[] = [];
s.skills.forEach(i => {
fields.push({
value: `${i.description}`,
title: `${i.intent}`,
});
});
attachments.push({
fallback: `${s.name}:${s.version}`,
pretext: `**SDM: ${s.name}:${s.version}**`,
fields,
});
});
await cli.addressChannels({
attachments,
}, {
id: `list/skills/${cli.configuration.name}`,
ttl: 60 * 120,
});
}
resolve({
code: 0,
});
} catch (e) {
await cli.addressChannels(slackErrorMessage(
`Failed to run list skills`,
`${e}`,
cli.context),
{ id: `list/skills/${cli.configuration.name}`, ttl: 60 * 60},
);
reject({
code: 1,
message: e,
});
}
});
}
/** Build Skill List
* @param {string} Atomist TeamID (Workspace ID)
* @param {Configuration} config
* @return {ListSkills} Skills to be printed
*/
export function buildSkillsList(config: Configuration, teamId: string): Promise<SdmSkills[]> {
const skills: SdmSkills[] = [];
return new Promise<SdmSkills[]>(async (resolve, reject) => {
try {
const regInfo = await getRegistrationInfo(config);
regInfo.forEach(r => {
logger.info(`buildSkillsList: Processing ${r.registration.name}`);
if (!r.registration.hasOwnProperty("commands")) {
return;
}
if (
!(r.registration.hasOwnProperty("team_ids") && r.registration.team_ids.includes(teamId)) &&
!(r.registration.hasOwnProperty("groups") && r.registration.groups.includes("all"))
) {
return;
}
const mySkills: ListSkill[] = [];
r.registration.commands.forEach(c => {
if (c.hasOwnProperty("intent") && c.intent && c.intent.length > 0 ) {
c.intent.forEach(i => {
mySkills.push({
sdm: r.registration.name,
intent: i,
description: c.description,
});
});
}
});
if (mySkills.length > 0) {
skills.push({name: r.registration.name, version: r.registration.version, skills: mySkills});
}
});
} catch (e) {
logger.error(`buildSkillsList: Failed to lookup skill list! Error => ${e}`);
reject(e);
}
resolve(skills);
});
}
/**
* Gets registration info for this API key
* @param {Configuration} config
* @return {JSON} registration info
*/
export const getRegistrationInfo = async (config: Configuration): Promise<RegObject[]> => {
logger.debug(`Starting getRegistrationInfo, using URL ${config.endpoints.api}`);
const httpClient = config.http.client.factory.create(config.endpoints.api);
const authorization = `Bearer ${config.apiKey}`;
try {
const result = await httpClient.exchange(config.endpoints.api, {
method: HttpMethod.Get,
headers: { Authorization: authorization },
});
return(result.body as RegObject[]);
} catch (e) {
logger.error("getRegistrationInfo: Error! Failed to retrieve data. Failure: " +
`[Status ${JSON.stringify(e.response.status)}] => ` +
JSON.stringify(e.response.data));
throw new Error(e);
}
};
/**
* Command Handler Registration for customized List Skills intent
*/
export const listSkills: CommandHandlerRegistration<NoParameters> = {
name: "ListSkills",
intent: [
"list skills",
"show skills",
"list skill",
"show skill",
"ls",
],
listener: listSkillsListener,
};
@ipcrm
Copy link
Author

ipcrm commented Feb 23, 2019

Configuration

Within your client.config.json, add to SDM:

"listSkills": {
   <configuration>
},

There are three options (all arrays) - explicitSdmAllow. filterIntents, and excludeIntentsFilter.


explicitSdmAllow: This array supplies a list of SDMs you want to display intents for. Simply add the name.

Note: Make sure you use the full name. If your SDM uses a scoped package name, include it. Example: @Atomist/demo-sdm

filterIntents: This array allows you to supply strings that should filter the displayed intents. Example; if I include a string of add only intents that include the word add will be displayed. Can be used independently or combined with other options.

excludeIntentsFilter: This array allows you to supply strings that should exclude the displayed intents. This will display all intents except the ones matching a string in this array. Can be used independently or combined with other options.

Example: Only show intents from the ipcrmdemo-sdm and @Atomist/lifecycle-automation SDMs. Of those intents, only display intents that include the word add. Of those resulting intents, do not display intents that include the string Maven.

  "sdm": {
    "listSkills": {
      "explicitSdmAllow": [
        "ipcrmdemo-sdm",
        "@Atomist/lifecycle-automation"
      ],
      "filterIntents": [
        "add"
      ],
      "excludeIntentsFilter": [
        "Maven"
      ]
    },
    ...
  }

NOTE: This replaces intents list skills, list skill, show skills, show skill and adds ls

To add to your SDM; simply register the command handler:

export function machine(
    configuration: SoftwareDeliveryMachineConfiguration,
): SoftwareDeliveryMachine {

    const sdm: SoftwareDeliveryMachine = createSoftwareDeliveryMachine(
        { name: "My SDM", configuration },
    );

    sdm.addCommand(listSkills)
    return sdm;
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment