Skip to content

Commit

Permalink
Merge pull request #2583 from newrelic/tweak-artifact
Browse files Browse the repository at this point in the history
Updates to artifact to conform to service structs
  • Loading branch information
Andrew Anguiano authored Oct 17, 2024
2 parents 8988c6f + 0e28386 commit b3da59b
Show file tree
Hide file tree
Showing 9 changed files with 884 additions and 186 deletions.
127 changes: 50 additions & 77 deletions utils/__tests__/build-validate-quickstart-artifact.test.js
Original file line number Diff line number Diff line change
@@ -1,20 +1,36 @@
import * as fs from 'fs';
import * as path from 'path';
import Quickstart from '../lib/Quickstart';
import DataSource from '../lib/DataSource';
import Alert from '../lib/Alert';
import Dashboard from '../lib/Dashboard';

import {
getArtifactComponents,
getDataSourceIds,
validateArtifact,
} from '../build-validate-quickstart-artifact';

jest.mock('../lib/Quickstart');
jest.mock('../lib/DataSource');
jest.mock('../lib/Alert');
jest.mock('../lib/Dashboard');
jest.mock('fs');
const MOCK_FILES_BASEPATH = path.resolve(__dirname, '..', 'mock_files');

const mockQuickstart1 = new Quickstart(
'/quickstarts/mock-quickstart-1/config.yml',
MOCK_FILES_BASEPATH
);

const mockQuickstart2 = new Quickstart(
'quickstarts/mock-quickstart-2/config.yml',
MOCK_FILES_BASEPATH
);

const mockDataSource1 = new DataSource(
'test-data-source',
MOCK_FILES_BASEPATH
)

const mockAlert1 = new Alert(
'mock-alert-policy-1',
MOCK_FILES_BASEPATH
);

const mockDashboard1 = new Dashboard('mock-dashboard-1', MOCK_FILES_BASEPATH);

