Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Updates to artifact to conform to service structs #2583

Merged
merged 25 commits into from
Oct 17, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
ca107fa
feat: Tweak artifact for better compatiblity (WIP)
Oct 2, 2024
235dfd1
WIP
Oct 4, 2024
8761b56
Merge branch 'main' of https://github.com/newrelic/newrelic-quickstar…
mickeyryan42 Oct 4, 2024
c258063
Merge https://github.com/newrelic/newrelic-quickstarts into tweak-art…
mickeyryan42 Oct 7, 2024
6f2c054
chore: Update dasbhoard artifact
josephgregoryii Oct 7, 2024
7c8f58d
Merge branch 'tweak-artifact' of https://github.com/newrelic/newrelic…
mickeyryan42 Oct 7, 2024
c3163e9
chore: Update dasbhoard artifact
josephgregoryii Oct 7, 2024
10e7560
chore: Resolve conflicts
mickeyryan42 Oct 7, 2024
db839fe
fix: Quickstart schema
josephgregoryii Oct 7, 2024
c9d1d6f
Merge branch 'tweak-artifact' of https://github.com/newrelic/newrelic…
mickeyryan42 Oct 8, 2024
1b1462b
feat: Update alert artifact and schema
josephgregoryii Oct 8, 2024
12bc1a5
feat: Update dashboard to reflrect alert schema
josephgregoryii Oct 8, 2024
8591bc7
Merge branch 'tweak-artifact' of https://github.com/newrelic/newrelic…
mickeyryan42 Oct 9, 2024
20eddc1
chore: Cleanup classes and add jsdocs
josephgregoryii Oct 9, 2024
be33c35
chore: formatting
josephgregoryii Oct 9, 2024
9f6149c
chore: Remove data source ids from schema artifact
josephgregoryii Oct 9, 2024
0c04dba
chore: Add public signature to class method
josephgregoryii Oct 9, 2024
324d2fa
Merge branch 'tweak-artifact' of https://github.com/newrelic/newrelic…
mickeyryan42 Oct 9, 2024
2169e87
test: Update tests
josephgregoryii Oct 9, 2024
bcddaae
feat: Update alertPolicies to alertConditions
mickeyryan42 Oct 11, 2024
13f3515
Merge branch 'tweak-artifact' of https://github.com/newrelic/newrelic…
mickeyryan42 Oct 11, 2024
1028159
fix: Change alert type to undercase
josephgregoryii Oct 14, 2024
ad0308c
chore: Comments and cleanup
josephgregoryii Oct 15, 2024
e2a8020
fix: Lowercase support level for quickstart artifact
josephgregoryii Oct 15, 2024
0e28386
fix: Change authors to be a list of strings
josephgregoryii Oct 16, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading