Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: project governance custom config #1504

Open
wants to merge 21 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 8 additions & 1 deletion dbt_healthcheck.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
from typing import Optional

def project_healthcheck(
manifest_path, catalog_path=None, config_path=None, config=None
manifest_path, catalog_path=None, config_path=None, config=None, token=None, tenant=None, backend_url: Optional[str] = None,
Comment on lines +1 to +4
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Add type hints and improve parameter documentation

The function signature could benefit from:

  1. Consistent type hints for all parameters
  2. Documentation explaining the purpose of each parameter

Consider applying this improvement:

from typing import Optional, Dict, Any, Union

def project_healthcheck(
    manifest_path: str,
    catalog_path: Optional[str] = None,
    config_path: Optional[str] = None,
    config: Optional[Dict[str, Any]] = None,
    token: Optional[str] = None,
    tenant: Optional[str] = None,
    backend_url: Optional[str] = None,
) -> Dict[str, Any]:
    """Perform health check on DBT project.
    
    Args:
        manifest_path: Path to DBT manifest file
        catalog_path: Optional path to DBT catalog file
        config_path: Optional path to configuration file
        config: Optional configuration dictionary
        token: Optional authentication token
        tenant: Optional tenant/instance name
        backend_url: Optional backend URL
    
    Returns:
        Dict containing model insights
    """
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
from typing import Optional
def project_healthcheck(
manifest_path, catalog_path=None, config_path=None, config=None
manifest_path, catalog_path=None, config_path=None, config=None, token=None, tenant=None, backend_url: Optional[str] = None,
from typing import Optional, Dict, Any, Union
def project_healthcheck(
manifest_path: str,
catalog_path: Optional[str] = None,
config_path: Optional[str] = None,
config: Optional[Dict[str, Any]] = None,
token: Optional[str] = None,
tenant: Optional[str] = None,
backend_url: Optional[str] = None,
) -> Dict[str, Any]:
"""Perform health check on DBT project.
Args:
manifest_path: Path to DBT manifest file
catalog_path: Optional path to DBT catalog file
config_path: Optional path to configuration file
config: Optional configuration dictionary
token: Optional authentication token
tenant: Optional tenant/instance name
backend_url: Optional backend URL
Returns:
Dict containing model insights
"""

):
try:
import logging
Expand All @@ -20,6 +22,9 @@ def project_healthcheck(
manifest=manifest,
catalog=catalog,
config=config,
token=token,
instance_name=tenant,
backend_url=backend_url,
Comment on lines +25 to +27
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Add parameter validation and consider renaming for consistency

  1. The parameter tenant is mapped to instance_name which could be confusing. Consider using consistent naming.
  2. The function should validate the token and backend_url before passing them to DBTInsightGenerator.

Consider adding validation:

+ if backend_url and not backend_url.startswith(('http://', 'https://')):
+     raise ValueError("backend_url must be a valid HTTP(S) URL")
+ if token and not isinstance(token, str):
+     raise ValueError("token must be a string")

insight_generator = DBTInsightGenerator(
    manifest=manifest,
    catalog=catalog,
    config=config,
    token=token,
    instance_name=tenant,
    backend_url=backend_url,
)

Committable suggestion skipped: line range outside the PR's diff.

)
reports = insight_generator.run()

Expand All @@ -28,6 +33,8 @@ def project_healthcheck(
k: [json.loads(item.json()) for item in v]
for k, v in reports[MODEL].items()
}

return {"model_insights": model_insights}

except Exception as e:
raise Exception(str(e))
2 changes: 1 addition & 1 deletion src/altimate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -335,7 +335,7 @@ export interface ConversationGroup {

@provideSingleton(AltimateRequest)
export class AltimateRequest {
private static ALTIMATE_URL = workspace
public static ALTIMATE_URL = workspace
Copy link
Contributor

Choose a reason for hiding this comment

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

Changing ALTIMATE_URL from private to public might expose it to unintended access. If not necessary, consider keeping it private.

Suggested change
public static ALTIMATE_URL = workspace
private static ALTIMATE_URL = workspace

.getConfiguration("dbt")
.get<string>("altimateUrl", "https://api.myaltimate.com");
Comment on lines +338 to 340
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Add URL validation for workspace configuration.

The URL from workspace configuration is used without validation. Consider adding URL validation to prevent potential security issues:

 public static ALTIMATE_URL = workspace
   .getConfiguration("dbt")
-  .get<string>("altimateUrl", "https://api.myaltimate.com");
+  .get<string>("altimateUrl", "https://api.myaltimate.com");
+  private static validateUrl(url: string): string {
+    try {
+      const parsedUrl = new URL(url);
+      if (!['http:', 'https:'].includes(parsedUrl.protocol)) {
+        throw new Error('Invalid protocol');
+      }
+      return url;
+    } catch (e) {
+      console.warn(`Invalid URL in configuration: ${url}, using default`);
+      return "https://api.myaltimate.com";
+    }
+  }

Committable suggestion skipped: line range outside the PR's diff.


Expand Down
3 changes: 2 additions & 1 deletion src/dbt_client/dbtCloudIntegration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -206,6 +206,7 @@ export class DBTCloudProjectIntegration
private validationProvider: ValidationProvider,
private deferToProdService: DeferToProdService,
private projectRoot: Uri,
private altimateRequest: AltimateRequest,
) {
this.terminal.debug(
"DBTCloudProjectIntegration",
Expand Down Expand Up @@ -1189,7 +1190,7 @@ export class DBTCloudProjectIntegration
this.throwBridgeErrorIfAvailable();
const result = await this.python?.lock<ProjectHealthcheck>(
(python) =>
python!`to_dict(project_healthcheck(${manifestPath}, ${catalogPath}, ${configPath}, ${config}))`,
python!`to_dict(project_healthcheck(${manifestPath}, ${catalogPath}, ${configPath}, ${config}, ${this.altimateRequest.getAIKey()}, ${this.altimateRequest.getInstanceName()}, ${AltimateRequest.ALTIMATE_URL}))`,
);
return result;
}
Expand Down
2 changes: 1 addition & 1 deletion src/dbt_client/dbtCoreIntegration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1155,7 +1155,7 @@ export class DBTCoreProjectIntegration
await healthCheckThread.ex`from dbt_healthcheck import *`;
const result = await healthCheckThread.lock<ProjectHealthcheck>(
(python) =>
python!`to_dict(project_healthcheck(${manifestPath}, ${catalogPath}, ${configPath}, ${config}))`,
python!`to_dict(project_healthcheck(${manifestPath}, ${catalogPath}, ${configPath}, ${config}, ${this.altimateRequest.getAIKey()}, ${this.altimateRequest.getInstanceName()}, ${AltimateRequest.ALTIMATE_URL}))`,
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Add validation for authentication parameters.

The code passes authentication parameters directly to project_healthcheck without validation. Consider adding checks to ensure these parameters are available and valid before making the call.

Apply this diff to add validation:

-          python!`to_dict(project_healthcheck(${manifestPath}, ${catalogPath}, ${configPath}, ${config}, ${this.altimateRequest.getAIKey()}, ${this.altimateRequest.getInstanceName()}, ${AltimateRequest.ALTIMATE_URL}))`,
+          const aiKey = this.altimateRequest.getAIKey();
+          const instanceName = this.altimateRequest.getInstanceName();
+          if (!aiKey || !instanceName) {
+            throw new Error('Missing required authentication parameters for health check.');
+          }
+          python!`to_dict(project_healthcheck(${manifestPath}, ${catalogPath}, ${configPath}, ${config}, ${aiKey}, ${instanceName}, ${AltimateRequest.ALTIMATE_URL}))`,
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
python!`to_dict(project_healthcheck(${manifestPath}, ${catalogPath}, ${configPath}, ${config}, ${this.altimateRequest.getAIKey()}, ${this.altimateRequest.getInstanceName()}, ${AltimateRequest.ALTIMATE_URL}))`,
const aiKey = this.altimateRequest.getAIKey();
const instanceName = this.altimateRequest.getInstanceName();
if (!aiKey || !instanceName) {
throw new Error('Missing required authentication parameters for health check.');
}
python!`to_dict(project_healthcheck(${manifestPath}, ${catalogPath}, ${configPath}, ${config}, ${aiKey}, ${instanceName}, ${AltimateRequest.ALTIMATE_URL}))`,

);
return result;
} finally {
Expand Down
1 change: 1 addition & 0 deletions src/inversify.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,7 @@ container
container.get(ValidationProvider),
container.get(DeferToProdService),
projectRoot,
container.get(AltimateRequest),
);
};
});
Expand Down
58 changes: 38 additions & 20 deletions src/webview_provider/insightsPanel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -561,27 +561,45 @@ export class InsightsPanel extends AltimateWebviewProvider {
args: JSON.stringify(args),
});
try {
await window.withProgress(
{
title: `Performing healthcheck...`,
location: ProgressLocation.Notification,
cancellable: false,
},
async () => {
const projectHealthcheck =
await this.dbtProjectContainer.executeAltimateDatapilotHealthcheck(
args,
);
this._panel!.webview.postMessage({
command: "response",
args: {
syncRequestId,
body: { projectHealthcheck },
status: true,
},
});
},
window.showInformationMessage(
"Started performing healthcheck. We will notify you once it's done.",
);

const projectHealthcheck =
await this.dbtProjectContainer.executeAltimateDatapilotHealthcheck(
args,
);
Comment on lines +568 to +571
Copy link
Contributor

Choose a reason for hiding this comment

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

💡 Codebase verification

Error handling needs improvement in the healthcheck execution chain

  • The insightsPanel.ts implementation shows incomplete error handling in the try block without a corresponding catch block
  • Multiple potential error points exist in the execution chain:
    • dbtProjectContainer.ts throws an error if project not found
    • dbtProject.ts throws an error if manifest path not found
    • dbtCoreIntegration.ts and dbtCloudIntegration.ts have error-throwing capabilities (throwBridgeErrorIfAvailable)
    • Python bridge operations could fail

Add proper error handling in insightsPanel.ts:

try {
  window.showInformationMessage("Started performing healthcheck. We will notify you once it's done.");
  const projectHealthcheck = await this.dbtProjectContainer.executeAltimateDatapilotHealthcheck(args);
  // ... success handling ...
} catch (error) {
  window.showErrorMessage(`Healthcheck failed: ${error.message}`);
  this.dbtTerminal.error("InsightsPanel", "Healthcheck execution failed", error);
}
🔗 Analysis chain

Verify error handling in executeAltimateDatapilotHealthcheck.

The health check execution might fail for various reasons (network issues, invalid configuration, etc.). Ensure comprehensive error handling in the called method.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Check error handling in executeAltimateDatapilotHealthcheck implementation

# Search for the method implementation
ast-grep --pattern 'executeAltimateDatapilotHealthcheck(args$_) {
  $$$
}'

# Search for error handling patterns
rg -A 5 'catch|throw|Error' src/dbt_client/dbtProjectContainer.ts

Length of output: 222


Script:

#!/bin/bash
# Let's first find the correct file containing the implementation
fd "dbtProjectContainer"

# Then search for the method and error handling patterns in the found files
rg -l "executeAltimateDatapilotHealthcheck"

# Once we find the files, we'll examine the implementation and error handling

Length of output: 178


Script:

#!/bin/bash
# Now that we found the correct files, let's examine the implementation and error handling
ast-grep --pattern 'executeAltimateDatapilotHealthcheck($_) {
  $$$
}'

