-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
6 changed files
with
151 additions
and
53 deletions.
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
import { assertEquals } from "https://deno.land/[email protected]/assert/mod.ts"; | ||
import { getLink } from "./elk.ts"; | ||
|
||
// NOTE: Each of these URLs should be opened in a browser to verify that they work as expected. | ||
|
||
Deno.test("getLink with simple input", () => { | ||
const got = getLink("devx", { app: "riff-raff", stage: "PROD" }); | ||
const want = | ||
"https://logs.gutools.co.uk/s/devx/app/discover#/?_g=(filters:!((query:(match_phrase:(app:'riff-raff'))),(query:(match_phrase:(stage:'PROD')))))"; | ||
assertEquals(got, want); | ||
}); | ||
|
||
Deno.test("getLink with columns", () => { | ||
const got = getLink("devx", { app: "riff-raff", stage: "PROD" }, [ | ||
"message", | ||
"level", | ||
]); | ||
const want = | ||
"https://logs.gutools.co.uk/s/devx/app/discover#/?_g=(filters:!((query:(match_phrase:(app:'riff-raff'))),(query:(match_phrase:(stage:'PROD')))))&_a=(columns:!(message,level))"; | ||
assertEquals(got, want); | ||
}); | ||
|
||
/* | ||
Filters and columns with colon(:) input should get wrapped in single quotes(') so that Kibana can parse them correctly. | ||
That is, gu:repo should become 'gu:repo'. | ||
*/ | ||
Deno.test("getLink with colon(:) input", () => { | ||
const got = getLink("devx", { | ||
"gu:repo.keyword": "guardian/amigo", | ||
stage: "PROD", | ||
}, ["message", "gu:repo"]); | ||
const want = | ||
"https://logs.gutools.co.uk/s/devx/app/discover#/?_g=(filters:!((query:(match_phrase:('gu:repo.keyword':'guardian/amigo'))),(query:(match_phrase:(stage:'PROD')))))&_a=(columns:!(message,'gu:repo'))"; | ||
assertEquals(got, want); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
function escapeColon(str: string): string { | ||
return str.includes(":") ? `'${str}'` : str; | ||
} | ||
export function getLink( | ||
space: string, | ||
filters: Record<string, string>, | ||
columns: string[] = [], | ||
): string { | ||
const kibanaFilters = Object.entries(filters).map(([key, value]) => { | ||
return `(query:(match_phrase:(${escapeColon(key)}:'${value}')))`; | ||
}); | ||
|
||
// The `#/` at the end is important for Kibana to correctly parse the query string | ||
// The `URL` object moves this to the end of the string, which breaks the link. | ||
const base = `https://logs.gutools.co.uk/s/${space}/app/discover#/`; | ||
|
||
const query = { | ||
...(kibanaFilters.length > 0 && { | ||
_g: `(filters:!(${kibanaFilters.join(",")}))`, | ||
}), | ||
...(columns.length > 0 && { | ||
_a: `(columns:!(${columns.map(escapeColon).join(",")}))`, | ||
}), | ||
}; | ||
|
||
const queryString = Object.entries(query) | ||
.map(([key, value]) => `${key}=${value}`) | ||
.join("&"); | ||
|
||
return `${base}?${queryString}`; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,35 +1,8 @@ | ||
import type { Args } from "https://deno.land/[email protected]/flags/mod.ts"; | ||
import { parse } from "https://deno.land/[email protected]/flags/mod.ts"; | ||
import { open } from "https://deno.land/x/[email protected]/index.ts"; | ||
|
||
export function getLink( | ||
space: string, | ||
filters: Record<string, string>, | ||
columns: string[], | ||
): string { | ||
const kibanaFilters = Object.entries(filters).map(([key, value]) => { | ||
return `(query:(match_phrase:(${key}:'${value}')))`; | ||
}); | ||
|
||
// The `#/` at the end is important for Kibana to correctly parse the query string | ||
// The `URL` object moves this to the end of the string, which breaks the link. | ||
const base = `https://logs.gutools.co.uk/s/${space}/app/discover#/`; | ||
|
||
const query = { | ||
...(kibanaFilters.length > 0 && { | ||
_g: `(filters:!(${kibanaFilters.join(",")}))`, | ||
}), | ||
...(columns.length > 0 && { | ||
_a: `(columns:!(${columns.join(",")}))`, | ||
}), | ||
}; | ||
|
||
const queryString = Object.entries(query) | ||
.map(([key, value]) => `${key}=${value}`) | ||
.join("&"); | ||
|
||
return `${base}?${queryString}`; | ||
} | ||
import { getLink } from "./elk.ts"; | ||
import { parseFilters, removeUndefined } from "./transform.ts"; | ||
|
||
function parseArguments(args: string[]): Args { | ||
return parse(args, { | ||
|
@@ -48,29 +21,6 @@ function parseArguments(args: string[]): Args { | |
}); | ||
} | ||
|
||
function escapeColon(str: string): string { | ||
return str.includes(":") ? `'${str}'` : str; | ||
} | ||
|
||
function parseFilters(filter: string[]): Record<string, string> { | ||
return filter.reduce((acc, curr) => { | ||
const [key, value] = curr.split("="); | ||
return { ...acc, [escapeColon(key)]: value }; | ||
}, {}); | ||
} | ||
|
||
function removeUndefined( | ||
obj: Record<string, string | undefined>, | ||
): Record<string, string> { | ||
return Object.entries(obj).filter(([, value]) => !!value).reduce( | ||
(acc, [key, value]) => ({ | ||
...acc, | ||
[key]: value, | ||
}), | ||
{}, | ||
); | ||
} | ||
|
||
function printHelp(): void { | ||
console.log(`Usage: devx-logs [OPTIONS...]`); | ||
console.log("\nOptional flags:"); | ||
|
@@ -114,7 +64,7 @@ async function main(inputArgs: string[]) { | |
}; | ||
|
||
const filters = removeUndefined(mergedFilters); | ||
const link = getLink(space, filters, column.map(escapeColon)); | ||
const link = getLink(space, filters, column); | ||
|
||
console.log(link); | ||
|
||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
import { assertEquals } from "https://deno.land/[email protected]/assert/assert_equals.ts"; | ||
import { parseFilters, removeUndefined } from "./transform.ts"; | ||
|
||
Deno.test("parseFilters", () => { | ||
const got = parseFilters(["stack=deploy", "stage=PROD", "app=riff-raff"]); | ||
const want = { | ||
stack: "deploy", | ||
stage: "PROD", | ||
app: "riff-raff", | ||
}; | ||
assertEquals(got, want); | ||
}); | ||
|
||
Deno.test("removeUndefined", () => { | ||
const got = removeUndefined({ | ||
stack: "deploy", | ||
stage: undefined, | ||
app: "riff-raff", | ||
team: undefined, | ||
}); | ||
const want = { | ||
stack: "deploy", | ||
app: "riff-raff", | ||
}; | ||
assertEquals(got, want); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
/** | ||
* Turn an array of strings of form `key=value` into an object of form `{ key: value }` | ||
*/ | ||
export function parseFilters(filter: string[]): Record<string, string> { | ||
return filter.reduce((acc, curr) => { | ||
const [key, value] = curr.split("="); | ||
return { ...acc, [key]: value }; | ||
}, {}); | ||
} | ||
|
||
/** | ||
* Remove keys from a `Record` whose value is `undefined` | ||
*/ | ||
export function removeUndefined( | ||
obj: Record<string, string | undefined>, | ||
): Record<string, string> { | ||
return Object.entries(obj).filter(([, value]) => !!value).reduce( | ||
(acc, [key, value]) => ({ | ||
...acc, | ||
[key]: value, | ||
}), | ||
{}, | ||
); | ||
} |