Skip to content

Commit

Permalink
Merge pull request #276 from atlassian/ARC-2288-add-metrics-to-requests
Browse files Browse the repository at this point in the history
ARC-2288 add metrics and update tests
  • Loading branch information
rachellerathbone authored Feb 16, 2024
2 parents 31058fe + 43100c0 commit 0e5af8a
Show file tree
Hide file tree
Showing 17 changed files with 375 additions and 161 deletions.
2 changes: 1 addition & 1 deletion app/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
"dependencies": {
"@atlassiansox/analytics-node-client": "^4.1.1",
"@forge/api": "3.0.0",
"@forge/metrics": "0.2.0",
"@forge/metrics": "^0.2.2",
"@forge/resolver": "1.5.14",
"atlassian-jwt": "^2.0.2",
"crypto": "^1.0.1",
Expand Down
3 changes: 3 additions & 0 deletions app/src/check-permissions.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import api, { route } from '@forge/api';
import { internalMetrics } from '@forge/metrics';
import { metricFailedRequests } from './common/metric-names';

// Forge does not export their Request type, so we have to resort to 'any' for now.
export const adminPermissionCheck = async (req: any): Promise<void> => {
Expand All @@ -21,6 +23,7 @@ export const adminPermissionCheck = async (req: any): Promise<void> => {
});

if (permissions.status === 403) {
internalMetrics.counter(metricFailedRequests.notJiraAdminError).incr();
throw new Error('Only Jira administrators can access the Jenkins for Jira admin page.');
}

Expand Down
27 changes: 26 additions & 1 deletion app/src/common/metric-names.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,5 +13,30 @@ export const metricResolverEmitter = {

export const metricFailedRequests = {
sendEventToJiraUnauthorizedError: `${server}.sendEventToJiraUnauthorized.failure`,
sendEventToJiraError: `${server}.sendEventToJira.failure`
sendEventToJiraError: `${server}.sendEventToJira.failure`,
notJiraAdminError: `${server}.error.not-jira-admin`,
jenkinsAppError: `${server}.error.jenkins-app-error`,
unexpectedWebTriggerError: `${server}.error.upexpected-webtrigger-error`,
resetJenkinsRequestNoUuidError: `${server}.error.reset-jenkins-request-no-uuid`,
unsupportedResetJenkinsRequestError: `${server}.error.reset-jenkins-request-unsupported-request`,
unexpectedResetJenkinsRequestError: `${server}.error.reset-jenkins-request-unexpected-request`,
resetJenkinsServerInvocationError: `${server}.error.reset-jenkins-server-invocation-error`,
deleteBuildsAndDeploymentsError: `${server}.error.delete-builds-and-deployments-error`,
connectJenkinsServerError: `${server}.error.connect-jenkins--server-error`,
disconnectJenkinsServerError: `${server}.error.disconnect-jenkins-server-error`,
getAllJenkinsServerError: `${server}.error.get-all-jenkins-server-error`,
getJenkinsServerWithSecretError: `${server}.error.get-jenkins-server-with-secret-error`,
updateJenkinsServerError: `${server}.error.udpate-jenkins-server-error`,
updateJenkinsServerStateError: `${server}.error.udpate-jenkins-server-state-error`
};

export const metricSuccessfulRequests = {
resetJenkinsRequest: `${server}.success.reset-jenkins-request`,
resetJenkinsServerInvocation: `${server}.error.reset-jenkins-server-invocation`,
deleteBuildsAndDeployments: `${server}.error.delete-builds-and-deployments`,
connectJenkinsServer: `${server}.success.connect-jenkins-server`,
disconnectJenkinsServer: `${server}.success.disconnect-jenkins-server`,
getAllJenkinsServer: `${server}.success.get-all-jenkins-server`,
getJenkinsServerWithSecret: `${server}.success.get-jenkins-server-with-secret`,
updateJenkinsServer: `${server}.error.udpate-jenkins-server`
};
35 changes: 33 additions & 2 deletions app/src/storage/connect-jenkins-server.test.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,42 @@
import { JenkinsServer } from '../common/types';
import { connectJenkinsServer } from './connect-jenkins-server';

jest.mock('@forge/api', () => {
return {
__getRuntime: jest.fn(),
storage: {
set: jest.fn(),
setSecret: jest.fn()
}
};
});

jest.mock('@forge/metrics', () => {
const incr = jest.fn();
const counter = jest.fn(() => ({ incr }));

return {
__esModule: true,
default: {
internalMetrics: {
counter
}
},
internalMetrics: {
counter
}
};
});

describe('Connect Jenkins Server Suite', () => {
afterEach(() => {
jest.clearAllMocks();
});

it('Should store Jenkins server configuration to Forge Storage', async () => {
const jenkinsServer: JenkinsServer = {
name: 'test-jenkins-sever',
uuid: 'unique-uid',
name: 'test-jenkins-server',
uuid: 'unique-uuid',
secret: 'secret!!!',
pipelines: []
};
Expand Down
11 changes: 9 additions & 2 deletions app/src/storage/connect-jenkins-server.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,18 @@
import { storage } from '@forge/api';
import { internalMetrics } from '@forge/metrics';
import { JenkinsServer } from '../common/types';
import { SECRET_STORAGE_KEY_PREFIX, SERVER_STORAGE_KEY_PREFIX } from './constants';
import { JenkinsServerStorageError } from '../common/error';
import { Logger } from '../config/logger';
import { sendAnalytics } from '../analytics/analytics-client';
import { AnalyticsTrackEventsEnum } from '../analytics/analytics-events';
import { metricFailedRequests, metricSuccessfulRequests } from '../common/metric-names';

// eslint-disable-next-line max-len
const connectJenkinsServer = async (jenkinsServer: JenkinsServer, cloudId: string, accountId: string): Promise<boolean> => {
const connectJenkinsServer = async (
jenkinsServer: JenkinsServer,
cloudId: string,
accountId: string
): Promise<boolean> => {
const logger = Logger.getInstance('connectJenkinsServer');

try {
Expand All @@ -18,10 +23,12 @@ const connectJenkinsServer = async (jenkinsServer: JenkinsServer, cloudId: strin
await storage.setSecret(`${SECRET_STORAGE_KEY_PREFIX}${uuid}`, secret);

logger.info('Jenkins server configuration saved successfully!', { uuid });
internalMetrics.counter(metricSuccessfulRequests.connectJenkinsServer).incr();
await sendConnectAnalytics(cloudId, accountId);
return true;
} catch (error) {
logger.error('Failed to store Jenkins server configuration', { error });
internalMetrics.counter(metricFailedRequests.connectJenkinsServerError).incr();
throw new JenkinsServerStorageError('Failed to store jenkins server configuration');
}
};
Expand Down
33 changes: 32 additions & 1 deletion app/src/storage/disconnect-jenkins-server.test.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,39 @@
import { disconnectJenkinsServer } from './disconnect-jenkins-server';

jest.mock('@forge/api', () => {
return {
__getRuntime: jest.fn(),
storage: {
delete: jest.fn(),
deleteSecret: jest.fn()
}
};
});

jest.mock('@forge/metrics', () => {
const incr = jest.fn();
const counter = jest.fn(() => ({ incr }));

return {
__esModule: true,
default: {
internalMetrics: {
counter
}
},
internalMetrics: {
counter
}
};
});

describe('Delete Jenkins Server Suite', () => {
afterEach(() => {
jest.clearAllMocks();
});

it('Should delete Jenkins server configuration from Forge Storage', async () => {
const result = await disconnectJenkinsServer('test-uid', 'cloudId', 'accountId');
const result = await disconnectJenkinsServer('test-uuid', 'cloudId', 'accountId');
expect(result).toBe(true);
});
});
5 changes: 4 additions & 1 deletion app/src/storage/disconnect-jenkins-server.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import { storage } from '@forge/api';
import { internalMetrics } from '@forge/metrics';
import { SECRET_STORAGE_KEY_PREFIX, SERVER_STORAGE_KEY_PREFIX } from './constants';
import { JenkinsServerStorageError } from '../common/error';
import { Logger } from '../config/logger';
import { sendAnalytics } from '../analytics/analytics-client';
import { AnalyticsTrackEventsEnum } from '../analytics/analytics-events';
import { metricFailedRequests, metricSuccessfulRequests } from '../common/metric-names';

export const disconnectJenkinsServer = async (uuid: string, cloudId: string, accountId: string): Promise<boolean> => {
const logger = Logger.getInstance('disconnectJenkinsServer');
Expand All @@ -13,11 +15,12 @@ export const disconnectJenkinsServer = async (uuid: string, cloudId: string, acc
const deleteSecretPromise = await storage.deleteSecret(`${SECRET_STORAGE_KEY_PREFIX}${uuid}`);
await Promise.all([deleteJenkinsServerPromise, deleteSecretPromise]);
logger.info('Jenkins server successfully disconnected!', { uuid });
internalMetrics.counter(metricSuccessfulRequests.disconnectJenkinsServer).incr();
await sendDisconnectAnalytics(cloudId, accountId);

return true;
} catch (error) {
logger.error('Failed to delete jenkins server configuration', { error });
internalMetrics.counter(metricFailedRequests.disconnectJenkinsServerError).incr();
throw new JenkinsServerStorageError('Failed to delete jenkins server configuration');
}
};
Expand Down
31 changes: 22 additions & 9 deletions app/src/storage/get-all-jenkins-servers.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,21 +5,30 @@ import { JenkinsServer } from '../common/types';
import { getAllJenkinsServers } from './get-all-jenkins-servers';

jest.mock('@forge/api', () => ({
__getRuntime: jest.fn(),
storage: {
get: jest.fn(),
set: jest.fn(),
setSecret: jest.fn(),
getSecret: jest.fn(),
delete: jest.fn(),
deleteSecret: jest.fn(),
query: jest.fn(() => new MockQueryBuilder())
},
webTrigger: {
getUrl: jest.fn()
},
startsWith: jest.fn()
}));

jest.mock('@forge/metrics', () => {
const incr = jest.fn();
const counter = jest.fn(() => ({ incr }));

return {
__esModule: true,
default: {
internalMetrics: {
counter
}
},
internalMetrics: {
counter
}
};
});

class MockQueryBuilder implements QueryBuilder {
limit(): QueryBuilder {
return this;
Expand Down Expand Up @@ -67,6 +76,10 @@ const mockListResult: ListResult = {
};

describe('Get All Jenkins Servers Suite', () => {
afterEach(() => {
jest.clearAllMocks();
});

it('Should return all Jenkins servers sorted by last event timestamp', async () => {
const allJenkinsServers: JenkinsServer[] = await getAllJenkinsServers();

Expand Down
4 changes: 4 additions & 0 deletions app/src/storage/get-all-jenkins-servers.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import {
startsWith, storage, ListResult, Result
} from '@forge/api';
import { internalMetrics } from '@forge/metrics';
import { JenkinsPipeline, JenkinsServer } from '../common/types';
import { SERVER_STORAGE_KEY_PREFIX } from './constants';
import { Logger } from '../config/logger';
import { metricFailedRequests, metricSuccessfulRequests } from '../common/metric-names';

async function getAllJenkinsServers(): Promise<JenkinsServer[]> {
const logger = Logger.getInstance('getAllJenkinsServers');
Expand All @@ -20,9 +22,11 @@ async function getAllJenkinsServers(): Promise<JenkinsServer[]> {
}

const jenkinsServers = transformToJenkinsServers(results);
internalMetrics.counter(metricSuccessfulRequests.getAllJenkinsServer).incr();
return jenkinsServers;
} catch (error) {
logger.error('Failed to fetch Jenkins server list', { error });
internalMetrics.counter(metricFailedRequests.getAllJenkinsServerError).incr();
throw error;
}
}
Expand Down
4 changes: 4 additions & 0 deletions app/src/storage/get-jenkins-server-with-secret.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import { storage } from '@forge/api';
import { internalMetrics } from '@forge/metrics';
import { getJenkinsServerSecret } from './get-jenkins-server-secret';
import { SERVER_STORAGE_KEY_PREFIX } from './constants';
import { JenkinsServer } from '../common/types';
import { NoJenkinsServerError } from '../common/error';
import { Logger } from '../config/logger';
import { metricFailedRequests, metricSuccessfulRequests } from '../common/metric-names';

const getJenkinsServerWithSecret = async (jenkinsServerUuid: string): Promise<JenkinsServer> => {
const logger = Logger.getInstance('getJenkinsServerWithSecret');
Expand All @@ -17,6 +19,7 @@ const getJenkinsServerWithSecret = async (jenkinsServerUuid: string): Promise<Je
}

const secret = await getJenkinsServerSecret(jenkinsServerUuid);
internalMetrics.counter(metricSuccessfulRequests.getJenkinsServerWithSecret).incr();

return {
...jenkinsServer,
Expand All @@ -25,6 +28,7 @@ const getJenkinsServerWithSecret = async (jenkinsServerUuid: string): Promise<Je
} catch (error) {
const errorMsg = `Failed to fetch Jenkins server for uuid ${jenkinsServerUuid}`;
logger.error(errorMsg);
internalMetrics.counter(metricFailedRequests.getJenkinsServerWithSecretError).incr();
throw new NoJenkinsServerError(errorMsg);
}
};
Expand Down
Loading

0 comments on commit 0e5af8a

Please sign in to comment.