# Also check for any try-catch blocks or error handling around this method
rg -B 2 -A 10 "executeAltimateDatapilotHealthcheck" src/manifest/dbtProjectContainer.ts

# Look for error handling patterns in the implementation
rg -A 5 'catch|throw|Error' src/manifest/dbtProjectContainer.ts

Length of output: 1048


Script:

#!/bin/bash
# Let's check the implementation of performDatapilotHealthcheck in the project class
rg -B 2 -A 10 "performDatapilotHealthcheck" 

# Also check for error handling in the calling code in insightsPanel.ts
rg -B 2 -A 10 "executeAltimateDatapilotHealthcheck" src/webview_provider/insightsPanel.ts

Length of output: 6320

if (this._panel?.visible) {
window.showInformationMessage("Healthcheck completed successfully.");
Copy link
Contributor

Choose a reason for hiding this comment

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

The message "Healthcheck completed successfully." is shown twice. Consider removing the first window.showInformationMessage call to avoid redundancy.

this._panel!.webview.postMessage({
command: "response",
args: {
syncRequestId,
body: { projectHealthcheck },
status: true,
},
});
return;
}
const result = await window.showInformationMessage(
"Healthcheck completed successfully.",
"View results",
);
if (result === "View results") {
this.dbtTerminal.debug(
"InsightsPanel",
"Sending healthcheck results to webview",
);
await commands.executeCommand("dbtPowerUser.Insights.focus");
}
this._panel!.webview.postMessage({
command: "response",
args: {
syncRequestId,
body: { projectHealthcheck },
status: true,
},
});
Comment on lines +595 to +602
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Consider adding error handling for webview communication.

