Skip to main content
Glama
nrwl

Nx MCP Server

Official
by nrwl
nx-cloud-fix-component.ts27.9 kB
import { ContextProvider } from '@lit-labs/context'; import type { CIPEInfo, CIPERunGroup, NxAiFix, NxCloudFixDetails, } from '@nx-console/shared-types'; import { EditorContext, editorContext, getVscodeStyleMappings, } from '@nx-console/shared-ui-components'; import { css, html, LitElement, TemplateResult } from 'lit'; import { customElement, property, state } from 'lit/decorators.js'; import './terminal-component'; import './components'; @customElement('nx-cloud-fix-component') export class NxCloudFixComponent extends EditorContext(LitElement) { constructor() { super(); if (this.editor === 'vscode') { // Add codicons CSS if (!document.querySelector('link[href*="codicon.css"]')) { const codiconsLink = document.createElement('link'); codiconsLink.rel = 'stylesheet'; codiconsLink.href = 'https://unpkg.com/@vscode/codicons@0.0.36/dist/codicon.css'; document.head.appendChild(codiconsLink); } } } protected override createRenderRoot(): Element | ShadowRoot { return this; } @property({ type: Object }) details: NxCloudFixDetails | undefined; @property({ type: Function }) onApply: | ((details: NxCloudFixDetails, commitMessage?: string) => void) | undefined; @property({ type: Function }) onReject: ((details: NxCloudFixDetails) => void) | undefined; @property({ type: Function }) onApplyLocally: ((details: NxCloudFixDetails) => void) | undefined; @property({ type: Function }) onShowDiff: (() => void) | undefined; @property({ type: Function }) onOpenExternalLink: ((url: string) => void) | undefined; @state() private isTerminalExpanded = true; @state() private userCommitMessage = ''; override render(): TemplateResult { if (!this.details) { return html`<div class="text-foreground flex h-[200px] items-center justify-center text-xl" > Loading... </div>`; } const { cipe, runGroup, terminalOutput } = this.details; const aiFix = runGroup.aiFix; if (!aiFix) { return html`<div class="mx-auto flex w-full flex-col pb-6"> No AI fix available </div>`; } return html` <div class="mx-auto flex w-full flex-col pb-6"> ${this.renderHeader(cipe, runGroup)} <div class="flex flex-col px-3"> <div class="px-3 py-2 pb-9"> ${!aiFix.failureClassification || aiFix.failureClassification === 'code_change' ? html` <p class="text-foreground m-0 leading-relaxed opacity-90"> Nx Cloud analyzes your failing CI tasks and automatically generates fixes whenever possible. The AI examines the error output, identifies the root cause, and suggests minimal code changes to resolve the issue. Once generated, the fix is verified by running the same task on Nx Cloud to ensure it resolves the error. </p> <p class="text-foreground m-0 mt-2 leading-relaxed opacity-90" > You can <span class="text-primary hover:text-primary cursor-pointer underline" @click="${() => this.handleShowDiff()}" > review the resulting git diff of the suggested changes</span >&nbsp;and choose to apply or reject them. </p> ` : html` <p class="text-foreground m-0 leading-relaxed opacity-90"> Nx Cloud has analyzed your failing CI task and identified the probable root cause. The analysis below explains why this task failed and what type of issue was detected. </p> `} </div> ${this.getFixSection(aiFix)} ${aiFix.suggestedFix && (!aiFix.failureClassification || aiFix.failureClassification === 'code_change') ? html`<div class="pointer-events-none relative m-0 h-9"> <div class="bg-border absolute left-1/2 top-0 h-full w-0.5 -translate-x-1/2" ></div> <div class="border-b-border absolute left-1/2 top-0 h-0 w-0 -translate-x-1/2 border-b-[10px] border-l-[8px] border-r-[8px] border-l-transparent border-r-transparent" ></div> </div>` : ''} ${!aiFix.failureClassification || aiFix.failureClassification === 'code_change' ? this.getStatusSection(aiFix) : ''} <div class="pointer-events-none relative m-0 h-9"> <div class="bg-border absolute left-1/2 top-0 h-full w-0.5 -translate-x-1/2" ></div> <div class="border-b-border absolute left-1/2 top-0 h-0 w-0 -translate-x-1/2 border-b-[10px] border-l-[8px] border-r-[8px] border-l-transparent border-r-transparent" ></div> </div> ${this.getTerminalSection(aiFix.taskIds[0], terminalOutput)} </div> <!-- Hidden pill-element to ensure Codicons CSS is loaded --> <pill-element style="display: none"></pill-element> </div> `; } private renderHeader(cipe: CIPEInfo, runGroup: CIPERunGroup): TemplateResult { return html` <div class="bg-background mx-3 flex justify-between p-3"> <div class="flex w-full flex-col"> <div class="flex w-full items-center justify-between"> <h1 class="text-foreground m-0 flex items-center gap-2 text-2xl font-semibold" > <svg role="img" xmlns="http://www.w3.org/2000/svg" stroke="currentColor" fill="transparent" viewBox="0 0 24 24" id="nx-cloud-header-logo" class="stroke-foreground h-6 w-6 flex-shrink-0 fill-transparent" > <path d="M22.167 7.167v-2.5a2.5 2.5 0 0 0-2.5-2.5h-15a2.5 2.5 0 0 0-2.5 2.5v15a2.5 2.5 0 0 0 2.5 2.5h2.5m15-15c-2.76 0-5 2.24-5 5s-2.24 5-5 5-5 2.24-5 5m15-15V19.59a2.577 2.577 0 0 1-2.576 2.576H7.167" stroke-width="2" ></path> </svg> Nx Cloud AI Fix ${this.editor === 'intellij' && this.onOpenExternalLink ? html`<span class="text-primary cursor-pointer hover:opacity-80" @click="${() => this.onOpenExternalLink!(cipe.cipeUrl)}" title="View CI Pipeline Execution" > <icon-element icon="link-external"></icon-element> </span>` : html`<a class="text-primary cursor-pointer hover:opacity-80" target="_blank" href="${cipe.cipeUrl}" title="View CI Pipeline Execution" > <icon-element icon="link-external"></icon-element> </a>`} </h1> <div class="bg-secondary text-secondaryForeground flex items-center gap-1.5 rounded-[18px] px-4 py-2 text-sm font-semibold" > <icon-element icon="git-branch"></icon-element> ${this.editor === 'intellij' && this.onOpenExternalLink && cipe.commitUrl ? html`<span @click="${() => this.onOpenExternalLink!(cipe.commitUrl!)}" class="cursor-pointer text-inherit no-underline hover:underline" > ${cipe.branch} </span>` : html`<a href="${cipe.commitUrl}" target="_blank" class="cursor-pointer text-inherit no-underline hover:underline" > ${cipe.branch} </a>`} </div> </div> </div> </div> `; } private handleApply() { if (this.details && this.onApply) { // Only pass custom message if user has edited it const customMessage = this.userCommitMessage && this.userCommitMessage !== '' && this.userCommitMessage !== this.getDefaultCommitMessage() ? this.userCommitMessage : undefined; this.onApply(this.details, customMessage); } } private getDefaultCommitMessage(): string { return this.details?.runGroup.aiFix?.suggestedFixDescription; } private handleCommitMessageChange(e: CustomEvent) { this.userCommitMessage = e.detail.value; } private handleApplyLocally() { if ( this.details && this.onApplyLocally && !this.details?.hasUncommittedChanges ) { this.onApplyLocally(this.details); } } private handleReject() { if (this.details && this.onReject) { this.onReject(this.details); } } private handleShowDiff() { if (this.onShowDiff) { this.onShowDiff(); } } private renderFormattedText(text: string): TemplateResult { // Regular expression to match text between backticks // This handles single backticks for inline code const parts = text.split(/(`[^`]*`)/g); return html`${parts.map((part) => { // Check if this part is code (wrapped in backticks) if (part.startsWith('`') && part.endsWith('`') && part.length > 1) { // Remove the backticks and render as code const code = part.slice(1, -1); // Handle empty code blocks if (code.length === 0) { return html`<code class="bg-fieldBackground rounded px-1 py-0.5 font-mono text-xs" >&nbsp;</code >`; } return html`<code class="bg-fieldBackground rounded px-1 py-0.5 font-mono text-xs" >${code}</code >`; } // Otherwise, render as regular text with line breaks const textLines = part.split('\n'); return textLines.map((line, i) => i === textLines.length - 1 ? line : html`${line}<br />`, ); })}`; } private getTerminalSection( taskId: string, terminalOutput: string, ): TemplateResult { let displayTaskId = ''; if (taskId.startsWith('nx-cloud record')) { displayTaskId = taskId; } else { displayTaskId = `nx run ${taskId}`; } return html` <div class="bg-background border-border ${this.isTerminalExpanded ? 'expanded' : ''} relative m-0 flex flex-col overflow-hidden rounded-none border" > <div class="${this.isTerminalExpanded ? 'border-b border-border' : 'border-b border-transparent transition-colors duration-200'} flex cursor-pointer select-none items-center gap-3 p-4 px-5" @click="${() => (this.isTerminalExpanded = !this.isTerminalExpanded)}" > <icon-element icon="${this.isTerminalExpanded ? 'chevron-down' : 'chevron-right'}" class="inline-flex h-4 w-4 flex-shrink-0 items-center justify-center" ></icon-element> <h2 class="m-0 flex items-center gap-2 text-base font-semibold"> Original failing task output: <span class="font-mono">${displayTaskId}</span> </h2> </div> ${this.isTerminalExpanded ? html` <div class="flex h-[400px] min-h-[300px] flex-col overflow-hidden pb-5 pl-0 pr-5 pt-0" > <terminal-component .content="${terminalOutput}" ></terminal-component> </div> ` : ''} </div> `; } private createCreatingFixSection(): TemplateResult { return html` <div class="border-border bg-background relative m-0 border p-6 text-center" > <div class="mx-auto mb-4 flex h-16 w-16 items-center justify-center"> <icon-element icon="loading" size="3rem" class="animate-spin-slow leading-none" ></icon-element> </div> <h2 class="text-foreground m-0 mb-2 text-lg font-semibold"> Creating Fix<span class="loading-dots inline-block w-6 text-left" ></span> </h2> <p class="text-foreground m-0 text-sm opacity-80"> Nx Cloud AI is analyzing the error and generating a fix. </p> </div> `; } private getFixSection(aiFix: NxAiFix): TemplateResult { // Check if this is an environment issue or other non-code_change classification const isEnvironmentIssue = aiFix.failureClassification && aiFix.failureClassification !== 'code_change'; // If fix was rejected, show the rejected state if (aiFix.userAction === 'REJECTED') { return html` <div class="border-border bg-background relative m-0 border p-6 text-center" > <div class="mx-auto mb-4 flex h-16 w-16 items-center justify-center"> <icon-element icon="circle-slash" size="3rem" class="text-foreground leading-none opacity-70" ></icon-element> </div> <h2 class="text-foreground m-0 mb-2 text-lg font-semibold"> Fix Rejected </h2> <p class="text-foreground m-0 text-sm opacity-80"> You chose not to apply the suggested fix. </p> </div> `; } // If fix was applied, show the applied state if (aiFix.userAction === 'APPLIED') { return html` <div class="border-border bg-background relative m-0 border p-6 text-center" > <div class="mx-auto mb-4 flex h-16 w-16 flex-col items-center justify-center" > <icon-element icon="git-branch" size="3rem" class="text-success leading-none" ></icon-element> </div> <h2 class="text-foreground m-0 mb-2 text-lg font-semibold"> Fix Applied </h2> <p class="text-foreground m-0 text-sm opacity-80"> The suggested fix has been committed to your branch. </p> </div> `; } if (aiFix.userAction === 'APPLIED_AUTOMATICALLY') { return html` <div class="border-border bg-background relative m-0 border p-6 text-center" > <div class="mx-auto mb-4 flex h-16 w-16 flex-col items-center justify-center" > <icon-element icon="git-branch" size="3rem" class="text-success leading-none" ></icon-element> </div> <h2 class="text-foreground m-0 mb-2 text-lg font-semibold"> Fix Applied Automatically </h2> <p class="text-foreground m-0 text-sm opacity-80"> The suggested fix has been automatically committed to your branch. </p> </div> `; } if (aiFix.userAction === 'APPLIED_LOCALLY') { return html` <div class="border-border bg-background relative m-0 border p-6 text-center" > <div class="mx-auto mb-4 flex h-16 w-16 flex-col items-center justify-center" > <icon-element icon="git-branch" size="3rem" class="text-success leading-none" ></icon-element> </div> <h2 class="text-foreground m-0 mb-2 text-lg font-semibold"> Fix Applied Locally </h2> <p class="text-foreground m-0 text-sm opacity-80"> The suggested changes have been applied locally. </p> </div> `; } // For environment issues, show a simplified version without apply/reject buttons if (isEnvironmentIssue) { return html` <div class="border-border bg-background relative m-0 border"> <div class="border-border relative flex items-start justify-between border-b p-4 px-5" > <div class="flex-1"> <h2 class="m-0 flex items-center gap-3 text-base font-semibold"> <icon-element icon="info"></icon-element> ${aiFix.failureClassification === 'environment_state' ? 'Environment Issue Detected' : 'Issue Analysis'} </h2> </div> </div> <div class="px-4 py-1 pb-4"> ${aiFix.suggestedFixReasoning ? html`<p class="text-foreground m-0 mb-3 text-sm opacity-90"> ${this.renderFormattedText(aiFix.suggestedFixReasoning)} </p>` : html`<p class="text-foreground m-0 mb-3 text-sm opacity-90"> Nx Cloud AI has analyzed this failure and determined it is not caused by code changes. This may be due to environment issues, external dependencies, or infrastructure problems. </p>`} </div> </div> `; } return html` <div class="border-border bg-background relative m-0 border"> <div class="border-border relative flex items-start justify-between border-b p-4 px-5" > <div class="flex-1"> <h2 class="m-0 flex items-center gap-3 text-base font-semibold"> <icon-element icon="sparkle"></icon-element> ${aiFix.suggestedFix ? 'Apply Suggested Fix' : 'No Fix Created'} </h2> </div> </div> <div class="px-4 py-1 pb-4"> ${aiFix.suggestedFixReasoning ? html`<p class="text-foreground m-0 mb-3 text-sm opacity-90"> ${this.renderFormattedText(aiFix.suggestedFixReasoning)} </p>` : ''} ${aiFix.suggestedFix ? html` <form-group-element variant="vertical"> <label-element for="commit-message" >Commit message</label-element > <textarea-element .value="${this.userCommitMessage}" placeholder="${this.getDefaultCommitMessage()}" rows="3" @value-changed="${this.handleCommitMessageChange}" ></textarea-element> </form-group-element> <div class="mt-4 flex justify-end gap-2"> <button-element text="Apply Fix" appearance="primary" @click="${() => this.handleApply()}" ></button-element> <button-element text="Apply Fix Locally" appearance="secondary" ?disabled=${this.details?.hasUncommittedChanges} title=${this.details?.hasUncommittedChanges ? 'You have uncommitted changes. Please commit or stash them before applying the fix locally.' : ''} @click="${() => this.handleApplyLocally()}" ></button-element> <button-element text="Reject" appearance="secondary" @click="${() => this.handleReject()}" ></button-element> </div> ` : ''} </div> </div> `; } private getStatusSection(aiFix: NxAiFix): TemplateResult { const hasAiFix = !!aiFix.suggestedFix; // TODO: Remove this once all environments have been migrated after deployment // Fall back to original validationStatus field for backwards compatibility const verificationStatus = aiFix.verificationStatus || (aiFix as any).validationStatus; const suggestedFixStatus = aiFix.suggestedFixStatus; if (suggestedFixStatus) { if (suggestedFixStatus === 'NOT_STARTED') { return this.createWaitingForFixSection(); } else if (suggestedFixStatus === 'IN_PROGRESS') { return this.createCreatingFixSection(); } else if (suggestedFixStatus === 'FAILED') { return this.createFixCreationFailedSection(); } else if (suggestedFixStatus === 'NOT_EXECUTABLE') { return this.createCancelledFixSection(); } else if (suggestedFixStatus === 'COMPLETED' && hasAiFix) { // Fix has been created, now check verification status // Continue to the verification status check below } } else { // todo: remove this once all environments have been updated if (!hasAiFix && verificationStatus === 'NOT_STARTED') { // Show creating fix state return this.createCreatingFixSection(); } // if the fix creation failed, show the proper error state if (!hasAiFix && verificationStatus == 'FAILED') { return this.createFixCreationFailedSection(); } } // Fix exists, show verification status // If there's no verification status but we have a fix, it means verification hasn't started yet if (!verificationStatus && hasAiFix) { return html``; // Don't show any status section if fix is ready but no verification info } switch (verificationStatus) { case 'IN_PROGRESS': return html` <div class="border-border bg-background relative m-0 border p-6 text-center" > <div class="mb-2 flex flex-col items-center justify-center gap-3"> <icon-element icon="loading" size="3rem" class="animate-spin-slow leading-none" ></icon-element> <h2 class="text-foreground m-0 text-lg font-semibold"> Verifying Fix<span class="loading-dots inline-block w-6 text-left" ></span> </h2> </div> <p class="text-foreground m-0 text-sm opacity-80"> Nx Cloud is verifying the fix. You can wait for verification to complete or apply the fix now if you're confident it's correct. </p> </div> `; case 'COMPLETED': return html` <div class="border-border bg-background relative m-0 border p-6 text-center" > <div class="mb-2 flex flex-col items-center justify-center gap-3"> <icon-element icon="verified" size="3rem" class="text-success leading-none" ></icon-element> <h2 class="text-foreground m-0 text-lg font-semibold"> Fix Verified on Nx Cloud </h2> </div> <p class="text-foreground m-0 text-sm opacity-80"> <span class="font-mono">${aiFix.taskIds[0]}</span> has been successfully re-run on Nx Cloud after applying the suggested changes. You can now commit the fix to your branch using the controls above. </p> </div> `; case 'FAILED': return html` <div class="border-border bg-background relative m-0 border p-6 text-center" > <div class="mb-2 flex flex-col items-center justify-center gap-3"> <icon-element icon="error" size="3rem" class="text-error leading-none" ></icon-element> <h2 class="text-foreground m-0 text-lg font-semibold"> Fix Verification Failed </h2> </div> <p class="text-foreground m-0 text-sm opacity-80"> The fix verification failed on Nx Cloud. You may still apply it if you believe it's correct/useful. </p> </div> `; default: // NOT_STARTED or other states return html``; } } createCancelledFixSection(): TemplateResult { return html` <div class="border-border bg-background relative m-0 border p-6 text-center" > <div class="mb-2 flex flex-col items-center justify-center gap-3"> <icon-element icon="circle-slash" size="3rem" class="text-error leading-none" ></icon-element> <h2 class="text-foreground m-0 text-lg font-semibold"> Fix Creation Cancelled </h2> </div> <p class="text-foreground m-0 text-sm opacity-80"> There were no fixes that were generated for this error. </p> </div> `; } createWaitingForFixSection(): TemplateResult { return html` <div class="border-border bg-background relative m-0 border p-6 text-center" > <div class="mb-2 flex flex-col items-center justify-center gap-3"> <icon-element icon="info" size="3rem" class="text-primary leading-none" ></icon-element> <h2 class="text-foreground m-0 text-lg font-semibold"> Nx Cloud is preparing to generate a fix </h2> </div> <p class="text-foreground m-0 text-sm opacity-80"> Nx Cloud is analyzing this run to see if a fix can be generated. This may take a moment. Please wait. </p> </div> `; } private createFixCreationFailedSection(): TemplateResult { return html` <div class="border-border bg-background relative m-0 border p-6 text-center" > <div class="mb-2 flex flex-col items-center justify-center gap-3"> <icon-element icon="error" size="3rem" class="text-error leading-none" ></icon-element> <h2 class="text-foreground m-0 text-lg font-semibold"> Fix Creation Failed </h2> </div> <p class="text-foreground m-0 text-sm opacity-80"> Nx Cloud was unable to generate a fix for the error. You can try running the task again or investigate the issue manually on the Nx Cloud UI </p> </div> `; } } @customElement('pill-element') class PillElement extends LitElement { protected override createRenderRoot(): Element | ShadowRoot { return this; } constructor() { super(); const codiconsLink = document.createElement('link'); codiconsLink.rel = 'stylesheet'; codiconsLink.href = 'https://unpkg.com/@vscode/codicons@0.0.36/dist/codicon.css'; this.appendChild(codiconsLink); } @property({ type: String }) text; @property({ type: String }) type = 'info'; override render(): TemplateResult { // Base classes for all pills const baseClasses = 'bg-badgeBackground text-badgeForeground text-xs font-medium box-border rounded-[18px] px-3 py-1 mr-2.5 font-semibold flex'; // Type-specific classes const typeClasses = { success: '!bg-success/10 !text-success', warning: '!bg-warning/10 !text-warning', error: '!bg-error/10 !text-error', info: '!bg-primary/10 !text-primary', }; const classes = `${baseClasses} ${typeClasses[this.type] || ''}`; if (!this.text) { return html`<div class="${classes}"> <div class="flex h-full items-center justify-center gap-1"> <slot></slot> </div> </div>`; } return html`<div class="${classes}"><span>${this.text}</span></div>`; } }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/nrwl/nx-console'

If you have feedback or need assistance with the MCP directory API, please join our Discord server