describe('built-validate-quickstart-artifact', () => {
beforeEach(() => {
Expand All @@ -26,58 +42,27 @@ describe('built-validate-quickstart-artifact', () => {
it('should find all of the components', () => {
Quickstart.getAll = jest
.fn()
.mockReturnValueOnce([
{ config: 'test-quickstart-1' },
{ config: 'test-quickstart-2' },
]);
.mockReturnValueOnce([mockQuickstart1, mockQuickstart2]);

DataSource.getAll = jest
.fn()
.mockReturnValueOnce([{ config: 'test-dataSource-1' }]);
DataSource.getAll = jest.fn().mockReturnValueOnce([mockDataSource1]);

Alert.getAll = jest
.fn()
.mockReturnValueOnce([{ config: 'test-alert-1' }]);
Dashboard.getAll = jest
.fn()
.mockReturnValueOnce([{ config: 'test-dashboard-1' }]);
Alert.getAll = jest.fn().mockReturnValueOnce([mockAlert1]);
Dashboard.getAll = jest.fn().mockReturnValueOnce([mockDashboard1]);

const actual = getArtifactComponents();

expect(actual.quickstarts).toHaveLength(2);
expect(actual.quickstarts[0]).toEqual('test-quickstart-1');
expect(actual.quickstarts[1]).toEqual('test-quickstart-2');
expect(actual.dataSources).toHaveLength(1);
expect(actual.dataSources[0]).toEqual('test-dataSource-1');
expect(actual.alerts).toHaveLength(1);
expect(actual.alerts[0]).toEqual('test-alert-1');
expect(actual.dashboards).toHaveLength(1);
expect(actual.dashboards[0]).toEqual('test-dashboard-1');
});
expect(actual.quickstarts[0].dashboards).toEqual([]);
expect(actual.quickstarts[1].dashboards).toEqual(['mock-dashboard-1']);

it('should produce a complete list of dataSource IDs', () => {
Quickstart.getAll = jest.fn().mockReturnValueOnce([]);
Alert.getAll = jest.fn().mockReturnValueOnce([]);
Dashboard.getAll = jest.fn().mockReturnValueOnce([]);
DataSource.getAll = jest
.fn()
.mockReturnValueOnce([
{ config: { id: 'community-1' } },
{ config: { id: 'community-2' } },
{ config: { id: 'community-3' } },
]);

const { dataSources } = getArtifactComponents();
fs.readFileSync.mockReturnValueOnce(JSON.stringify(['core-1', 'core-2']));
expect(actual.dataSources).toHaveLength(1);
expect(actual.dataSources[0].id).toEqual('test-data-source');

const actual = getDataSourceIds('dummy-file.json', dataSources);
expect(Object.keys(actual.alerts)).toHaveLength(1);
expect(Object.keys(actual.alerts)).toContain('mock-alert-policy-1');

expect(actual).toHaveLength(5);
expect(actual).toContain('community-1');
expect(actual).toContain('community-2');
expect(actual).toContain('community-3');
expect(actual).toContain('core-1');
expect(actual).toContain('core-2');
expect(Object.keys(actual.dashboards)).toHaveLength(1);
expect(Object.keys(actual.dashboards)).toContain('mock-dashboard-1');
});
});

Expand All @@ -86,8 +71,8 @@ describe('built-validate-quickstart-artifact', () => {
type: 'object',
properties: {
quickstarts: { type: 'array' },
alerts: { type: 'array' },
dashboards: { type: 'array' },
alerts: { type: 'object' },
dashboards: { type: 'object' },
dataSources: {
type: 'array',
items: {
Expand All @@ -109,20 +94,12 @@ describe('built-validate-quickstart-artifact', () => {
DataSource.getAll = jest
.fn()
.mockReturnValueOnce([
{ config: { id: 'community-1', title: 'DataSource 1' } },
{ config: { id: 'community-2', title: 'DataSource 2' } },
{ config: { id: 'community-3', title: 'DataSource 3' } },
mockDataSource1
]);

const components = getArtifactComponents();

fs.readFileSync.mockReturnValueOnce(JSON.stringify(['core-1', 'core-2']));
const dataSourceIds = getDataSourceIds(
'dummy-file.json',
components.dataSources
);

const artifact = { ...components, dataSourceIds };
const artifact = { ...components, dataSourceIds: ['core-1', 'core-2'] };

const actual = validateArtifact(TEST_SCHEMA, artifact);

Expand All @@ -133,23 +110,19 @@ describe('built-validate-quickstart-artifact', () => {
Quickstart.getAll = jest.fn().mockReturnValueOnce([]);
Alert.getAll = jest.fn().mockReturnValueOnce([]);
Dashboard.getAll = jest.fn().mockReturnValueOnce([]);
DataSource.getAll = jest
.fn()
.mockReturnValueOnce([
{ config: { id: 'community-1', title: 'DataSource 1' } },
{ config: { id: false, title: 'DataSource 2' } },
{ config: { id: 'community-3', title: 3 } },
]);
DataSource.getAll = jest.fn().mockReturnValueOnce([]);

const components = getArtifactComponents();

fs.readFileSync.mockReturnValueOnce(JSON.stringify(['core-1', 'core-2']));
const dataSourceIds = getDataSourceIds(
'dummy-file.json',
components.dataSources
);

const artifact = { ...components, dataSourceIds };
const artifact = {
...components,
dataSources: [
{ id: 'community-1', title: 'DataSource 1' },
{ id: false, title: 'DataSource 2' },
{ id: 'community-3', title: 3 },
],
dataSourceIds: ['core-1', 'core-2'],
};

const actual = validateArtifact(TEST_SCHEMA, artifact);

Expand Down
43 changes: 29 additions & 14 deletions utils/build-validate-quickstart-artifact.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,10 @@ import get from 'lodash/get';
import Quickstart from "./lib/Quickstart";
import DataSource from "./lib/DataSource";
import Alert from "./lib/Alert";
import Dashboard, { DashboardConfig } from "./lib/Dashboard";
import Dashboard from "./lib/Dashboard";
import Ajv, { type ErrorObject } from 'ajv';
import { QuickstartConfig, QuickstartConfigAlert } from './types/QuickstartConfig';
import { DataSourceConfig } from './types/DataSourceConfig';
import { passedProcessArguments } from './lib/helpers';
import { ArtifactDataSourceConfig, ArtifactDashboardConfig, ArtifactQuickstartConfig, ArtifactAlertConfig } from './types/Artifact';

type ArtifactSchema = Record<string, unknown>;

Expand All @@ -20,10 +19,10 @@ type InvalidItem = {
}

type ArtifactComponents = {
quickstarts: QuickstartConfig[],
dataSources: DataSourceConfig[],
alerts: QuickstartConfigAlert[][],
dashboards: DashboardConfig[]
quickstarts: ArtifactQuickstartConfig[],
dataSources: ArtifactDataSourceConfig[],
alerts: ArtifactAlertConfig,
dashboards: ArtifactDashboardConfig
}

type Artifact = ArtifactComponents | {
Expand All @@ -38,17 +37,33 @@ const getSchema = (filepath: string): ArtifactSchema => {

// NOTE: we could run these in parallel to speed up the script
export const getArtifactComponents = (): ArtifactComponents => {
const quickstarts = Quickstart.getAll().map((quickstart) => quickstart.config);
const quickstarts = Quickstart
.getAll()
.map((quickstart) => quickstart.transformForArtifact());

console.log(`[*] Found ${quickstarts.length} quickstarts`);

const dataSources = DataSource.getAll().map((dataSource) => dataSource.config);
const dataSources = DataSource
.getAll()
.map((dataSource) => dataSource.transformForArtifact());

console.log(`[*] Found ${dataSources.length} dataSources`);

const alerts = Alert.getAll().map((alert) => alert.config);
console.log(`[*] Found ${alerts.length} alerts`);
const alerts = Alert.getAll().reduce((acc, alert) => {
const conditions = alert.transformForArtifact()

return { ...acc, ...conditions }

}, {});

console.log(`[*] Found ${Object.keys(alerts).length} alerts`);

const dashboards = Dashboard.getAll().reduce((acc, dash) => {
const dashboard = dash.transformForArtifact()
return { ...acc, ...dashboard }

const dashboards = Dashboard.getAll().map((dashboard) => dashboard.config);
console.log(`[*] Found ${dashboards.length} dashboards`);
}, {});
console.log(`[*] Found ${Object.keys(dashboards).length} dashboards`);

return {
quickstarts,
Expand All @@ -58,7 +73,7 @@ export const getArtifactComponents = (): ArtifactComponents => {
}
};

export const getDataSourceIds = (filepath: string, communityDataSources: DataSourceConfig[]): string[] => {
export const getDataSourceIds = (filepath: string, communityDataSources: ArtifactComponents['dataSources']): string[] => {
const coreDataSourceIds = yaml.load(
fs.readFileSync(filepath).toString('utf8')
) as string[];
Expand Down
53 changes: 48 additions & 5 deletions utils/lib/Alert.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import type {
AlertType,
QuickstartAlertInput,
} from '../types/QuickstartMutationVariable';
import type { ArtifactAlertConfig, ArtifactAlertType } from '../types/Artifact';
import type { QuickstartConfigAlert } from '../types/QuickstartConfig';
import type { NerdGraphResponseWithLocalErrors } from '../types/nerdgraph';

Expand Down Expand Up @@ -79,7 +80,11 @@ export type SubmitSetRequiredDataSourcesMutationResult =
| NerdGraphResponseWithLocalErrors<AlertPolicySetRequiredDataSourcesMutationResults>
| { errors: ErrorOrNerdGraphError[] };

class Alert extends Component<QuickstartConfigAlert[], QuickstartAlertInput[]> {
class Alert extends Component<
QuickstartConfigAlert[],
QuickstartAlertInput[],
ArtifactAlertConfig
> {
constructor(identifier: string, basePath?: string) {
super(identifier, basePath);
this.isValid = this.validate();
Expand Down Expand Up @@ -134,6 +139,38 @@ class Alert extends Component<QuickstartConfigAlert[], QuickstartAlertInput[]> {
}
}

/**
* Method extracts criteria from the config and returns an object appropriately
* structured for the artifact.
*/
transformForArtifact() {
if (!this.isValid) {
console.error(
`Alert is invalid.\nPlease check if the path at ${this.identifier} exists.`
);
return {};
}

const alertPolicy = this.config.map((condition) => {
const { description, name, type } = condition;

return {
description: description && description.trim(),
displayName: name && name.trim(),
rawConfiguration: JSON.stringify(condition),
sourceUrl: Component.getAssetSourceUrl(this.configPath),
type: type && (type.trim().toLowerCase() as ArtifactAlertType),
};
});

return { [this.identifier]: alertPolicy };
}

/**
* Method creates mutation variables for a given Alert.
*
* @deprecated This function should be removed once we have finished our new build publishing pipeline
*/
getMutationVariables() {
if (!this.isValid) {
console.error(
Expand Down Expand Up @@ -190,13 +227,17 @@ class Alert extends Component<QuickstartConfigAlert[], QuickstartAlertInput[]> {

return true;
} catch (error) {
logger.error(`Alert Condition: Validaiton for ${displayName} failed with an error`);
logger.error(
`Alert Condition: Validaiton for ${displayName} failed with an error`
);

return false;
}
});

logger.debug(`Alert condition: Finished validation for alert at ${this.identifier}`);
logger.debug(
`Alert condition: Finished validation for alert at ${this.identifier}`
);

return validations.every(Boolean);
}
Expand Down Expand Up @@ -299,8 +340,10 @@ class Alert extends Component<QuickstartConfigAlert[], QuickstartAlertInput[]> {
}

static getAll() {
const alertPaths = glob.sync(path.join(__dirname, '..', '..', 'alert-policies', '**', '*.+(yml|yaml)'));
return alertPaths.map(alertPath => {
const alertPaths = glob.sync(
path.join(__dirname, '..', '..', 'alert-policies', '**', '*.+(yml|yaml)')
);
return alertPaths.map((alertPath) => {
// The identifier for alerts is the folder and the file name
// e.g. `node-js/HighCpuUtilization.yml`
const identifier = path.join(...alertPath.split('/').slice(-2, -1));
Expand Down
3 changes: 2 additions & 1 deletion utils/lib/Component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import * as fs from 'fs';
import * as yaml from 'js-yaml';

import { GITHUB_REPO_BASE_URL } from '../constants';
abstract class Component<ConfigType, MutationVariablesType> {
abstract class Component<ConfigType, MutationVariablesType, ArtifactType> {
public identifier: string; // Local path to the component. Ex: python/flask
public configPath: string; // Absolute path to the config file within the repository
public config: ConfigType;
Expand All @@ -23,6 +23,7 @@ abstract class Component<ConfigType, MutationVariablesType> {
abstract getConfigFilePath(): string;
abstract getConfigContent(): ConfigType;
abstract getMutationVariables(): MutationVariablesType;
abstract transformForArtifact(): ArtifactType;

get fullPath() {
return path.join(this.basePath, this.configPath);
Expand Down
Loading

0 comments on commit b3da59b

Please sign in to comment.