The webview message sending is not wrapped in a try-catch block, which could lead to unhandled exceptions if the webview is closed.

Add error handling:

-      this._panel!.webview.postMessage({
-        command: "response",
-        args: {
-          syncRequestId,
-          body: { projectHealthcheck },
-          status: true,
-        },
-      });
+      try {
+        if (this._panel) {
+          await this._panel.webview.postMessage({
+            command: "response",
+            args: {
+              syncRequestId,
+              body: { projectHealthcheck },
+              status: true,
+            },
+          });
+        }
+      } catch (error) {
+        this.dbtTerminal.error(
+          "InsightsPanel",
+          "Failed to send healthcheck results to webview",
+          error
+        );
+      }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
this._panel!.webview.postMessage({
command: "response",
args: {
syncRequestId,
body: { projectHealthcheck },
status: true,
},
});
try {
if (this._panel) {
await this._panel.webview.postMessage({
command: "response",
args: {
syncRequestId,
body: { projectHealthcheck },
status: true,
},
});
}
} catch (error) {
this.dbtTerminal.error(
"InsightsPanel",
"Failed to send healthcheck results to webview",
error
);
}

} catch (e) {
this.emitError(
syncRequestId,
Expand Down
7 changes: 7 additions & 0 deletions webview_panels/.storybook/preview.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,12 @@ import { decorator as VsCodeDecorator } from "./__mocks__/vscode";
import AppProvider from "../src/modules/app/AppProvider";

const theme = "vscode-dark"; // vscode-light
const css = `
.sb-show-main.vscode-dark {
--vscode-panel-background: #1e1e1e;
}
`;

const preview: Preview = {
decorators: [
(story) => {
Expand All @@ -17,6 +23,7 @@ const preview: Preview = {
VsCodeDecorator,
(Story) => (
<AppProvider>
<style>{css}</style>
<Story />
</AppProvider>
),
Expand Down
83 changes: 70 additions & 13 deletions webview_panels/src/lib/altimate/altimate-components.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { ButtonHTMLAttributes } from 'react';
import { ButtonProps } from 'reactstrap';
import { CaseReducerActions } from '@reduxjs/toolkit';
import { ChatMessage } from '@ant-design/pro-chat';
import { ComponentType } from 'react';
import { Dispatch } from 'react';
import { JSX as JSX_2 } from 'react/jsx-runtime';
import { PayloadAction } from '@reduxjs/toolkit';
Expand Down Expand Up @@ -66,9 +67,16 @@ export declare interface CoachAiResponse {
personalizationScope: string;
}

export declare const CoachForm: ({ taskLabel, context, onClose, extra }: Props_10) => JSX_2.Element;
export declare const CoachForm: (props: CoachFormProps) => JSX_2.Element;

export declare const CoachFormButton: ({}: Props_11) => JSX_2.Element;
export declare const CoachFormButton: ({}: Props_10) => JSX_2.Element;
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Remove empty interface for CoachFormButton props

The empty interface Props_10 should be removed if no props are needed, or properly defined if props are required.

Apply this diff:

-declare interface Props_10 {
-}

-export declare const CoachFormButton: ({}: Props_10) => JSX_2.Element;
+export declare const CoachFormButton: () => JSX_2.Element;

Also applies to: 350-351

🧰 Tools
🪛 Biome

[error] 72-72: Unexpected empty object pattern.

(lint/correctness/noEmptyPattern)


declare interface CoachFormProps {
taskLabel: keyof typeof TaskLabels;
context?: Record<string, unknown>;
extra?: Record<string, unknown>;
onClose: (data?: unknown) => void;
}

export declare const CodeBlock: ({ code, language, fileName, editorTheme, theme, showLineNumbers, className, titleActions, }: Props_4) => JSX.Element;

Expand Down Expand Up @@ -206,7 +214,7 @@ export declare const IconButton: (props: Props) => JSX.Element;
export declare interface Learning extends z.infer<typeof learningSchema> {
}

export declare const Learnings: ({ filters, learning }: Props_12) => JSX_2.Element;
export declare const Learnings: ({ filters, learning }: Props_11) => JSX_2.Element;

export declare const learningSchema: z.ZodObject<{
train_doc_uid: z.ZodString;
Expand Down Expand Up @@ -282,28 +290,74 @@ export declare enum PersonalizationScope {
ALL_USERS = "AllUsers"
}

export declare enum ProjectGovernorAllowedFiles {
Manifest = "Manifest",
Catalog = "Catalog"
}

export declare interface ProjectGovernorCheck extends z.infer<typeof ProjectGovernorCheckSchema> {
id: string;
}

export declare interface ProjectGovernorCheckConfirmationResponse {
ok: boolean;
}

export declare interface ProjectGovernorCheckFormValues {
description: string;
type: ProjectGovernorCheckTypes;
}

export declare const ProjectGovernorCheckSchema: z.ZodObject<{
name: z.ZodString;
alias: z.ZodString;
type: z.ZodEnum<[string, ...string[]]>;
description: z.ZodString;
files_required: z.ZodArray<z.ZodEnum<[string, ...string[]]>, "many">;
config: z.ZodRecord<z.ZodString, z.ZodUnknown>;
}, "strip", z.ZodTypeAny, {
type: string;
name: string;
description: string;
alias: string;
files_required: string[];
config: Record<string, unknown>;
}, {
type: string;
name: string;
description: string;
alias: string;
files_required: string[];
config: Record<string, unknown>;
}>;

export declare enum ProjectGovernorCheckTypes {
DOCUMENTATION = "documentation",
TESTS = "tests",
MODEL = "model",
FILE_STRUCTURE = "file_structure"
}

export declare interface ProjectGovernorCheckValidateResponse {
name: string;
description: string;
}

declare interface Props extends ButtonHTMLAttributes<HTMLButtonElement> {
color?: string;
}

declare interface Props_10 {
taskLabel: keyof typeof TaskLabels;
context?: Record<string, unknown>;
extra?: Record<string, unknown>;
onClose: () => void;
}

declare interface Props_11 {
}

declare interface Props_12 {
filters?: {
taskLabel?: keyof typeof TaskLabels;
};
learning?: string | null;
}

declare interface Props_13 {
declare interface Props_12 {
onSelect: (selected: (typeof TeamMatesConfig)[0], action: TeamMateActionType) => Promise<boolean | undefined>;
client: keyof typeof TeamMateAvailability;
}
Expand Down Expand Up @@ -437,7 +491,8 @@ export declare enum TaskLabels {
DocGen = "DocGen",
ChartBot = "ChartBot",
SqlBot = "SqlExpert",
OpportunitiesBot = "OpportunitiesBot"
OpportunitiesBot = "OpportunitiesBot",
ProjectGovernor = "ProjectGovernor"
}

export declare const TeammateActions: CaseReducerActions< {
Expand All @@ -463,6 +518,8 @@ export declare interface TeamMateConfig {
key: TaskLabels;
seeInAction?: boolean;
comingSoon?: boolean;
displayComponent?: ComponentType<any>;
formComponent?: ComponentType<any>;
Comment on lines +521 to +522
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Improve type safety for TeamMateConfig component properties

The any type for component props reduces type safety. Consider creating specific interfaces for the expected props of these components.

Apply this diff:

-    displayComponent?: ComponentType<any>;
-    formComponent?: ComponentType<any>;
+    displayComponent?: ComponentType<TeamMateDisplayProps>;
+    formComponent?: ComponentType<TeamMateFormProps>;

+interface TeamMateDisplayProps {
+    // Add specific props for display component
+}
+
+interface TeamMateFormProps {
+    // Add specific props for form component
+}

Committable suggestion skipped: line range outside the PR's diff.

}

export declare interface TeamMateContextProps {
Expand All @@ -474,7 +531,7 @@ export declare const TeamMateProvider: ({ children, }: {
children: ReactNode;
}) => JSX.Element;

export declare const TeamMates: ({ onSelect, client }: Props_13) => JSX_2.Element;
export declare const TeamMates: ({ onSelect, client }: Props_12) => JSX_2.Element;

export declare const TeamMatesConfig: TeamMateConfig[];

Expand Down
47 changes: 25 additions & 22 deletions webview_panels/src/lib/altimate/altimate-components.js
Original file line number Diff line number Diff line change
@@ -1,34 +1,37 @@
import { A as o, B as t, m as n, p as i, o as r, q as C, n as m, t as T, v as l, C as c, F as p, k as g, i as v, h as L, D as u, I as h, y as B, l as M, L as d, P as A, G as b, K as k, J as y, r as F, z as P, E as x, x as D, T as I, H as S, w as f } from "./main.js";
import { A as s, B as t, m as n, p as r, o as i, q as C, n as c, t as m, v as l, C as T, F as p, k as v, i as g, h, D as L, I as u, y as M, l as P, L as d, P as k, N as A, O as B, M as y, G as F, K as G, J as b, r as S, z as j, E as x, x as D, T as I, H as f, w } from "./main.js";
import "reactstrap";
export {
o as ApiHelper,
s as ApiHelper,
t as Badge,
n as CLL,
i as ChatTriggerLink,
r as Chatbot,
r as ChatTriggerLink,
i as Chatbot,
C as Citations,
m as CllEvents,
T as CoachForm,
c as CllEvents,
m as CoachForm,
l as CoachFormButton,
c as CodeBlock,
T as CodeBlock,
p as ContentCategory,
g as ConversationGroupProvider,
v as ConversationInputForm,
L as ConversationSources,
u as DbtDocs,
h as IconButton,
B as Learnings,
M as Lineage,
v as ConversationGroupProvider,
g as ConversationInputForm,
h as ConversationSources,
L as DbtDocs,
u as IconButton,
M as Learnings,
P as Lineage,
d as LoadingButton,
A as PersonalizationScope,
b as TaskLabels,
k as TeamMateActionType,
y as TeamMateAvailability,
F as TeamMateProvider,
P as TeamMates,
k as PersonalizationScope,
A as ProjectGovernorAllowedFiles,
B as ProjectGovernorCheckSchema,
y as ProjectGovernorCheckTypes,
F as TaskLabels,
G as TeamMateActionType,
b as TeamMateAvailability,
S as TeamMateProvider,
j as TeamMates,
x as TeamMatesConfig,
D as TeammateActions,
I as Tooltip,
S as learningSchema,
f as useTeamMateContext
f as learningSchema,
w as useTeamMateContext
};
Loading
Loading