Skip to content

Commit

Permalink
Print spinners during backend interaction
Browse files Browse the repository at this point in the history
Currently, we provide no user feedback whilst we contact the P0 backend.
This makes it difficult for the user to distinguish between waiting on
a backend response and a hung process.

Use a yarn-style Unicode spinner to indicate that the process is still
live.
  • Loading branch information
nbrahms committed Oct 1, 2024
1 parent b2e854a commit 2d9a7cc
Show file tree
Hide file tree
Showing 4 changed files with 63 additions and 16 deletions.
6 changes: 3 additions & 3 deletions src/commands/kubeconfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ You should have received a copy of the GNU General Public License along with @p0
import { retryWithSleep } from "../common/retry";
import { authenticate } from "../drivers/auth";
import { guard } from "../drivers/firestore";
import { Ansi, print2 } from "../drivers/stdio";
import { AnsiSgr, print2 } from "../drivers/stdio";
import {
awsCloudAuth,
profileName,
Expand Down Expand Up @@ -153,8 +153,8 @@ const kubeconfigAction = async (

if (process.env.AWS_ACCESS_KEY_ID) {
print2(
`${Ansi.Yellow}Warning: AWS credentials were detected in your environment, which may cause kubectl errors. ` +
`To avoid issues, unset with \`unset AWS_ACCESS_KEY_ID\`.${Ansi.Reset}`
`${AnsiSgr.Yellow}Warning: AWS credentials were detected in your environment, which may cause kubectl errors. ` +
`To avoid issues, unset with \`unset AWS_ACCESS_KEY_ID\`.${AnsiSgr.Reset}`
);
}
};
Expand Down
14 changes: 7 additions & 7 deletions src/commands/ls.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ You should have received a copy of the GNU General Public License along with @p0
import { fetchCommand } from "../drivers/api";
import { authenticate } from "../drivers/auth";
import { guard } from "../drivers/firestore";
import { print2, print1, Ansi } from "../drivers/stdio";
import { print2, print1, AnsiSgr, spinUntil } from "../drivers/stdio";
import { max, orderBy } from "lodash";
import pluralize from "pluralize";
import yargs from "yargs";
Expand Down Expand Up @@ -53,10 +53,10 @@ const ls = async (
}>
) => {
const authn = await authenticate();
const data = await fetchCommand<LsResponse>(authn, args, [
"ls",
...args.arguments,
]);
const data = await spinUntil(
"Listing accessible resources",
fetchCommand<LsResponse>(authn, args, ["ls", ...args.arguments])
);
const allArguments = [...args._, ...args.arguments];

if (data && "ok" in data && data.ok) {
Expand Down Expand Up @@ -89,8 +89,8 @@ const ls = async (
isSameValue
? item.key
: maxLength > 30
? `${item.key}\n ${Ansi.Dim}${tagPart}${Ansi.Reset}`
: `${item.key.padEnd(maxLength)}${Ansi.Dim} - ${tagPart}${Ansi.Reset}`
? `${item.key}\n ${AnsiSgr.Dim}${tagPart}${AnsiSgr.Reset}`
: `${item.key.padEnd(maxLength)}${AnsiSgr.Dim} - ${tagPart}${AnsiSgr.Reset}`
}`
);
}
Expand Down
13 changes: 8 additions & 5 deletions src/commands/shared/request.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ You should have received a copy of the GNU General Public License along with @p0
import { fetchCommand } from "../../drivers/api";
import { authenticate } from "../../drivers/auth";
import { doc } from "../../drivers/firestore";
import { print2 } from "../../drivers/stdio";
import { print2, spinUntil } from "../../drivers/stdio";
import { Authn } from "../../types/identity";
import { PluginRequest, Request, RequestResponse } from "../../types/request";
import { onSnapshot } from "firebase/firestore";
Expand Down Expand Up @@ -99,10 +99,13 @@ export const request =
): Promise<RequestResponse<T> | undefined> => {
const resolvedAuthn = authn ?? (await authenticate());
const { userCredential } = resolvedAuthn;
const data = await fetchCommand<RequestResponse<T>>(resolvedAuthn, args, [
command,
...args.arguments,
]);
const data = await spinUntil(
"Requesting access",
fetchCommand<RequestResponse<T>>(resolvedAuthn, args, [
command,
...args.arguments,
])
);

if (data && "ok" in data && "message" in data && data.ok) {
const logMessage =
Expand Down
46 changes: 45 additions & 1 deletion src/drivers/stdio.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ You should have received a copy of the GNU General Public License along with @p0
* - Better testing
* - Later redirection / duplication
*/
import { sleep } from "../util";
import { mapValues } from "lodash";

/** Used to output machine-readable text to stdout
Expand All @@ -39,7 +40,50 @@ export function print2(message: any) {
const AnsiCodes = {
Reset: "00",
Dim: "02",
Green: "32",
Yellow: "33",
} as const;

export const Ansi = mapValues(AnsiCodes, (v) => `\u001b[${v}m`);
const Ansi = (value: string) => `\u001b[${value}`;

/** Creates an ANSI Select Graphic Rendition code */
export const AnsiSgr = mapValues(AnsiCodes, (v) => Ansi(v + "m"));

/** Resets the terminal cursor to the beginning of the line */
export function reset2() {
process.stderr.write(Ansi("0G"));
}

/** Clears the current terminal line */
export function clear2() {
// Replaces text with spaces
process.stderr.write(Ansi("2K"));
reset2();
}

const Spin = {
items: ["⠇", "⠋", "⠙", "⠸", "⠴", "⠦"],
delayMs: 200,
};

/** Prints a Unicode spinner until a promise resolves */
export const spinUntil = async <T>(message: string, promise: Promise<T>) => {
let isDone = false;
let ix = 0;
void promise.finally(() => (isDone = true)).catch(() => {});
while (!isDone) {
await sleep(Spin.delayMs);
if (isDone) break;
clear2();
process.stderr.write(
AnsiSgr.Green +
Spin.items[ix % Spin.items.length] +
" " +
message +
AnsiSgr.Reset
);
ix++;
}
clear2();
return await promise;
};

0 comments on commit 2d9a7cc

Please sign in to comment.