Skip to content

Commit

Permalink
chore(atlas-service, indexes): implement automation agent request hel…
Browse files Browse the repository at this point in the history
…per, implement rolling indexes service COMPASS-8215 (#6236)

* chore(atlas-service, indexes): implement automation agent request helper, implement rolling indexes service

* chore(atlas-service, indexes): add tests and fix depcheck

* chore(web): adjust test to account for new prop

* chore(atlas-service): refactor automation agent types to clarify the response picking up logic

* chore(atlas-service, indexes): more type fixes
  • Loading branch information
gribnoysup authored Sep 18, 2024
1 parent e68050f commit 94538e6
Show file tree
Hide file tree
Showing 10 changed files with 431 additions and 10 deletions.
6 changes: 6 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions packages/atlas-service/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@
"@mongodb-js/compass-telemetry": "^1.1.7",
"@mongodb-js/compass-user-data": "^0.3.7",
"@mongodb-js/compass-utils": "^0.6.12",
"@mongodb-js/connection-info": "^0.8.0",
"@mongodb-js/devtools-connect": "^3.2.10",
"@mongodb-js/devtools-proxy-support": "^0.3.9",
"@mongodb-js/oidc-plugin": "^1.1.1",
Expand Down
53 changes: 44 additions & 9 deletions packages/atlas-service/src/atlas-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,12 @@ import {
} from './util';
import type { Logger } from '@mongodb-js/compass-logging';
import type { PreferencesAccess } from 'compass-preferences-model';
import type { AtlasClusterMetadata } from '@mongodb-js/connection-info';
import type {
AutomationAgentRequestTypes,
AutomationAgentResponse,
} from './make-automation-agent-op-request';
import { makeAutomationAgentOpRequest } from './make-automation-agent-op-request';

export type AtlasServiceOptions = {
defaultHeaders?: Record<string, string>;
Expand Down Expand Up @@ -35,19 +41,25 @@ export class AtlasService {
cloudEndpoint(path?: string): string {
return encodeURI(`${this.config.cloudBaseUrl}${path ? `/${path}` : ''}`);
}
regionalizedCloudEndpoint(
_atlasMetadata: Pick<AtlasClusterMetadata, 'regionalBaseUrl'>,
path?: string
): string {
// TODO: eventually should apply the regional url logic
// https://github.com/10gen/mms/blob/9f858bb987aac6aa80acfb86492dd74c89cbb862/client/packages/project/common/ajaxPrefilter.ts#L34-L49
return this.cloudEndpoint(path);
}
driverProxyEndpoint(path?: string): string {
return encodeURI(`${this.config.wsBaseUrl}${path ? `/${path}` : ''}`);
}
async fetch(url: RequestInfo, init?: RequestInit): Promise<Response> {
async fetch(url: RequestInfo | URL, init?: RequestInit): Promise<Response> {
throwIfNetworkTrafficDisabled(this.preferences);
throwIfAborted(init?.signal as AbortSignal);
this.logger.log.info(
this.logger.mongoLogId(1_001_000_297),
'AtlasService',
'Making a fetch',
{
url,
}
{ url }
);
try {
const res = await fetch(url, {
Expand All @@ -74,16 +86,13 @@ export class AtlasService {
this.logger.mongoLogId(1_001_000_298),
'AtlasService',
'Fetch errored',
{
url,
err,
}
{ url, err }
);
throw err;
}
}
async authenticatedFetch(
url: RequestInfo,
url: RequestInfo | URL,
init?: RequestInit
): Promise<Response> {
const authHeaders = await this.authService.getAuthHeaders();
Expand All @@ -95,4 +104,30 @@ export class AtlasService {
},
});
}
automationAgentFetch<OpType extends keyof AutomationAgentRequestTypes>(
atlasMetadata: Pick<
AtlasClusterMetadata,
'projectId' | 'clusterUniqueId' | 'regionalBaseUrl' | 'metricsType'
>,
opType: OpType,
opBody: Omit<
AutomationAgentRequestTypes[OpType],
'clusterId' | 'serverlessId'
>
): Promise<AutomationAgentResponse<OpType>> {
const opBodyClusterId =
atlasMetadata.metricsType === 'serverless'
? { serverlessId: atlasMetadata.clusterUniqueId }
: { clusterId: atlasMetadata.clusterUniqueId };
return makeAutomationAgentOpRequest(
this.authenticatedFetch.bind(this),
this.regionalizedCloudEndpoint(atlasMetadata),
atlasMetadata.projectId,
opType,
Object.assign(
opBodyClusterId,
opBody
) as AutomationAgentRequestTypes[OpType]
);
}
}
124 changes: 124 additions & 0 deletions packages/atlas-service/src/make-automation-agent-op-request.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
import Sinon from 'sinon';
import { makeAutomationAgentOpRequest } from './make-automation-agent-op-request';
import { expect } from 'chai';

describe('makeAutomationAgentOpRequest', function () {
const successSpecs = [
[
'succeeds if backend returned requestId and response',
{ _id: 'abc', requestType: 'listIndexStats' },
{
_id: 'abc',
requestType: 'listIndexStats',
response: [{ indexName: 'test' }],
},
],
] as const;

const failSpecs = [
[
'fails if initial request fails',
new Error('NetworkError'),
{},
/NetworkError/,
],
[
'fails if await response fails',
{ _id: 'abc', requestType: 'listIndexStats' },
new Error('NetworkError'),
/NetworkError/,
],
[
'fails if backend did not return requestId',
{},
{},
/Got unexpected backend response/,
],
[
'fails if backend returned requestId but no response',
{ _id: 'abc', requestType: 'listIndexStats' },
{},
/Got unexpected backend response/,
],
] as const;

function getMockFetch(
requestResponse: Record<string, unknown> | Error,
awaitResponse: Record<string, unknown> | Error
) {
return Sinon.stub()
.onFirstCall()
.callsFake(() => {
return requestResponse instanceof Error
? Promise.reject(requestResponse)
: Promise.resolve({
ok: true,
staus: 200,
json() {
return Promise.resolve(requestResponse);
},
});
})
.onSecondCall()
.callsFake(() => {
return awaitResponse instanceof Error
? Promise.reject(awaitResponse)
: Promise.resolve({
ok: true,
staus: 200,
json() {
return Promise.resolve(awaitResponse);
},
});
});
}

function getRequestBodyFromFnCall(call: Sinon.SinonSpyCall<any, any>) {
return JSON.parse(call.args[1].body);
}

for (const [
successSpecName,
requestResponse,
awaitResponse,
] of successSpecs) {
it(successSpecName, async function () {
const fetchFn = getMockFetch(requestResponse, awaitResponse);
const res = await makeAutomationAgentOpRequest(
fetchFn,
'http://example.com',
'abc',
'listIndexStats',
{ clusterId: 'abc', db: 'db', collection: 'coll' }
);
expect(getRequestBodyFromFnCall(fetchFn.firstCall)).to.deep.eq({
clusterId: 'abc',
collection: 'coll',
db: 'db',
});
expect(res).to.deep.eq(awaitResponse.response);
});
}

for (const [
failSpecName,
requestResponse,
awaitResponse,
errorMessage,
] of failSpecs) {
it(failSpecName, async function () {
try {
await makeAutomationAgentOpRequest(
getMockFetch(requestResponse, awaitResponse),
'http://example.com',
'abc',
'listIndexStats',
{ clusterId: 'abc', db: 'db', collection: 'coll' }
);
expect.fail('Expected makeAutomationAgentOpRequest call to fail');
} catch (err) {
expect((err as any).message).to.match(errorMessage);
}
});
}
});
Loading

0 comments on commit 94538e6

Please sign in to comment.