diff --git a/handbook/company/ceo.md b/handbook/company/ceo.md index 00315beca2..7c14636b91 100644 --- a/handbook/company/ceo.md +++ b/handbook/company/ceo.md @@ -271,20 +271,6 @@ Marking as _"private"_ in Gong doesn't limit access, instead, use this process: - If appropriate, make this doc "ΒΆΒΆ," plus fleeties who were on the call. - Include in the doc "link to video:" - Include in the doc "link to transcript:" - - -## Rituals -(Who will...) - - - -## Slack channels - -| Channel | Purpose | Standard Operating Procedure | DRI | -| ------- | ------- | ---------------------------- | --- | -| [g-ceo](https://fleetdm.slack.com/archives/C05K87USYUV) | Open line of communication for Fleeties to make requests of the 🦿 [CEO support team](https://fleetdm.com/handbook/company/ceo#team) | Reach out to the AttC with requests or to add an item on the πŸˆβ€β¬›πŸ—£οΈ [CEO office minutes](https://docs.google.com/document/d/12cd0N8KvHkfJxYlo7ggdisrvqw4MCErDoIzLjmBIdj4/edit) | [@sampfluger]([https://github.com/sampfluger88](https://fleetdm.slack.com/archives/D05DW7BT2GY)) | -| [help-being-ceo](https://fleetdm.slack.com/archives/C03U703J0G5) | Private [hallway](https://about.gitlab.com/handbook/leadership/1-1/suggested-agenda-format/#hallway) for the CEO to drop tasks and personal scheduling information | - Mark items with πŸ‘€ (emoji reaction) to indicate that you are working on the task. - Mark requests with βœ… (emoji reaction) to indicate that you completed or filed away the task | [@sampfluger]([https://github.com/sampfluger88](https://fleetdm.slack.com/archives/D05DW7BT2GY)) | - ### Why not mention the CEO in Slack threads? Everyone else who works at Fleet is expected to read and reply (or acknowledge with an emoji reaction) every time they're mentioned in Slack, evenΒ deep inside long threads. @@ -300,8 +286,7 @@ I held on as long as I could. But due to volume, in late 2022, I made the decis - If you mention me in a top-level channel message, I'll see and read it in 1 business day. - If you send me a direct message, I'll see and read that ASAP. Keep in mind I am often in meetings all day, and may not be able to reply promptly. -When in doubt, you can look at my calendar and join whatever meeting I'm in. If none of that works, and there is an emergency where you need my immediate attention, get help from Zach Wasserman. -Thank you so much!" πŸ™‡ +Thank you so much!" πŸ™‡ ## Kanban @@ -312,6 +297,19 @@ Any Fleet team member can view the [πŸˆβ€β¬›#g-ceo kanban board](https://app.z ## Intake To make a request of the business operations department, [create an issue using one of our issue templates](https://github.com/fleetdm/confidential/issues/new/choose). If you don't see what you need, or you are unsure, [create a custom request issue](https://github.com/fleetdm/confidential/issues/new/choose) and someone in business operations will reply within 1 business day. +## Rituals +(Who will...) + + + +## Slack channels + +| Channel | Purpose | Standard Operating Procedure | DRI | +| ------- | ------- | ---------------------------- | --- | +| [g-ceo](https://fleetdm.slack.com/archives/C05K87USYUV) +| Open line of communication for Fleeties to make requests of the 🦿 [CEO support team](https://fleetdm.com/handbook/company/ceo#team) +| Reach out to the AttC with requests or to add an item on the | AttC | +| [help-being-ceo](https://fleetdm.slack.com/archives/C03U703J0G5) | Private [hallway](https://about.gitlab.com/handbook/leadership/1-1/suggested-agenda-format/#hallway) for the CEO to drop tasks and personal scheduling information | - Mark items with πŸ‘€ (emoji reaction) to indicate that you are working on the task. - Mark requests with βœ… (emoji reaction) to indicate that you completed or filed away the task | AttC | diff --git a/handbook/company/rituals.yml b/handbook/company/rituals.yml index df985e757a..0d95218b84 100644 --- a/handbook/company/rituals.yml +++ b/handbook/company/rituals.yml @@ -1,52 +1,64 @@ +# https://github.com/fleetdm/fleet/pull/13084 + + + - - task: "Prioritize for next sprint" - startedOn: "2023-08-09" - frequency: "Triweekly" + task: "Prioritize for next sprint" # Title that will actually show in rituals table + startedOn: "2023-08-09" # Needs to align with frequency e.g. if frequency is every thrid Thursday startedOn === any third thursday + frequency: "Triweekly" # must be supported by description: "Drag next sprint's priorities to the top of the 'Not yet' column." # example of a longer thing: description: "[Prioritizing next sprint](https://fleetdm.com/handbook/company/communication)" - dri: "sampfluger88" - autoIssue: - labels: [ "#g-ceo" ] + moreInfoUrl: "https://fleetdm.com/handbook/company/why-this-way#why-make-work-visible" #URL used to highlight "description:" test in table + dri: "sampfluger88" # DRI for ritual (assignee if autoIssue) (TODO display GitHub proflie pic instead of name or title) + autoIssue: # Enables automation of GitHub issues + labels: [ "#g-ceo" ] # label to be applied to issue - task: "Process the CEO's inbox" startedOn: "2023-07-29" frequency: "Daily, 4 times" description: "TODO" + moreInfoUrl: "https://fleetdm.com/handbook/company/ceo#process-the-ceos-email" dri: "sampfluger88" - task: "Process the CEO's calendar" startedOn: "2023-07-29" frequency: "Daily, 4 times" - description: "TODO" + description: "TODO" + moreInfoUrl: "https://fleetdm.com/handbook/company/ceo#process-the-ceos-calendar" dri: "sampfluger88" - task: "Fleet IT warehouse management" startedOn: "2023-07-29" frequency: "Weekly" description: "TODO" + moreInfoUrl: "https://fleetdm.com/handbook/company/ceo#fleet-it-warehouse-management" dri: "sampfluger88" - task: "Send weekly update" startedOn: "2023-07-28" frequency: "Weekly" description: "TODO" + moreInfoUrl: "https://fleetdm.com/handbook/company/ceo#send-the-weekly-update" dri: "sampfluger88" - task: "Wipe e-group agenda" startedOn: "2023-07-26" frequency: "After each weekly e-group meeting" description: "TODO" + moreInfoUrl: "https://fleetdm.com/handbook/company/ceo#post-e-group-archival" dri: "sampfluger88" - task: "Share recording of all hands meeting" startedOn: "2023-07-xx" frequency: "After each monthly all hands meeting" - description: "[Sharing the all hands recording](https://fleetdm.com/handbook/company/ceo#after-the-all-hands)" + description: "Sharing the all hands recording" + moreInfoUrl: "https://fleetdm.com/handbook/company/ceo#after-the-all-hands" dri: "sampfluger88" - task: "Prepare all hands deck" startedOn: "2023-07-xx" frequency: "The night (CT) before each monthly all hands meeting" - description: "[Preparing the all hands deck](https://fleetdm.com/handbook/company/ceo#prep-for-the-all-hands-call)" + description: "Preparing the all hands deck" + moreInfoUrl: "https://fleetdm.com/handbook/company/ceo#preparing-for-the-all-hands" dri: "sampfluger88" - task: "Prepare board deck" diff --git a/website/api/controllers/handbook/view-basic-handbook.js b/website/api/controllers/handbook/view-basic-handbook.js index a790b8141a..9ac67e8425 100644 --- a/website/api/controllers/handbook/view-basic-handbook.js +++ b/website/api/controllers/handbook/view-basic-handbook.js @@ -55,6 +55,7 @@ module.exports = { } } + // Respond with view. return { path: require('path'), @@ -69,6 +70,7 @@ module.exports = { thisPage.meta.description ? thisPage.meta.description // Β« custom meta description for this page, if provided in markdown : 'View the Fleet handbook.'// Β« otherwise use a generic description ), + rituals: sails.config.builtStaticContent.rituals, algoliaPublicKey: sails.config.custom.algoliaPublicKey, }; diff --git a/website/assets/js/components/rituals.component.js b/website/assets/js/components/rituals.component.js new file mode 100644 index 0000000000..f3c6ba0aa8 --- /dev/null +++ b/website/assets/js/components/rituals.component.js @@ -0,0 +1,78 @@ +/** + * + * ----------------------------------------------------------------------------- + * + * + * @type {Component} + * + * + * ----------------------------------------------------------------------------- + */ + +parasails.registerComponent('rituals', { + // ╔═╗╦═╗╔═╗╔═╗╔═╗ + // ╠═╝╠╦╝║ β•‘β• β•β•β•šβ•β•— + // β•© β•©β•šβ•β•šβ•β•β•© β•šβ•β• + props: [ + 'rituals', + ], + + // ╦╔╗╔╦╔╦╗╦╔═╗╦ ╔═╗╔╦╗╔═╗╔╦╗╔═╗ + // β•‘β•‘β•‘β•‘β•‘ β•‘ ║╠═╣║ β•šβ•β•— β•‘ ╠═╣ β•‘ β•‘β•£ + // β•©β•β•šβ•β•© β•© β•©β•© ╩╩═╝ β•šβ•β• β•© β•© β•© β•© β•šβ•β• + data: function (){ + return { + //… + }; + }, + + // ╦ ╦╔╦╗╔╦╗╦ + // ╠═╣ β•‘ β•‘β•‘β•‘β•‘ + // β•© β•© β•© β•© ╩╩═╝ + template: ` +
+ + + + + + + + + + + + + + + + + + + + +
Task nameStarted onFrequencyDescriptionDRI
{{ritual.task}}{{ritual.startedOn}}{{ritual.frequency}}{{ritual.description}}{{ritual.description}}{{ritual.dri}}
+
+ `, + + // ╦ ╦╔═╗╔═╗╔═╗╦ ╦╔═╗╦ ╔═╗ + // β•‘ β•‘β• β•£ β•‘β•£ β•‘ β•šβ•¦β•β•‘ β•‘ β•‘β•£ + // β•©β•β•β•©β•š β•šβ•β•β•šβ•β• β•© β•šβ•β•β•©β•β•β•šβ•β• + beforeMount: function() { + //… + }, + mounted: async function(){ + //… + }, + beforeDestroy: function() { + //… + }, + + // ╦╔╗╔╔╦╗╔═╗╦═╗╔═╗╔═╗╔╦╗╦╔═╗╔╗╔╔═╗ + // β•‘β•‘β•‘β•‘ β•‘ β•‘β•£ ╠╦╝╠═╣║ β•‘ β•‘β•‘ β•‘β•‘β•‘β•‘β•šβ•β•— + // β•©β•β•šβ• β•© β•šβ•β•β•©β•šβ•β•© β•©β•šβ•β• β•© β•©β•šβ•β•β•β•šβ•β•šβ•β• + methods: { + + + } +}); diff --git a/website/scripts/build-static-content.js b/website/scripts/build-static-content.js index 4e6f2adfe9..4927ec5f10 100644 --- a/website/scripts/build-static-content.js +++ b/website/scripts/build-static-content.js @@ -25,6 +25,19 @@ module.exports = { // The data we're compiling will get built into this dictionary and then written on top of the .sailsrc file. let builtStaticContent = {}; + let baseHeadersForGithubRequests; + if(!skipGithubRequests){ + baseHeadersForGithubRequests = { + 'User-Agent': 'Fleet-Standard-Query-Library', + 'Accept': 'application/vnd.github.v3+json', + }; + + if(githubAccessToken) { + // If a GitHub access token was provided, add it to the baseHeadersForGithubRequests object. + baseHeadersForGithubRequests['Authorization'] = `token ${githubAccessToken}`; + } + } + await sails.helpers.flow.simultaneously([ async()=>{// Parse query library from YAML and prepare to bake them into the Sails app's configuration. let RELATIVE_PATH_TO_QUERY_LIBRARY_YML_IN_FLEET_REPO = 'docs/01-Using-Fleet/standard-query-library/standard-query-library.yml'; @@ -121,15 +134,6 @@ module.exports = { } } else {// If the --skipGithubRequests flag was not provided, we'll query GitHub's API to get additional information about each contributor. - let baseHeadersForGithubRequests = { - 'User-Agent': 'Fleet-Standard-Query-Library', - 'Accept': 'application/vnd.github.v3+json', - }; - - if(githubAccessToken) { - // If a GitHub access token was provided, add it to the baseHeadersForGithubRequests object. - baseHeadersForGithubRequests['Authorization'] = `token ${githubAccessToken}`; - } await sails.helpers.flow.simultaneouslyForEach(githubUsernames, async(username)=>{ githubDataByUsername[username] = await sails.helpers.http.get.with({ url: 'https://api.github.com/users/' + encodeURIComponent(username), @@ -720,6 +724,112 @@ module.exports = { } builtStaticContent.pricingTable = pricingTableCategories; }, + async()=>{ + let rituals = {}; + // Find all the files in the top level /handbook folder and it's sub-folders + let FILES_IN_HANDBOOK_FOLDER = await sails.helpers.fs.ls.with({ + dir: path.join(topLvlRepoPath, '/handbook'), + depth: 3 + }); + // Filter the list of filenames to get the rituals YAML files. + let ritualTablesYamlFiles = FILES_IN_HANDBOOK_FOLDER.filter((filePath)=>{ + return _.endsWith(filePath, 'rituals.yml'); + }); + + let githubLabelsToCheck = []; + let KNOWN_AUTOMATABLE_FREQUENCIES = ['Daily', 'Weekly', 'Triweekly']; + // Process each rituals YAML file. These will be added to the builtStaticContent as JSON + for(let ritualsYamlFilePath of ritualTablesYamlFiles){ + // Get this rituals.yml file's parent folder name, we'll use this as the key for this section's rituals in the ritualsTables dictionary + let relativeRepoPathForThisRitualsFile = path.relative(topLvlRepoPath, ritualsYamlFilePath); + // Parse the rituals YAML file. + let yaml = await sails.helpers.fs.read(ritualsYamlFilePath); + let ritualsFromRitualTableYaml = YAML.parse(yaml, {prettyErrors: true}); + + // Make sure each ritual in the rituals YAML file has a task, startedOn, frequency, description, and DRI. + for(let ritual of ritualsFromRitualTableYaml){ + if(!ritual.task){ // Throw an error if a ritual is missing a task + throw new Error(`Could not build rituals from ${ritualsYamlFilePath}. A ritual in the YAML file is missing a task. To resolve add a task value (the name of the ritual) and try running this script again`); + } + if(!ritual.startedOn){// Throw an error if a ritual is missing a startedOn value. + throw new Error(`Could not build rituals from ${ritualsYamlFilePath}. A ritual in the YAML file is missing a startedOn. To resolve add a startedOn value to the "${ritual.task}" ritual and try running this script again`); + } + if(!ritual.frequency){// Throw an error if a ritual is missing a frequency value. + throw new Error(`Could not build rituals from ${ritualsYamlFilePath}. A ritual in the YAML file is missing a frequency. To resolve add a frequency value to the "${ritual.task}" ritual and try running this script again`); + } + if(!ritual.description){// Throw an error if a ritual is missing a description value. + throw new Error(`Could not build rituals from ${ritualsYamlFilePath}. A ritual in the YAML file is missing a description. To resolve add a description value to the "${ritual.task}" ritual and try running this script again`); + } + if(!ritual.dri){// Throw an error if a ritual is missing a dri value. + throw new Error(`Could not build rituals from ${ritualsYamlFilePath}. A ritual in the YAML file is missing a DRI. To resolve add a DRI value to the "${ritual.task}" ritual and try running this script again`); + } + if (ritual.autoIssue) { // If this ritual has an autoIssue value, we'll check to make sure the frequency is supported, and that the autoIssue value has a label value (an array of strings). + if (!KNOWN_AUTOMATABLE_FREQUENCIES.includes(ritual.frequency)) { + throw new Error(`Could not build rituals from ${ritualsYamlFilePath}. Invalid ritual: "${ritual.task}" indicates frequency "${ritual.frequency}", but that isn't supported with automations turned on. Supported frequencies: ${KNOWN_AUTOMATABLE_FREQUENCIES}`); + } + if(!skipGithubRequests){ // If the ritual has an autoIssue value, we'll validate that the DRI value is a GitHub username. + await sails.helpers.http.get.with({ + url: 'https://api.github.com/users/' + encodeURIComponent(ritual.dri), + headers: baseHeadersForGithubRequests + }).intercept((err)=>{// If the above GET requests return a non 200 response we'll look for signs that the user has hit their GitHub API rate limit. + if (err.raw.statusCode === 403 && err.raw.headers['x-ratelimit-remaining'] === '0') {// If the user has reached their GitHub API rate limit, we'll throw an error that suggest they run this script with the `--skipGithubRequests` flag. + return new Error('GitHub API rate limit exceeded. If you\'re running this script in a development environment, use the `--skipGithubRequests` flag to skip querying the GitHub API. See full error for more details:\n'+err); + } else if(err.raw.statusCode === 404) {// If the GitHub API responds with a 404, we'll throw an error with a message about the invalid GitHub username. + return new Error(`Could not build rituals from ${ritualsYamlFilePath}. The DRI value of a ritual (${ritual.task}) contains an invalid GitHub username (${ritual.dri}). To resolve, make sure the DRI value for this ritual is a valid GitHub username.`); + } else {// If the error was not a 404 and not because of the user's API rate limit, we'll display the full error + return err; + } + }); + } + if(!ritual.autoIssue.labels || !_.isArray(ritual.autoIssue.labels)){ // If the autoIssue value exists, but does not contain an array of labels, throw an error + throw new Error(`Could not build rituals from ${ritualsYamlFilePath}. "${ritual.task}" contains an invalid autoIssue value. To resolve, add a "labels" value (An array of strings) to the autoIssue value.`); + } + // Check each label in the labels array + for(let label of ritual.autoIssue.labels) { + if(typeof label !== 'string') { + throw new Error(`Could not build rituals from ${ritualsYamlFilePath}. A ritual (${ritual.task}) in the YAML file contains an invalid value in the labels array of the autoIssue value. To resolve, ensure every value in the nested labels array of the autoIssue value is a string.`); + } + // Add this label to the array of labels to check. We'll check to see if all labels are valid at the after we've processed all rituals YAML files. + githubLabelsToCheck.push({ + label: label, + ritualUsingLabel: ritual.task, + ritualsYamlFilePath: relativeRepoPathForThisRitualsFile + }); + }//∞ + } + + } + // Add the rituals from this file to the rituals dictionary, using the file's relativeRepoPath (e.g, handbook/company/rituals.md) as a key. + rituals[relativeRepoPathForThisRitualsFile] = ritualsFromRitualTableYaml; + + }//∞ + // Validate all GitHub labels used in all ritual yaml files. Note: We check these here to minimize requests to the GitHub API. We'll send requests to get all existing labels in the Fleet repo, and will throw an error if a label in a rituals YAML file does not exist in the repo. + if(!skipGithubRequests) { + let allExistingLabelsInFleetRepo = []; + let pageOfResultsReturned = 1; + // Get all the labels in the fleetdm/fleet repo. Note: We use sails.helpers.flow.until() here so we can build + await sails.helpers.flow.until(async ()=>{ + let pageOfLabels = await sails.helpers.http.get.with({ + url: `https://api.github.com/repos/fleetdm/fleet/labels?per_page=100&page=${pageOfResultsReturned}`, + headers: baseHeadersForGithubRequests + }); + allExistingLabelsInFleetRepo = allExistingLabelsInFleetRepo.concat(pageOfLabels); + pageOfResultsReturned++; + return pageOfLabels.length < 100; + });//∞ + // Get an array containing only the names of labels. + let allLabelNames = _.pluck(allExistingLabelsInFleetRepo, 'name'); + // Validate each label, if a label does not exist in the fleetdm/fleet repo, throw an error. + await sails.helpers.flow.simultaneouslyForEach(githubLabelsToCheck, async(labelInfo)=>{ + if(!_.contains(allLabelNames, labelInfo.label)){ + throw new Error(`Could not build rituals from ${labelInfo.ritualsYamlFilePath}. The labels array nested within the autoIssue value of a ritual (${labelInfo.ritualUsingLabel}) contains an invalid GitHub label (${labelInfo.label}). To resolve, make sure all labels in the labels array are labels that exist in the fleetdm/fleet repo.`); + } + });//∞ + }//fi + + // Add the rituals dictionary to builtStaticContent.rituals + builtStaticContent.rituals = rituals; + }, ]); diff --git a/website/scripts/create-issues-for-todays-rituals.js b/website/scripts/create-issues-for-todays-rituals.js index d174e1a221..1da5b54fc0 100644 --- a/website/scripts/create-issues-for-todays-rituals.js +++ b/website/scripts/create-issues-for-todays-rituals.js @@ -38,23 +38,6 @@ module.exports = { } - // Validate rituals - for (let ritual of rituals) { - - let KNOWN_AUTOMATABLE_FREQUENCIES = ['Daily', 'Weekly', 'Triweekly'];//TODO: others - if (ritual.autoIssue && !KNOWN_AUTOMATABLE_FREQUENCIES.includes(ritual.frequency)) { - throw new Error(`Invalid ritual: "${ritual.task}" indicates frequency "${ritual.frequency}", but that isn't supported with automations turned on. Supported frequencies: ${KNOWN_AUTOMATABLE_FREQUENCIES}`); - } - - // TODO: validate task - // TODO: validate description - // TODO: validate DRI (github username) - // TODO: validate ritual.autoIssue - // TODO: validate ritual.autoIssue.labels - - // TODO: other validations - - }//∞ for (let ritual of rituals) { diff --git a/website/views/layouts/layout-customer.ejs b/website/views/layouts/layout-customer.ejs index 560194c1fc..f3b8c2e623 100644 --- a/website/views/layouts/layout-customer.ejs +++ b/website/views/layouts/layout-customer.ejs @@ -222,6 +222,7 @@ + diff --git a/website/views/layouts/layout-landing.ejs b/website/views/layouts/layout-landing.ejs index 69960d8be8..854bc1e576 100644 --- a/website/views/layouts/layout-landing.ejs +++ b/website/views/layouts/layout-landing.ejs @@ -222,6 +222,7 @@ + diff --git a/website/views/layouts/layout-sandbox.ejs b/website/views/layouts/layout-sandbox.ejs index 687de4eec9..bc9d424589 100644 --- a/website/views/layouts/layout-sandbox.ejs +++ b/website/views/layouts/layout-sandbox.ejs @@ -349,6 +349,7 @@ + diff --git a/website/views/layouts/layout.ejs b/website/views/layouts/layout.ejs index cb55ec6084..2fe645cc75 100644 --- a/website/views/layouts/layout.ejs +++ b/website/views/layouts/layout.ejs @@ -422,6 +422,7 @@ +