Skip to content

Commit cb805e5

Browse files
committed
feat: remote tracknig with UT
1 parent 6c2ebb4 commit cb805e5

File tree

6 files changed

+172
-172
lines changed

6 files changed

+172
-172
lines changed

messages/source.json

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{
2+
"NonSourceTrackedOrgError": "This command can only be used on orgs that have source tracking enabled, such as sandboxes and scratch orgs."
3+
}

package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@
8080
"nyc": "^15.1.0",
8181
"prettier": "^2.3.2",
8282
"pretty-quick": "^3.1.1",
83-
"sinon": "^11.1.2",
83+
"sinon": "^10.0.0",
8484
"ts-node": "^10.1.0",
8585
"typescript": "^4.3.5"
8686
},

src/shared/remoteSourceTrackingService.ts

+52-56
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,10 @@
99

1010
import * as path from 'path';
1111
import { join as pathJoin } from 'path';
12-
import { ConfigFile, Connection, fs, Logger, Org, SfdxError } from '@salesforce/core';
12+
import { ConfigFile, fs, Logger, Org, SfdxError, Messages } from '@salesforce/core';
1313
import { Dictionary, Optional } from '@salesforce/ts-types';
14-
import { Duration, env, sleep, toNumber } from '@salesforce/kit';
14+
import { Duration, env, toNumber } from '@salesforce/kit';
15+
import { retryDecorator } from 'ts-retry-promise';
1516

