Add tags to standard query library and fleetdm.com/queries (#3970)

* handle query tags in build-static-content script, update query readme

* show tags in query library, add ability to filter by tags

* fix lint errors

* update mobile styles

* fix CTA link

* update mobile layout

* remove tag line-height and font size

* Update build-static-content.js

* Style update

* remove margin from selected tag, adjust OS logo placement

* requested changes from code review

Co-authored-by: Mike Thomas <mthomas@fleetdm.com>
This commit is contained in:
eashaw
2022-02-03 15:49:36 -06:00
committed by GitHub
co-authored by Mike Thomas
parent 4cb23ea586
commit 243306de02
7 changed files with 105 additions and 8 deletions
@@ -27,6 +27,7 @@ Want to add your own query?
purpose: What is the goal of running your query? Ex. Detection
remediation: Are there any remediation steps to resolve the detection triggered by your query? If not, insert "N/A."
contributors: zwass,mike-j-thomas
tags: Keywords that can help users find other relevant queries, each tag should be seperated by a comma. (e.g., "foo,bar")
```
2. Replace each field and submit a pull request to the fleetdm/fleet GitHub repository.
+12 -1
View File
@@ -8,6 +8,7 @@ parasails.registerPage('query-library', {
searchString: '', // The user input string to be searched against the query library
selectedKind: 'all queries', // Initially set to all, the user may select a different option to filter queries by purpose (e.g., "all queries", "informational", "policies")
selectedPlatform: 'all platforms', // Initially set to all, the user may select a different option to filter queries by platform (e.g., "all platforms", "macOS", "Windows", "Linux")
selectedTag: '', // Initially set to a blank string, the user may select a tag filter queries by.
},
computed: {
@@ -15,7 +16,8 @@ parasails.registerPage('query-library', {
return this.queries.filter(
(query) =>
this._isIncluded(query.platforms, this.selectedPlatform) &&
this._isIncluded(query.kind, this.selectedKind)
this._isIncluded(query.kind, this.selectedKind) &&
this._isIncluded(query.tags, this.selectedTag)
);
},
@@ -50,6 +52,10 @@ parasails.registerPage('query-library', {
this.selectedPlatform = platform;
},
clickSelectTag(tag) {
this.selectedTag = tag;
},
clickCard: function (querySlug) {
window.location = '/queries/' + querySlug; // we can trust the query slug is url-safe
},
@@ -111,6 +117,11 @@ parasails.registerPage('query-library', {
textToSearch += ', ' + normalize(contributor.name) + ', ' + normalize(contributor.handle);
});
}
if (query.tags) {
query.tags.forEach((tag) => {
textToSearch += ', ' + normalize(tag);
});
}
return (searchTerms.some((term) => textToSearch.includes(term)));
});
},
+8
View File
@@ -27,6 +27,14 @@
border-color: #E2E4EA;
}
[purpose='query-tag'] {
font-size: 12px;
font-weight: 700;
padding: 4px 8px;
border-radius: 20px;
background-color: #E2E4EA;
}
[purpose='remediation'] {
overflow-wrap: break-word;
word-wrap: break-word;
+35 -1
View File
@@ -36,6 +36,25 @@
}
}
[purpose='query-tag'] {
font-size: 12px;
font-weight: 700;
padding: 4px 8px;
border-radius: 20px;
background-color: #E2E4EA;
}
[purpose='selected-tag'] {
border-bottom: 1px solid #e1e4e9;
padding: 16px 0;
margin-left: 30px;
margin-right: 30px;
p {
margin-bottom: 0;
line-height: 18px;
}
}
.input-group {
&.search {
width: 250px;
@@ -205,7 +224,7 @@
border-radius: 8px;
&:hover {
.query-card {
background-color: #f1f0ff;
background-color: #F8F7FF;
box-shadow: none;
border: none;
border-radius: 8px;
@@ -221,6 +240,13 @@
box-shadow: 0 4px 16px 0 rgba(0, 0, 0, 0.1);
margin-bottom: 90px;
width: 100%;
padding: 40px;
p {
margin-block-end: 0px;
}
a {
font-size: 16px;
}
}
.card-body {
@@ -268,6 +294,14 @@
margin-bottom: 0px;
}
[purpose='selected-tag'] {
margin-left: 30px;
margin-right: 30px;
[purpose='query-tag'] {
margin-top: 8px;
}
}
.results {
margin-top: 16px;
}
+23
View File
@@ -30,6 +30,7 @@ module.exports = {
let queriesWithProblematicResolutions = [];
let queriesWithProblematicContributors = [];
let queriesWithProblematicTags = [];
let queries = YAML.parseAllDocuments(yaml).map((yamlDocument)=>{
let query = yamlDocument.toJSON().spec;
query.kind = yamlDocument.toJSON().kind;
@@ -40,6 +41,25 @@ module.exports = {
} else if (query.resolution === undefined) {
query.resolution = 'N/A';// « We set this to a string here so that the data type is always string. We use N/A so folks can see there's no remediation and contribute if desired.
}
if (query.tags) {
if(!_.isString(query.tags)) {
queriesWithProblematicTags.push(query);
} else {
// Splitting tags into an array to format them.
let tagsToFormat = query.tags.split(',');
let formattedTags = [];
for (let tag of tagsToFormat) {
if(tag !== '') {// « Ignoring any blank tags caused by trailing commas in the YAML.
// Formatting tags in sentence case, and removing any extra whitespace.
formattedTags.push(_.capitalize(_.trim(tag)));
}
}
// Removing any duplicate tags.
query.tags = _.uniq(formattedTags);
}
} else {
query.tags = []; // « if there are no tags, we set query.tags to an empty array so it is always the same data type.
}
// GitHub usernames may only contain alphanumeric characters or single hyphens, and cannot begin or end with a hyphen.
if (!query.contributors || (query.contributors !== undefined && !_.isString(query.contributors)) || query.contributors.split(',').some((contributor) => contributor.match('^[^A-za-z0-9].*|[^A-Za-z0-9-]|.*[^A-za-z0-9]$'))) {
@@ -52,6 +72,9 @@ module.exports = {
if (queriesWithProblematicResolutions.length >= 1) {
throw new Error('Failed parsing YAML for query library: The "resolution" of a query should either be absent (undefined) or a single string (not a list of strings). And "resolution" should only be present when a query\'s kind is "policy". But one or more queries have an invalid "resolution": ' + _.pluck(queriesWithProblematicResolutions, 'slug').sort());
}//•
if (queriesWithProblematicTags.length >= 1) {
throw new Error('Failed parsing YAML for query library: The "tags" of a query should either be absent (undefined) or a single string (not a list of strings). "tags" should be be be seperated by a comma. But one or more queries have invalid "tags": ' + _.pluck(queriesWithProblematicTags, 'slug').sort());
}
// Assert uniqueness of slugs.
if (queries.length !== _.uniq(_.pluck(queries, 'slug')).length) {
throw new Error('Failed parsing YAML for query library: Queries as currently named would result in colliding (duplicate) slugs. To resolve, rename the queries whose names are too similar. Note the duplicates: ' + _.pluck(queries, 'slug').sort());
+11
View File
@@ -41,6 +41,17 @@
</div>
</div>
</div>
<div class="border-top py-2 pb-md-4">
<div class="d-flex flex-md-column justify-content-between justify-content-md-start align-items-center align-items-md-start pt-md-3">
<h5 class="pb-md-2 m-0">Tags</h5>
<div :class="[query.tags.length > 2 ? 'mt-1' : '']" class="d-flex flex-wrap align-items-center justify-content-end justify-content-sm-start">
<span class="mb-1" v-if="!query.tags || !query.tags.length">--</span>
<div purpose="query-tag" class="mr-md-1 ml-1 ml-md-0" :class="[query.tags.length > 2 ? 'mb-1' : '']" v-for="tag in query.tags">
{{tag}}
</div>
</div>
</div>
</div>
<div class="border-top py-2">
<div class="d-flex flex-md-column justify-content-between justify-content-md-start align-items-center align-items-md-start py-1 py-md-3">
<h5 class="pb-md-2 m-0">Purpose</h5>
+15 -6
View File
@@ -92,18 +92,28 @@
</div>
</div>
<div class="results">
<div purpose="selected-tag" v-if="selectedTag">
<p>Showing {{ selectedKind === 'query' ? 'informational queries' : selectedKind === 'policy' ? 'policies' : 'all queries'}} for <span purpose="query-tag" class="d-inline-block" style="cursor: pointer;" @click="clickSelectTag('')">{{selectedTag}}<span style="color: #8b8fa2;" class="fa fa-times-circle pl-2"></span></span></p>
</div>
<div class="category__informational">
<div v-for="query of queriesList">
<div class="card results" @click="clickCard(query.slug)">
<div class="card-body">
<div class="row justify-content-between align-items-center query-card">
<div class="col-12">
<div class="d-block d-sm-flex flex-wrap">
<h5 class="card-title m-0 mb-1 mr-sm-2">{{query.name}}</h5>
<div class="my-2 my-sm-0 flex-wrap">
<span class="mr-2 mb-1 text-nowrap d-inline-block" purpose="query-tag" v-for="tag in query.tags" @click.stop="clickSelectTag(tag)">{{tag}}</span>
</div>
</div>
</div>
<div class="col-sm-9 col-md-9">
<h5 class="card-title m-0">{{query.name}}</h5>
<p class="font-italic mb-1 p-0 description">{{query.description}}</p>
<div class="contributors" v-if="query.contributors && query.contributors.length">
<div class="d-flex mb-2 mb-sm-1 align-items-center">
<div v-for="contributor in query.contributors">
<div class="d-flex m-1 avatar-frame" @click="clickAvatar(contributor)">
<div class="d-flex m-1 avatar-frame" @click.stop="clickAvatar(contributor)">
<img alt="a GitHub user avatar" :alt="contributor" :src="contributor.avatarUrl" />
</div>
</div>
@@ -111,7 +121,7 @@
</div>
</div>
</div>
<div class="col-sm-3 col-md-auto">
<div class="col-sm-3 col-md-auto align-self-start">
<div class="text-sm-right m-0">
<img class="d-inline-flex mr-1 mr-sm-0 ml-sm-1 ml-md-2 logo"
src="/images/os-macos-black-16x16@2x.png" alt="macOS"
@@ -138,12 +148,11 @@
</div>
<div class="d-flex justify-content-center p-3">
<div class="card call-to-action col-6-grow my-5 library">
<div class="card-body">
<div class="card-body px-0">
<h3 class="mb-3">Contributors</h3>
<p><strong>Want to add your own query?</strong> Please submit a pull request
<a href="https://github.com/fleetdm/fleet/edit/main/docs/01-Using-Fleet/standard-query-library/standard-query-library.yml">
over on GitHub
</a>.
over on GitHub</a>.
</p>
</div>
</div>