Skip to content

Commit

Permalink
fix: add a did you mean action to AuthInfo
Browse files Browse the repository at this point in the history
  • Loading branch information
WillieRuemmele committed Jun 5, 2024
1 parent f8876d8 commit f7328a2
Show file tree
Hide file tree
Showing 6 changed files with 96 additions and 3 deletions.
4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -53,11 +53,12 @@
],
"dependencies": {
"@jsforce/jsforce-node": "^3.2.0",
"@salesforce/kit": "^3.1.1",
"@salesforce/kit": "^3.1.2",
"@salesforce/schemas": "^1.9.0",
"@salesforce/ts-types": "^2.0.9",
"ajv": "^8.15.0",
"change-case": "^4.1.2",
"fast-levenshtein": "^3.0.0",
"faye": "^1.4.0",
"form-data": "^4.0.0",
"js2xmlparser": "^4.0.1",
Expand All @@ -75,6 +76,7 @@
"@salesforce/ts-sinon": "^1.4.19",
"@types/benchmark": "^2.1.5",
"@types/chai-string": "^1.4.5",
"@types/fast-levenshtein": "^0.0.4",
"@types/jsonwebtoken": "9.0.6",
"@types/proper-lockfile": "^4.1.4",
"@types/semver": "^7.5.8",
Expand Down
12 changes: 11 additions & 1 deletion src/org/authInfo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ import { StateAggregator } from '../stateAggregator/stateAggregator';
import { filterSecrets } from '../logger/filters';
import { Messages } from '../messages';
import { getLoginAudienceCombos, SfdcUrl } from '../util/sfdcUrl';
import { findSuggestion } from '../util/findSuggestion';
import { Connection, SFDX_HTTP_HEADERS } from './connection';
import { OrgConfigProperties } from './orgConfigProperties';
import { Org, SandboxFields } from './org';
Expand Down Expand Up @@ -813,7 +814,16 @@ export class AuthInfo extends AsyncOptionalCreatable<AuthInfo.Options> {
}
// If a username with NO oauth options, ensure authorization already exist.
else if (username && !authOptions && !(await this.stateAggregator.orgs.exists(username))) {
throw messages.createError('namedOrgNotFound', [username]);
throw SfError.create({
name: 'NamedOrgNotFoundError',
message: messages.getMessage('namedOrgNotFound', [username]),
actions: [
`It looks like you mistyped the username or alias. Did you mean "${findSuggestion(username, [
...(await this.stateAggregator.orgs.list()).map((f) => f.split('.json')[0]),
...Object.keys(this.stateAggregator.aliases.getAll()),
])}"?`,
],
});
} else {
await this.initAuthOptions(authOptions);
}
Expand Down
25 changes: 25 additions & 0 deletions src/util/findSuggestion.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
/*
* Copyright (c) 2023, salesforce.com, inc.
* All rights reserved.
* Licensed under the BSD 3-Clause license.
* For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause
*/
import Levenshtein from 'fast-levenshtein';

/**
* From the haystack, will find the closest value to the needle
*
* @param needle - what the user provided - find results similar to this
* @param haystack - possible results to search against
*/
export const findSuggestion = (needle: string, haystack: string[]): string => {
// we'll use this array to keep track of which piece of hay is the closest to the users entered value.
// keys closer to the index 0 will be a closer guess than keys indexed further from 0
// an entry at 0 would be a direct match, an entry at 1 would be a single character off, etc.
const index: string[] = [];
haystack.map((hay) => {
index[Levenshtein.get(needle, hay)] = hay;
});

return index.find((item) => item !== undefined) ?? '';
};
7 changes: 7 additions & 0 deletions test/unit/org/authInfoTest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ import { OrgAccessor } from '../../../src/stateAggregator/accessors/orgAccessor'
import { Crypto } from '../../../src/crypto/crypto';
import { Config } from '../../../src/config/config';
import { SfdcUrl } from '../../../src/util/sfdcUrl';
import * as suggestion from '../../../src/util/findSuggestion';
import { SfError } from '../../../src';

class AuthInfoMockOrg extends MockTestOrgData {
public privateKey = 'authInfoTest/jwt/server.key';
Expand Down Expand Up @@ -2083,11 +2085,16 @@ describe('AuthInfo No fs mock', () => {

it('invalid devhub username', async () => {
const expectedErrorName = 'NamedOrgNotFoundError';
stubMethod($$.SANDBOX, suggestion, 'findSuggestion').returns('[email protected]');
try {
await shouldThrow(AuthInfo.create({ username: '[email protected]', isDevHub: true }));
} catch (e) {
expect(e).to.have.property('name', expectedErrorName);
expect(e).to.have.property('message', 'No authorization information found for [email protected].');
expect(e).to.have.property('actions');
expect((e as SfError).actions).to.deep.equal([
'It looks like you mistyped the username or alias. Did you mean "[email protected]"?',
]);
}
});
});
32 changes: 32 additions & 0 deletions test/unit/util/findSuggestion.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
/*
* Copyright (c) 2023, salesforce.com, inc.
* All rights reserved.
* Licensed under the BSD 3-Clause license.
* For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause
*/
import { expect } from 'chai';
import { findSuggestion } from '../../../src/util/findSuggestion';

describe('findSuggestion Unit Tests', () => {
it('will find one suggestion', () => {
const res = findSuggestion('needl', ['haystack', 'needle']);
expect(res).to.equal('needle');
});

it('will return empty string when no haystack', () => {
const res = findSuggestion('needl', []);
expect(res).to.equal('');
});

it('will return last closest result', () => {
// j-k-l-m-n
// 'needl' should be right between 'needk' and 'needm' - but we found 'needm' last, which overwrites 'needk'
const res = findSuggestion('needl', ['needk', 'needm']);
expect(res).to.equal('needm');
});

it('will find closest result', () => {
const res = findSuggestion('a', ['z', 'x', 'y']);
expect(res).to.equal('y');
});
});
19 changes: 18 additions & 1 deletion yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -568,7 +568,7 @@
typescript "^5.4.3"
wireit "^0.14.4"

"@salesforce/kit@^3.1.1":
"@salesforce/kit@^3.1.2":
version "3.1.2"
resolved "https://registry.yarnpkg.com/@salesforce/kit/-/kit-3.1.2.tgz#270741c54c70969df19ef17f8979b4ef1fa664b2"
integrity sha512-si+ddvZDgx9q5czxAANuK5xhz3pv+KGspQy1wyia/7HDPKadA0QZkLTzUnO1Ju4Mux32CNHEb2y9lw9jj+eVTA==
Expand Down Expand Up @@ -696,6 +696,11 @@
resolved "https://registry.yarnpkg.com/@types/chai/-/chai-4.3.16.tgz#b1572967f0b8b60bf3f87fe1d854a5604ea70c82"
integrity sha512-PatH4iOdyh3MyWtmHVFXLWCCIhUbopaltqddG9BzB+gMIzee2MJrvd+jouii9Z3wzQJruGWAm7WOMjgfG8hQlQ==

"@types/fast-levenshtein@^0.0.4":
version "0.0.4"
resolved "https://registry.yarnpkg.com/@types/fast-levenshtein/-/fast-levenshtein-0.0.4.tgz#a16ff6607189edf08ac631e54b5774b0faf12d87"
integrity sha512-tkDveuitddQCxut1Db8eEFfMahTjOumTJGPHmT9E7KUH+DkVq9WTpVvlfenf3S+uCBeu8j5FP2xik/KfxOEjeA==

"@types/json-schema@^7.0.12":
version "7.0.15"
resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.15.tgz#596a1747233694d50f6ad8a7869fcb6f56cf5841"
Expand Down Expand Up @@ -2229,6 +2234,13 @@ fast-levenshtein@^2.0.6:
resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917"
integrity sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==

fast-levenshtein@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-3.0.0.tgz#37b899ae47e1090e40e3fd2318e4d5f0142ca912"
integrity sha512-hKKNajm46uNmTlhHSyZkmToAc56uZJwYq7yrciZjqOxnlfQwERDQJmHPUp7m1m9wx8vgOe8IaCKZ5Kv2k1DdCQ==
dependencies:
fastest-levenshtein "^1.0.7"

fast-redact@^3.1.1:
version "3.5.0"
resolved "https://registry.yarnpkg.com/fast-redact/-/fast-redact-3.5.0.tgz#e9ea02f7e57d0cd8438180083e93077e496285e4"
Expand All @@ -2244,6 +2256,11 @@ fast-uri@^2.3.0:
resolved "https://registry.yarnpkg.com/fast-uri/-/fast-uri-2.3.0.tgz#bdae493942483d299e7285dcb4627767d42e2793"
integrity sha512-eel5UKGn369gGEWOqBShmFJWfq/xSJvsgDzgLYC845GneayWvXBf0lJCBn5qTABfewy1ZDPoaR5OZCP+kssfuw==

fastest-levenshtein@^1.0.7:
version "1.0.16"
resolved "https://registry.yarnpkg.com/fastest-levenshtein/-/fastest-levenshtein-1.0.16.tgz#210e61b6ff181de91ea9b3d1b84fdedd47e034e5"
integrity sha512-eRnCtTTtGZFpQCwhJiUOuxPQWRXVKYDn0b2PeHfXL6/Zi53SLAzAHfVhVWK2AryC/WH05kGfxhFIPvTF0SXQzg==

fastq@^1.6.0:
version "1.17.1"
resolved "https://registry.yarnpkg.com/fastq/-/fastq-1.17.1.tgz#2a523f07a4e7b1e81a42b91b8bf2254107753b47"
Expand Down

3 comments on commit f7328a2

@svc-cli-bot
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Logger Benchmarks - ubuntu-latest

Benchmark suite Current: f7328a2 Previous: 3976f7c Ratio
Child logger creation 465974 ops/sec (±1.80%) 467835 ops/sec (±1.60%) 1.00
Logging a string on root logger 774476 ops/sec (±11.99%) 774722 ops/sec (±10.63%) 1.00
Logging an object on root logger 568889 ops/sec (±8.05%) 588904 ops/sec (±7.92%) 1.04
Logging an object with a message on root logger 7208 ops/sec (±204.90%) 4189 ops/sec (±217.41%) 0.58
Logging an object with a redacted prop on root logger 384994 ops/sec (±12.95%) 507288 ops/sec (±8.26%) 1.32
Logging a nested 3-level object on root logger 374888 ops/sec (±6.98%) 377042 ops/sec (±6.48%) 1.01

This comment was automatically generated by workflow using github-action-benchmark.

@svc-cli-bot
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Logger Benchmarks - windows-latest

Benchmark suite Current: f7328a2 Previous: 3976f7c Ratio
Child logger creation 328651 ops/sec (±1.10%) 324884 ops/sec (±1.12%) 0.99
Logging a string on root logger 867117 ops/sec (±4.46%) 699770 ops/sec (±5.36%) 0.81
Logging an object on root logger 575272 ops/sec (±5.87%) 613094 ops/sec (±5.76%) 1.07
Logging an object with a message on root logger 3522 ops/sec (±220.49%) 8108 ops/sec (±200.19%) 2.30
Logging an object with a redacted prop on root logger 462524 ops/sec (±6.80%) 459854 ops/sec (±7.39%) 0.99
Logging a nested 3-level object on root logger 323712 ops/sec (±4.96%) 321072 ops/sec (±4.45%) 0.99

This comment was automatically generated by workflow using github-action-benchmark.

@svc-cli-bot
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Performance Alert ⚠️

Possible performance regression was detected for benchmark 'Logger Benchmarks - windows-latest'.
Benchmark result of this commit is worse than the previous benchmark result exceeding threshold 2.

Benchmark suite Current: f7328a2 Previous: 3976f7c Ratio
Logging an object with a message on root logger 3522 ops/sec (±220.49%) 8108 ops/sec (±200.19%) 2.30

This comment was automatically generated by workflow using github-action-benchmark.

Please sign in to comment.