1617
export type MemberRevision = {
1718
serverRevisionCounter: number;
@@ -33,6 +34,11 @@ export type ChangeElement = {
3334
deleted?: boolean;
3435
};
3536

37+
interface Contents {
38+
serverMaxRevisionCounter: number;
39+
sourceMembers: Dictionary<MemberRevision>;
40+
}
41+
3642
export namespace RemoteSourceTrackingService {
3743
// Constructor Options for RemoteSourceTrackingService.
3844
export interface Options extends ConfigFile.Options {
@@ -84,14 +90,7 @@ export class RemoteSourceTrackingService extends ConfigFile<RemoteSourceTracking
8490
private static remoteSourceTrackingServiceDictionary: Dictionary<RemoteSourceTrackingService> = {};
8591
protected logger!: Logger;
8692
private org!: Org;
87-
private readonly FIRST_REVISION_COUNTER_API_VERSION: string = '47.0';
88-
private conn!: Connection;
89-
private currentApiVersion!: string;
9093
private isSourceTrackedOrg = true;
91-
protected contents!: {
92-
serverMaxRevisionCounter: number;
93-
sourceMembers: { [key: string]: MemberRevision };
94-
};
9594

9695
// A short term cache (within the same process) of query results based on a revision.
9796
// Useful for source:pull, which makes 3 of the same queries; during status, building manifests, after pull success.
@@ -111,7 +110,7 @@ export class RemoteSourceTrackingService extends ConfigFile<RemoteSourceTracking
111110
if (!this.remoteSourceTrackingServiceDictionary[options.username]) {
112111
this.remoteSourceTrackingServiceDictionary[options.username] = await RemoteSourceTrackingService.create(options);
113112
}
114-
return this.remoteSourceTrackingServiceDictionary[options.username];
113+
return this.remoteSourceTrackingServiceDictionary[options.username] as RemoteSourceTrackingService;
115114
}
116115

117116
/**
@@ -132,8 +131,6 @@ export class RemoteSourceTrackingService extends ConfigFile<RemoteSourceTracking
132131
this.options.filename = RemoteSourceTrackingService.getFileName();
133132
this.org = await Org.create({ aliasOrUsername: this.options.username });
134133
this.logger = await Logger.child(this.constructor.name);
135-
this.conn = this.org.getConnection();
136-
this.currentApiVersion = this.conn.getApiVersion();
137134

138135
try {
139136
await super.init();
@@ -162,7 +159,7 @@ export class RemoteSourceTrackingService extends ConfigFile<RemoteSourceTracking
162159
// for SourceMembers. If a certain error is thrown during the query we won't try to do
163160
// source tracking for this org. Calling querySourceMembersFrom() has the extra benefit
164161
// of caching the query so we don't have to make an identical request in the same process.
165-
await this.querySourceMembersFrom(0);
162+
await this.querySourceMembersFrom({ fromRevision: 0 });
166163

167164
this.logger.debug('Initializing source tracking state');
168165
contents.serverMaxRevisionCounter = 0;
@@ -254,7 +251,7 @@ export class RemoteSourceTrackingService extends ConfigFile<RemoteSourceTracking
254251
if (toRevision != null) {
255252
members = await this.querySourceMembersTo(toRevision);
256253
} else {
257-
members = await this.querySourceMembersFrom(0);
254+
members = await this.querySourceMembersFrom({ fromRevision: 0 });
258255
}
259256

260257
await this.trackSourceMembers(members, true);
@@ -273,19 +270,19 @@ export class RemoteSourceTrackingService extends ConfigFile<RemoteSourceTracking
273270
}
274271

275272
private getServerMaxRevision(): number {
276-
return this['contents'].serverMaxRevisionCounter;
273+
return (this['contents'] as Contents).serverMaxRevisionCounter;
277274
}
278275

279276
private setServerMaxRevision(revision = 0): void {
280-
this['contents'].serverMaxRevisionCounter = revision;
277+
(this['contents'] as Contents).serverMaxRevisionCounter = revision;
281278
}
282279

283280
private getSourceMembers(): Dictionary<MemberRevision> {
284-
return this['contents'].sourceMembers;
281+
return (this['contents'] as Contents).sourceMembers;
285282
}
286283

287284
private initSourceMembers(): void {
288-
this['contents'].sourceMembers = {};
285+
(this['contents'] as Contents).sourceMembers = {};
289286
}
290287

291288
// Return a tracked element as MemberRevision data.
@@ -294,7 +291,7 @@ export class RemoteSourceTrackingService extends ConfigFile<RemoteSourceTracking
294291
}
295292

296293
private setMemberRevision(key: string, sourceMember: MemberRevision): void {
297-
this.getContents().sourceMembers[key] = sourceMember;
294+
(this.getContents() as unknown as Contents).sourceMembers[key] = sourceMember;
298295
}
299296

300297
// Adds the given SourceMembers to the list of tracked MemberRevisions, optionally updating
@@ -439,51 +436,52 @@ export class RemoteSourceTrackingService extends ConfigFile<RemoteSourceTracking
439436
this.logger.debug(`Computed SourceMember polling timeout of ${pollingTimeout}s`);
440437
}
441438

442-
const pollStartTime = Date.now();
443-
let pollEndTime: number;
444-
let totalPollTime = 0;
445439
const fromRevision = this.getServerMaxRevision();
446440
this.logger.debug(
447441
`Polling for ${memberNames.length} SourceMembers from revision ${fromRevision} with timeout of ${pollingTimeout}s`
448442
);
449443

450-
let pollAttempts = 1;
444+
let pollAttempts = 0;
451445

446+
// we need to keep asking the server for sourceMembers until time runs out OR we find everything in matches
452447
const matches = new Set(memberNames);
453-
454-
retry;
448+
let sourceMembers;
455449
const poll = async (): Promise<SourceMember[]> => {
456-
const allMembers = await this.querySourceMembersFrom(fromRevision, pollAttempts !== 1, false);
450+
pollAttempts += 1;
451+
sourceMembers = await this.querySourceMembersFrom({
452+
fromRevision,
453+
quiet: pollAttempts !== 1,
454+
useCache: false,
455+
});
457456

458-
for (const member of allMembers) {
457+
for (const member of sourceMembers) {
459458
matches.delete(member.MemberName);
460459
}
461460

462461
this.logger.debug(
463462
`[${pollAttempts}] Found ${memberNames.length - matches.size} of ${memberNames.length} SourceMembers`
464463
);
465-
pollEndTime = Date.now();
466-
totalPollTime = Math.round((pollEndTime - pollStartTime) / 1000) || 1;
467-
if (matches.size === 0 || totalPollTime >= pollingTimeout) {
468-
return allMembers;
464+
if (matches.size === 0) {
465+
return sourceMembers;
469466
}
470467

471468
if (matches.size < 20) {
472-
this.logger.debug(`Still looking for SourceMembers: ${[...matches].join(',')}`);
469+
this.logger.debug(`Still looking for SourceMembers: ${Array.from(matches).join(',')}`);
473470
}
474-
475-
await sleep(Duration.seconds(1));
476-
pollAttempts += 1;
477-
return poll();
471+
throw new Error();
478472
};
479-
const sourceMembers = await poll();
480-
481-
if (matches.size === 0) {
482-
this.logger.debug(`Retrieved all SourceMember data after ${totalPollTime}s and ${pollAttempts} attempts`);
483-
} else {
484-
this.logger.warn(`Polling for SourceMembers timed out after ${totalPollTime}s and ${pollAttempts} attempts`);
473+
const pollingFunction = retryDecorator(poll, {
474+
timeout: pollingTimeout * 1000,
475+
delay: 1000,
476+
retries: 'INFINITELY',
477+
});
478+
try {
479+
await pollingFunction();
480+
this.logger.debug(`Retrieved all SourceMember data after ${pollAttempts} attempts`);
481+
} catch {
482+
this.logger.warn(`Polling for SourceMembers timed out after ${pollAttempts} attempts`);
485483
if (matches.size < 51) {
486-
this.logger.debug(`Could not find ${matches.size} SourceMembers: ${[...matches].join(',')}`);
484+
this.logger.debug(`Could not find ${matches.size} SourceMembers: ${Array.from(matches).join(',')}`);
487485
} else {
488486
this.logger.debug(`Could not find SourceMembers for ${matches.size} components`);
489487
}
@@ -497,7 +495,11 @@ export class RemoteSourceTrackingService extends ConfigFile<RemoteSourceTracking
497495
await this.trackSourceMembers(sourceMembers, true);
498496
}
499497

500-
private async querySourceMembersFrom(fromRevision?: number, quiet = false, useCache = true): Promise<SourceMember[]> {
498+
private async querySourceMembersFrom({
499+
fromRevision,
500+
quiet = false,
501+
useCache = true,
502+
}: { fromRevision?: number; quiet?: boolean; useCache?: boolean } = {}): Promise<SourceMember[]> {
501503
const rev = fromRevision != null ? fromRevision : this.getServerMaxRevision();
502504

503505
if (useCache) {
@@ -519,27 +521,21 @@ export class RemoteSourceTrackingService extends ConfigFile<RemoteSourceTracking
519521

520522
private async querySourceMembersTo(toRevision: number, quiet = false): Promise<SourceMember[]> {
521523
const query = `SELECT MemberType, MemberName, IsNameObsolete, RevisionCounter FROM SourceMember WHERE RevisionCounter <= ${toRevision}`;
522-
return this.query(query, quiet);
524+
return this.query<SourceMember>(query, quiet);
523525
}
524526

525527
private async query<T>(query: string, quiet = false): Promise<T[]> {
526-
// to switch to using RevisionCounter - apiVersion > 46.0
527-
// set the api version of the connection to 47.0, query, revert api version
528528
if (!this.isSourceTrackedOrg) {
529-
throw SfdxError.create('salesforce-alm', 'source', 'NonSourceTrackedOrgError');
529+
Messages.importMessagesDirectory(__dirname);
530+
this.messages = Messages.loadMessages('@salesforce/source-tracking', 'source');
531+
throw SfdxError.create('@salesforce/source-tracking', 'source', 'NonSourceTrackedOrgError');
530532
}
531533
if (!quiet) {
532534
this.logger.debug(query);
533535
}
534536

535-
let results;
536-
if (parseFloat(this.currentApiVersion) < parseFloat(this.FIRST_REVISION_COUNTER_API_VERSION)) {
537-
this.conn.setApiVersion(this.FIRST_REVISION_COUNTER_API_VERSION);
538-
results = await this.conn.tooling.autoFetchQuery<T>(query);
539-
this.conn.setApiVersion(this.currentApiVersion);
540-
} else {
541-
results = await this.conn.tooling.autoFetchQuery<T>(query);
542-
}
537+
const results = await this.org.getConnection().tooling.autoFetchQuery<T>(query);
538+
543539
return results.records;
544540
}
545541
}

test/nuts/scenario.nut.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import { TestSession } from '@salesforce/cli-plugins-testkit';
1010
import { fs } from '@salesforce/core';
1111
import { expect } from 'chai';
1212
import { shouldThrow } from '@salesforce/core/lib/testSetup';
13-
import { ShadowRepo } from '../../src/shared/repo';
13+
import { ShadowRepo } from '../../src/shared/localShadowRepo';
1414

1515
describe('end-to-end-test', () => {
1616
let session: TestSession;

0 commit comments

Comments
 (0)