diff --git a/eslint.config.mjs b/eslint.config.mjs index 9812433..351d06e 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -27,6 +27,7 @@ export default defineConfig([ '@typescript-eslint/no-require-imports': 'off', '@typescript-eslint/no-this-alias': 'off', '@typescript-eslint/no-explicit-any': 'off', + '@typescript-eslint/no-namespace': 'off', }, }, ]); diff --git a/src/alert/condition.ts b/src/alert/condition.ts index 048a25f..0e951fd 100644 --- a/src/alert/condition.ts +++ b/src/alert/condition.ts @@ -1,13 +1,17 @@ -import type { GrafanaCondition } from '../grafana'; +import type { + GrafanaCondition, + GrafanaEvaluatorType, + GrafanaOperatorType, + GrafanaReducerType, +} from '../grafana'; class Condition { - private state: GrafanaCondition; - private _evaluator: { params: any[]; type: string }; - private _operator: { type: string }; - private _query: { params: string[] }; - private _reducer: { params: any[]; type: string }; + private state: Partial; + private _evaluator: GrafanaCondition['evaluator']; + private _operator: GrafanaCondition['operator']; + private _query: GrafanaCondition['query']; + private _reducer: GrafanaCondition['reducer']; constructor(opts: Partial = {}) { - // @ts-expect-error todo: should fields be optional? this.state = {}; this._evaluator = { @@ -32,7 +36,12 @@ class Condition { this.state[opt] = opts[opt]; }); } - withEvaluator(value, type) { + withEvaluator(value: (string | number)[], type: 'within_range'): Condition; + withEvaluator(value: string | number, type: 'gt' | 'lt'): Condition; + withEvaluator( + value: (string | number) | (string | number)[], + type: GrafanaEvaluatorType + ) { const types = ['gt', 'lt', 'within_range']; if (!types.includes(type)) { @@ -42,15 +51,15 @@ class Condition { this._evaluator.type = type; if (['gt', 'lt'].includes(type)) { - this._evaluator.params = [value]; + this._evaluator.params = [value as string]; } else if (Array.isArray(value)) { this._evaluator.params = value; } return this; } - withOperator(operator) { - const types = ['and', 'or']; + withOperator(operator: GrafanaOperatorType) { + const types = ['and', 'or'] as const; if (!types.includes(operator)) { throw Error(`Operator must be one of [${types.toString}]`); @@ -67,7 +76,7 @@ class Condition { this._operator.type = 'and'; return this; } - onQuery(query, duration, from) { + onQuery(query: string, duration?: string, from?: string) { if (typeof query !== 'string') { throw Error( 'Query identifier must be a string. eg. "A" or "B", etc...' @@ -79,7 +88,7 @@ class Condition { return this; } - withReducer(type) { + withReducer(type: GrafanaReducerType) { const types = [ 'min', 'max', @@ -89,7 +98,7 @@ class Condition { 'last', 'median', 'diff', - ]; + ] satisfies GrafanaReducerType[]; if (!types.includes(type)) { throw Error(`Reducer has to be one of [${types.toString()}]`); @@ -98,7 +107,7 @@ class Condition { this._reducer.type = type; return this; } - generate() { + generate(): GrafanaCondition { return Object.assign( {}, { diff --git a/src/alert/index.ts b/src/alert/index.ts index fe34d2a..164ca22 100644 --- a/src/alert/index.ts +++ b/src/alert/index.ts @@ -1,7 +1,11 @@ -import Alert = require('./alert'); -import Condition = require('./condition'); +import _Alert = require('./alert'); +import _Condition = require('./condition'); -export = { - Alert: Alert, - Condition: Condition, -}; +namespace alert { + export type Alert = _Alert; + export const Alert = _Alert; + export type Condition = _Condition; + export const Condition = _Condition; +} + +export = alert; diff --git a/src/annotations/index.ts b/src/annotations/index.ts index ae5a35e..fa946e8 100644 --- a/src/annotations/index.ts +++ b/src/annotations/index.ts @@ -18,8 +18,11 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. -import Graphite = require('./graphite'); +import _Graphite = require('./graphite'); -export = { - Graphite: Graphite, -}; +namespace annotations { + export type Graphite = _Graphite; + export const Graphite = _Graphite; +} + +export = annotations; diff --git a/src/grafana.ts b/src/grafana.ts index 497e6ac..61e883a 100644 --- a/src/grafana.ts +++ b/src/grafana.ts @@ -66,12 +66,32 @@ export interface GrafanaDashboardListPanel extends GrafanaSharedProps { links: any[]; } +export type GrafanaEvaluatorType = 'gt' | 'lt' | 'within_range'; + +export type GrafanaOperatorType = 'and' | 'or'; + +export type GrafanaReducerType = + | 'min' + | 'max' + | 'sum' + | 'avg' + | 'count' + | 'last' + | 'median' + | 'diff'; + export interface GrafanaCondition extends GrafanaSharedProps { - type: string; - query: { params: string[] }; - reducer: { type: string; params: any[] }; - evaluator: { type: string; params: any[] }; - operator: { type: string }; + type: 'query'; + query: { + params: [ + query: string, + duration: string | undefined, + from: string | undefined, + ]; + }; + reducer: { params: string[]; type: GrafanaReducerType }; + evaluator: { params: (string | number)[]; type: GrafanaEvaluatorType }; + operator: { type: GrafanaOperatorType }; } export interface GrafanaAlert extends GrafanaSharedProps { @@ -91,97 +111,122 @@ export interface GrafanaGraphPanel extends GrafanaSharedProps { alert?: GrafanaAlert; aliasColors: any; bars: boolean; - editable: boolean; - error: boolean; + editable?: boolean; + error?: boolean; fill: number; - grid: { - leftMax: null; - leftMin: null; - rightMax: null; - rightMin: null; - threshold1: null; - threshold1Color: string; - threshold2: null; - threshold2Color: string; + grid?: { + leftMax?: null; + leftMin?: null; + rightMax?: null; + rightMin?: null; + threshold1?: null; + threshold1Color?: string; + threshold2?: null; + threshold2Color?: string; }; id: number; legend: { + alignAsTable?: boolean; + rightSide?: boolean; avg: boolean; current: boolean; max: boolean; min: boolean; - show: boolean; + show?: boolean; total: boolean; values: boolean; + sort?: string; + sortDesc?: boolean; + table?: boolean; + hideEmpty?: boolean; + hideZero?: boolean; + sideWidth?: number; }; lines: boolean; linewidth: number; - links: any[]; + links?: any[]; nullPointMode: string; percentage: boolean; pointradius: number; points: boolean; renderer: string; seriesOverrides: any[]; - span: number; + span?: number; stack: boolean; steppedLine: boolean; targets: any[]; title: string; - tooltip: { shared: boolean; value_type: string }; + tooltip: { shared: boolean; value_type: string; sort?: number }; type: 'graph'; - 'x-axis': boolean; - 'y-axis': boolean; - y_formats: string[]; - datasource: string; + 'x-axis'?: boolean; + 'y-axis'?: boolean; + y_formats?: string[]; + datasource?: + | null + | { + type: string; + uid: string; + } + | string; } export interface GrafanaSingleStatPanel extends GrafanaSharedProps { - cacheTimeout: null; - colorBackground: boolean; - colorValue: boolean; - colors: string[]; - editable: boolean; - error: boolean; - format: string; + cacheTimeout?: null; + colorBackground?: boolean; + colorValue?: boolean; + colors?: string[]; + editable?: boolean; + error?: boolean; + format?: string; id: number; - interval: null; - links: any[]; - maxDataPoints: number; - nullPointMode: string; - nullText: null; - postfix: string; - postfixFontSize: string; - prefix: string; - prefixFontSize: string; - span: number; - sparkline: { + interval?: null; + links?: any[]; + maxDataPoints?: number; + nullPointMode?: string; + nullText?: null; + postfix?: string; + postfixFontSize?: string; + prefix?: string; + prefixFontSize?: string; + span?: number; + sparkline?: { fillColor: string; full: boolean; lineColor: string; show: boolean; }; targets: any[]; - thresholds: string; + thresholds?: string; title: string; - type: 'singlestat'; - valueFontSize: string; - valueMaps: { op: string; text: string; value: string }[]; - valueName: string; - datasource: string; + type: 'singlestat' | 'stat'; + valueFontSize?: string; + valueMaps?: { op: string; text: string; value: string }[]; + valueName?: string; + datasource?: + | null + | { + type: string; + uid: string; + } + | string; } export interface GrafanaTablePanel extends GrafanaSharedProps { title: string; - error: boolean; - span: number; - editable: boolean; + error?: boolean; + span?: number; + editable?: boolean; type: 'table'; - isNew: boolean; + isNew?: boolean; id: number; - // todo: should this be required? - datasource?: string; - styles: ( + datasource?: + | null + | { + type: string; + uid: string; + } + | string; + styles?: ( | { type: 'date'; pattern: string; dateFormat: string } | { unit: string; @@ -194,27 +239,27 @@ export interface GrafanaTablePanel extends GrafanaSharedProps { } )[]; targets: any[]; - transform: string; - pageSize: null; - showHeader: boolean; - columns: { text: string; value: string }[]; - scroll: boolean; - fontSize: string; - sort: { col: number; desc: boolean }; - links: any[]; + transform?: string; + pageSize?: null; + showHeader?: boolean; + columns?: { text: string; value: string }[]; + scroll?: boolean; + fontSize?: string; + sort?: { col: number; desc: boolean }; + links?: any[]; } export interface GrafanaTextPanel extends GrafanaSharedProps { - title: string; - error: boolean; - span: number; - editable: boolean; + title?: string; + error?: boolean; + span?: number; + editable?: boolean; type: 'text'; id: number; - mode: string; - content: string; - style: any; - links: any[]; + mode?: string; + content?: string; + style?: any; + links?: any[]; } export type GrafanaPanel = @@ -222,17 +267,22 @@ export type GrafanaPanel = | GrafanaGraphPanel | GrafanaSingleStatPanel | GrafanaTablePanel - | GrafanaTextPanel; + | GrafanaTextPanel + | GrafanaRowPanel; export interface GrafanaRow extends GrafanaSharedProps { - title: string; - showTitle: boolean; - height: string; - editable: boolean; - collapse: boolean; + title?: string; + showTitle?: boolean; + height?: string; + editable?: boolean; + collapse?: boolean; panels: GrafanaPanel[]; } +export interface GrafanaRowPanel extends GrafanaRow { + type: 'row'; +} + export interface GrafanaExternalLink extends GrafanaSharedProps { title: string; tooltip?: string; @@ -260,7 +310,8 @@ export interface GrafanaDashboard extends GrafanaSharedProps { refresh: boolean | string; schemaVersion: number; hideAllLegends?: boolean; - rows: GrafanaRow[]; + rows?: GrafanaRow[]; + panels?: GrafanaPanel[]; annotations: { list: GrafanaGraphiteAnnotation[]; enable?: boolean }; templating: { list: GrafanaTemplate[]; enable?: boolean }; time?: null | { diff --git a/src/index.ts b/src/index.ts index f939ab7..92a3e86 100644 --- a/src/index.ts +++ b/src/index.ts @@ -18,30 +18,38 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. -import Dashboard = require('./dashboard'); -import Row = require('./row'); -import ExternalLink = require('./external-link'); -import Target = require('./target'); -import Panels = require('./panels'); -import Alert = require('./alert/alert'); -import Condition = require('./alert/condition'); -import Templates = require('./templates'); -import publish = require('./publish'); -import generateGraphId = require('./id'); +import _Dashboard = require('./dashboard'); +import _Row = require('./row'); +import _ExternalLink = require('./external-link'); +import _Target = require('./target'); +import _Panels = require('./panels'); +import _Alert = require('./alert/alert'); +import _Condition = require('./alert/condition'); +import _Templates = require('./templates'); +import _publish = require('./publish'); +import _generateGraphId = require('./id'); import config = require('./config'); -import Annotations = require('./annotations'); +import _Annotations = require('./annotations'); -export = { - Dashboard: Dashboard, - Row: Row, - ExternalLink: ExternalLink, - Panels: Panels, - Templates: Templates, - Alert, - Condition, - Annotations: Annotations, - Target: Target, - publish: publish, - generateGraphId: generateGraphId, - configure: config.configure, -}; +namespace _dashgen { + export type Dashboard = _Dashboard; + export const Dashboard = _Dashboard; + export type Row = _Row; + export const Row = _Row; + export type ExternalLink = _ExternalLink; + export const ExternalLink = _ExternalLink; + export const Panels = _Panels; + export const Templates = _Templates; + export type Alert = _Alert; + export const Alert = _Alert; + export type Condition = _Condition; + export const Condition = _Condition; + export const Annotations = _Annotations; + export type Target = _Target; + export const Target = _Target; + export const publish = _publish; + export const generateGraphId = _generateGraphId; + export const configure = config.configure; +} + +export = _dashgen; diff --git a/src/panels/graph.ts b/src/panels/graph.ts index b9a78af..15304f3 100644 --- a/src/panels/graph.ts +++ b/src/panels/graph.ts @@ -22,6 +22,7 @@ import generateGraphId = require('../id'); import type { GrafanaGraphPanel } from '../grafana'; import type Row from '../row'; import type Dashboard from '../dashboard'; +import type Alert from '../alert/alert'; type GraphPanelOptions = Partial< GrafanaGraphPanel & { @@ -111,19 +112,21 @@ class Graph { return this.state; } - addAlert(alert) { + addAlert(alert: Alert) { this.state.alert = alert.generate(); } - addTarget(target) { + addTarget(target: string & { hide: any }) { const refs = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'; const builtTarget = target.toString(); - const targetWithRefs = { - target: builtTarget, - hide: target.hide, - refId: refs[this._currentRefIndex++], - }; + const targetWithRefs: { target: string; hide: unknown; refId: string } = + { + target: builtTarget, + hide: target.hide, + // todo: can potentially run out of bounds + refId: refs[this._currentRefIndex++]!, + }; const targetFull = handleRefTargets(builtTarget, this.state.targets); const targetToAdd = Object.assign({}, targetWithRefs, targetFull); @@ -131,7 +134,7 @@ class Graph { } } -function getRefsFromTarget(target) { +function getRefsFromTarget(target: string) { const refMatchRegex = /.*?#(\w)[,)]/g; const refs = []; let matches; @@ -142,11 +145,21 @@ function getRefsFromTarget(target) { return refs; } -function handleRefTargets(target, targets) { +type TargetObject = { + target: string; + hide: unknown; + refId: string; + targetFull?: string | undefined; +}; + +function handleRefTargets( + target: string, + targets: TargetObject[] +): { targetFull?: string | undefined } { if (target.includes('#')) { const refs = getRefsFromTarget(target); - const findTargetByRefId = (targets, refId) => - targets.find((target) => target.refId === refId).target; + const findTargetByRefId = (targets: TargetObject[], refId: string) => + targets.find((target) => target.refId === refId)!.target; return { targetFull: refs.reduce( diff --git a/src/panels/index.ts b/src/panels/index.ts index f72ac2c..358325f 100644 --- a/src/panels/index.ts +++ b/src/panels/index.ts @@ -18,22 +18,23 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. -import Graph = require('./graph'); -import SingleStat = require('./singlestat'); -import Text = require('./text'); -import Table = require('./table'); -import DashboardList = require('./dashboard_list'); +import _Graph = require('./graph'); +import _SingleStat = require('./singlestat'); +import _Text = require('./text'); +import _Table = require('./table'); +import _DashboardList = require('./dashboard_list'); -const panels = { - Graph: Graph, - SingleStat: SingleStat, - Text: Text, - Table: Table, - DashboardList: DashboardList, -}; - -// eslint-disable-next-line @typescript-eslint/no-namespace namespace panels { + export type Graph = _Graph; + export const Graph = _Graph; + export type SingleStat = _SingleStat; + export const SingleStat = _SingleStat; + export type Text = _Text; + export const Text = _Text; + export type Table = _Table; + export const Table = _Table; + export type DashboardList = _DashboardList; + export const DashboardList = _DashboardList; export type Panel = Graph | SingleStat | Text | Table | DashboardList; } export = panels; diff --git a/src/templates/index.ts b/src/templates/index.ts index 5323a32..493e404 100644 --- a/src/templates/index.ts +++ b/src/templates/index.ts @@ -18,12 +18,14 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. -import Custom = require('./custom'); -import Query = require('./query'); +import _Custom = require('./custom'); +import _Query = require('./query'); -const templates = { - Custom: Custom, - Query: Query, -}; +namespace templates { + export type Custom = _Custom; + export const Custom = _Custom; + export type Query = _Query; + export const Query = _Query; +} export = templates; diff --git a/test/alert/condition.test.js b/test/alert/condition.test.js index b8bbfa6..4f2032e 100644 --- a/test/alert/condition.test.js +++ b/test/alert/condition.test.js @@ -22,6 +22,7 @@ test('condition .withEvaluator should accept array of values', () => { test('condition .witEvaluator should throw when passing a weird value', () => { const condition = new Condition(); + // @ts-expect-error intentionally bad input expect(() => condition.withEvaluator(2, 'bla')).toThrow(); }); @@ -33,6 +34,7 @@ test('condition .withOperator', () => { test('condition .withOperator should throw when passing invalid value', () => { const condition = new Condition(); + // @ts-expect-error intentionally bad input expect(() => condition.withOperator('bla')).toThrow(); }); @@ -64,6 +66,7 @@ test('condition should allow setting the query metric with .onQuery', () => { test('condition should throw when using .onQuery with a weird value', () => { const condition = new Condition(); + // @ts-expect-error intentionally bad input expect(() => condition.onQuery(2)).toThrow(); }); @@ -75,6 +78,7 @@ test('condition should allow choosing condition reducer type', () => { test('condition should throw when using .withReducer with a weird value', () => { const condition = new Condition(); + // @ts-expect-error intentionally bad input expect(() => condition.withReducer('bla')).toThrow(); }); @@ -85,6 +89,7 @@ test('override condition values from constructor', () => { type: 'max', }, }; + // @ts-expect-error type is not inferred correctly in js file const condition = new Condition(overrideReducer).generate(); expect(condition.reducer).toEqual({ diff --git a/tsconfig.json b/tsconfig.json index 2a565ca..b4c1d7c 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -15,8 +15,8 @@ // Other Outputs "sourceMap": true, - "declaration": false, - "declarationMap": false, + "declaration": true, + "declarationMap": true, // Stricter Typechecking Options "noUncheckedIndexedAccess": true,