Skip to content

Commit

Permalink
TEMP COMMIT UNCHECKED
Browse files Browse the repository at this point in the history
  • Loading branch information
ofekby committed Dec 5, 2023
1 parent 6ad2176 commit 8e2b5d8
Show file tree
Hide file tree
Showing 8 changed files with 381 additions and 164 deletions.
2 changes: 2 additions & 0 deletions src/enterprise/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ import BINARY_STATE from "../binary/binaryStateSingleton";
import { activeTextEditorState } from "../activeTextEditorState";
import { ChatAPI } from "../tabnineChatWidget/ChatApi";
import ChatViewProvider from "../tabnineChatWidget/ChatViewProvider";
import USER_INFO_STATE from "./lifecycle/UserInfoState";

export async function activate(
context: vscode.ExtensionContext
Expand All @@ -64,6 +65,7 @@ export async function activate(
context.subscriptions.push(await setEnterpriseContext());
context.subscriptions.push(new WorkspaceUpdater());
context.subscriptions.push(BINARY_STATE);
context.subscriptions.push(USER_INFO_STATE);
context.subscriptions.push(activeTextEditorState);
context.subscriptions.push(
commands.registerCommand(CONFIG_COMMAND, () => {
Expand Down
28 changes: 28 additions & 0 deletions src/enterprise/lifecycle/UserInfoState.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { Disposable } from "vscode";
import EventEmitterBasedState from "../../state/EventEmitterBasedState";
import getUserInfo, { UserInfo } from "../requests/UserInfo";
import { useDerviedState } from "../../state/deriveState";
import BINARY_STATE from "../../binary/binaryStateSingleton";

class UserInfoState extends EventEmitterBasedState<UserInfo> {
toDispose: Disposable;

constructor() {
super();

this.toDispose = useDerviedState(
BINARY_STATE,
(s) => s.is_logged_in,
() => {
void this.asyncSet(fetchUserState);
}
);
}
}

async function fetchUserState(): Promise<UserInfo | null> {
return (await getUserInfo()) ?? null;
}

const USER_INFO_STATE = new UserInfoState();
export default USER_INFO_STATE;
159 changes: 42 additions & 117 deletions src/enterprise/statusBar/StatusBar.ts
Original file line number Diff line number Diff line change
@@ -1,148 +1,73 @@
import { Disposable, ExtensionContext, authentication, window } from "vscode";
import { Disposable, ExtensionContext, window } from "vscode";
import { StatusItem } from "./StatusItem";
import { StatusState, showLoginNotification } from "./statusAction";
import { isHealthyServer } from "../update/isHealthyServer";
import { rejectOnTimeout } from "../../utils/utils";
import { getState, tabNineProcess } from "../../binary/requests/requests";
import {
BINARY_NOTIFICATION_POLLING_INTERVAL,
BRAND_NAME,
CONGRATS_MESSAGE_SHOWN_KEY,
} from "../../globals/consts";
import getUserInfo, { UserInfo } from "../requests/UserInfo";
import { Logger } from "../../utils/logger";
import { completionsState } from "../../state/completionsState";
import { showLoginNotification } from "./statusAction";
import { CONGRATS_MESSAGE_SHOWN_KEY } from "../../globals/consts";
import StatusBarState from "./StatusBarState";
import { useDerviedState } from "../../state/deriveState";
import USER_INFO_STATE from "../lifecycle/UserInfoState";
import { StatusBarStateData } from "./calculateStatusBarState";

export class StatusBar implements Disposable {
private item: StatusItem;

private statusPollingInterval: NodeJS.Timeout | undefined = undefined;

private disposables: Disposable[] = [];

private context: ExtensionContext;

private statusBarState = new StatusBarState();

constructor(context: ExtensionContext) {
context.subscriptions.push(this);
this.context = context;
this.item = new StatusItem();
void authentication.getSession(BRAND_NAME, []);

this.disposables.push(
authentication.onDidChangeSessions((e) => {
if (e.provider.id === BRAND_NAME) {
void this.enforceLogin();
this.statusBarState,
this.statusBarState.onChange((statusBarData) => {
this.updateStatusBar(statusBarData);
}),
useDerviedState(
USER_INFO_STATE,
(s) => s.isLoggedIn,
(isLoggedIn) => {
if (isLoggedIn) {
showLoginNotification();
}
}
})
)
);
this.setDefaultStatus();

// eslint-disable-next-line @typescript-eslint/unbound-method
this.setServerRequired().catch(Logger.error);

completionsState.on("changed", () => this.setDefaultStatus());
}

private async setServerRequired() {
Logger.debug("Checking if server url is set and healthy.");
if (await isHealthyServer()) {
Logger.debug("Server is healthy");
this.setDefaultStatus();
} else {
Logger.warn("Server url isn't set or not responding to GET /health");
this.item.setWarning("Please set your Tabnine server URL");
this.item.setCommand(StatusState.SetServer);
private updateStatusBar(statusBarData: StatusBarStateData) {
switch (statusBarData.type) {
case "default":
this.item.setDefault();
void this.showFirstSuceessNotification();
break;
case "loading":
this.item.setLoading();
break;
case "error":
this.item.setError(statusBarData.message);
break;
case "warning":
this.item.setWarning(statusBarData.message);
break;
default:
}
}

public waitForProcess() {
Logger.debug("Waiting for Tabnine process to become ready.");
this.item.setLoading();
this.item.setCommand(StatusState.WaitingForProcess);

rejectOnTimeout(tabNineProcess.onReady, 10_000).then(
() => this.enforceLogin(),
() => this.setProcessTimedoutError()
);
}

private setProcessTimedoutError() {
Logger.error("Timedout waiting for Tabnine process to become ready.");
this.item.setError("Tabnine failed to start, view logs for more details");
this.item.setCommand(StatusState.ErrorWaitingForProcess);
}

private setGenericError(error: Error) {
Logger.error(error);
this.item.setError("Something went wrong");
this.item.setCommand(StatusState.OpenLogs);
}

private setDefaultStatus() {
if (!completionsState.value) {
this.item.setCompletionsDisabled();
} else {
this.item.setDefault();
}
this.item.setCommand(StatusState.Ready);
}

private async enforceLogin() {
const userInfo = await getUserInfo();
if (userInfo?.isLoggedIn) {
Logger.debug("The user is logged in.");
this.checkTeamMembership(userInfo);
} else {
Logger.info(
"The user isn't logged in, set status bar and showing notification"
);
this.item.setWarning("Please sign in to access Tabnine");
this.item.setCommand(StatusState.LogIn);
showLoginNotification();
if (statusBarData.command) {
this.item.setCommand(statusBarData.command);
}
}

private checkTeamMembership(userInfo: UserInfo | null | undefined) {
this.setDefaultStatus();
try {
if (!userInfo?.team) {
Logger.warn("User isn't part of a team");
this.item.setWarning("You are not part of a team");
this.item.setCommand(StatusState.NotPartOfTheTeam);
} else {
Logger.debug("Everything seems to be fine, we are ready!");
this.setReady();
}
} catch (error) {
this.setGenericError(error as Error);
}
}

private setReady() {
void this.showFirstSuceessNotification();
this.setDefaultStatus();
this.statusPollingInterval = setInterval(() => {
void getState().then(
(state) => {
if (state?.cloud_connection_health_status !== "Ok") {
this.item.setWarning(
"Connectivity issue - Tabnine is unable to reach the server"
);
this.item.setCommand(StatusState.ConnectivityIssue);
} else {
this.setDefaultStatus();
}
},
(error) => this.setGenericError(error as Error)
);
}, BINARY_NOTIFICATION_POLLING_INTERVAL);
waitForProcess() {
this.statusBarState.startWaitingForProcess();
}

public dispose() {
this.item.dispose();
Disposable.from(...this.disposables).dispose();
if (this.statusPollingInterval) {
clearInterval(this.statusPollingInterval);
}
}

private async showFirstSuceessNotification() {
Expand Down
135 changes: 135 additions & 0 deletions src/enterprise/statusBar/StatusBarState.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
import { Disposable } from "vscode";
import EventEmitterBasedNonNullState from "../../state/EventEmitterBasedNonNullState";
import { tabNineProcess } from "../../binary/requests/requests";
import {
PromiseStateData,
convertPromiseToState,
triggeredPromiseState,
useDerviedState,
} from "../../state/deriveState";
import BINARY_STATE from "../../binary/binaryStateSingleton";
import { rejectOnTimeout } from "../../utils/utils";
import { completionsState } from "../../state/completionsState";
import { isHealthyServer } from "../update/isHealthyServer";
import { UserInfo } from "../requests/UserInfo";
import USER_INFO_STATE from "../lifecycle/UserInfoState";
import calculateStatusBarState, {
INITIAL_STATE,
StatusBarStateData,
} from "./calculateStatusBarState";

export default class StatusBarState extends EventEmitterBasedNonNullState<StatusBarStateData> {
private toDispose: Disposable;

private processStartedState = triggeredPromiseState(() =>
rejectOnTimeout(tabNineProcess.onReady, 10_000)
);

constructor() {
super(INITIAL_STATE);

const serverHealthOnPluginStartState = convertPromiseToState(
isHealthyServer()
);

const startedProcessDisposable = this.processStartedState.onChange(
(startedState) => {
this.updateState(
BINARY_STATE.get()?.cloud_connection_health_status,
startedState,
completionsState.value,
serverHealthOnPluginStartState.get(),
USER_INFO_STATE.get()
);
}
);
const serverHealthDisposable = serverHealthOnPluginStartState.onChange(
(isHealthy) => {
this.updateState(
BINARY_STATE.get()?.cloud_connection_health_status,
this.processStartedState.get(),
completionsState.value,
isHealthy,
USER_INFO_STATE.get()
);
}
);

this.updateState(
BINARY_STATE.get()?.cloud_connection_health_status,
this.processStartedState.get(),
completionsState.value,
serverHealthOnPluginStartState.get(),
USER_INFO_STATE.get()
);

const stateDisposable = useDerviedState(
BINARY_STATE,
(s) => s.cloud_connection_health_status,
(cloudConnection) => {
this.updateState(
cloudConnection,
this.processStartedState.get(),
completionsState.value,
serverHealthOnPluginStartState.get(),
USER_INFO_STATE.get()
);
}
);

const userInfoStateDisposable = USER_INFO_STATE.onChange((userInfo) => {
this.updateState(
BINARY_STATE.get()?.cloud_connection_health_status,
this.processStartedState.get(),
completionsState.value,
serverHealthOnPluginStartState.get(),
userInfo
);
});

completionsState.on("changed", () => {
this.updateState(
BINARY_STATE.get()?.cloud_connection_health_status,
this.processStartedState.get(),
completionsState.value,
serverHealthOnPluginStartState.get(),
USER_INFO_STATE.get()
);
});

this.toDispose = Disposable.from(
userInfoStateDisposable,
stateDisposable,
startedProcessDisposable,
serverHealthDisposable,
this.processStartedState
);
}

private updateState(
cloudConnection: "Ok" | string | undefined | null,
processStartedState: PromiseStateData<unknown>,
isCompletionsEnabled: boolean,
serverHealthOnPluginStart: PromiseStateData<boolean>,
userInfo: UserInfo | null
) {
this.set(
calculateStatusBarState(
cloudConnection,
processStartedState,
isCompletionsEnabled,
serverHealthOnPluginStart,
userInfo
)
);
}

startWaitingForProcess() {
this.processStartedState.trigger();
}

dispose(): void {
super.dispose();
this.toDispose.dispose();
}
}
Loading

0 comments on commit 8e2b5d8

Please sign in to comment.