Skip to content
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
2 changes: 2 additions & 0 deletions .changeset/proud-ravens-post.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
---
---
Original file line number Diff line number Diff line change
@@ -1,5 +1,15 @@
import { normalizeCDKConstructPath } from '@aws-amplify/cli-core';

import {
CloudFormationClient,
DescribeStackEventsCommand,
StackEvent,
} from '@aws-sdk/client-cloudformation';
import {
LogLevel,
normalizeCDKConstructPath,
printer,
} from '@aws-amplify/cli-core';
import { BackendIdentifierConversions } from '@aws-amplify/platform-core';
import { BackendIdentifier } from '@aws-amplify/plugin-types';
/**
* Creates a friendly name for a resource, using CDK metadata when available.
* @param logicalId The logical ID of the resource
Expand Down Expand Up @@ -51,3 +61,128 @@ export const createFriendlyName = (
const result = name || logicalId;
return result;
};

export type CloudFormationEventDetails = {
eventId: string;
timestamp: Date;
logicalId: string;
physicalId?: string;
resourceType: string;
status: string;
statusReason?: string;
stackId: string;
stackName: string;
};

/**
* Type for parsed CloudFormation resource status
*/
export type ResourceStatus = {
resourceType: string;
resourceName: string;
status: string;
timestamp: string;
key: string;
statusReason?: string;
eventId?: string;
};

/**
* Service for fetching CloudFormation events directly from the AWS API
*/
export class CloudFormationEventsService {
private cfnClient: CloudFormationClient;

/**
* Creates a new CloudFormationEventsService instance
*/
constructor() {
this.cfnClient = new CloudFormationClient({});
}

/**
* Gets CloudFormation events for a stack
* @param backendId The backend identifier
* @param sinceTimestamp Optional timestamp to filter events that occurred after this time
* @returns Array of CloudFormation events
*/
async getStackEvents(
backendId: BackendIdentifier,
sinceTimestamp?: Date,
): Promise<CloudFormationEventDetails[]> {
try {
const stackName = BackendIdentifierConversions.toStackName(backendId);
printer.log(
`Fetching CloudFormation events for stack: ${stackName}`,
LogLevel.DEBUG,
);

const command = new DescribeStackEventsCommand({ StackName: stackName });

const response = await this.cfnClient.send(command);

let events = response.StackEvents || [];

// Filter events by timestamp if provided
if (sinceTimestamp) {
const beforeCount = events.length;
events = events.filter(
(event) => event.Timestamp && event.Timestamp > sinceTimestamp,
);
printer.log(
`Filtered events by timestamp: ${beforeCount} -> ${events.length}`,
LogLevel.DEBUG,
);
}

const mappedEvents = events.map((event) => this.mapStackEvent(event));

return mappedEvents;
} catch (error) {
printer.log(
`Error fetching CloudFormation events: ${String(error)}`,
LogLevel.ERROR,
);
if (error instanceof Error) {
printer.log(`Error stack: ${error.stack}`, LogLevel.DEBUG);
}
return [];
}
}

/**
* Converts CloudFormation event details to ResourceStatus format
* @param event The CloudFormation event details
* @returns ResourceStatus object
*/
convertToResourceStatus(event: CloudFormationEventDetails): ResourceStatus {
return {
resourceType: event.resourceType,
resourceName: event.logicalId,
status: event.status,
timestamp: event.timestamp.toLocaleTimeString(),
key: `${event.resourceType}:${event.logicalId}`,
statusReason: event.statusReason,
eventId: event.eventId,
};
}

/**
* Maps AWS SDK StackEvent to our CloudFormationEventDetails type
* @param event The StackEvent from AWS SDK
* @returns CloudFormationEventDetails object
*/
private mapStackEvent(event: StackEvent): CloudFormationEventDetails {
return {
eventId: event.EventId || '',
timestamp: event.Timestamp || new Date(),
logicalId: event.LogicalResourceId || '',
physicalId: event.PhysicalResourceId,
resourceType: event.ResourceType || '',
status: event.ResourceStatus || '',
statusReason: event.ResourceStatusReason,
stackId: event.StackId || '',
stackName: event.StackName || '',
};
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,15 @@ import { useState, useEffect, useRef } from 'react';
import ConsoleViewer from './components/ConsoleViewer';
import Header from './components/Header';
import ResourceConsole from './components/ResourceConsole';
import DeploymentProgress from './components/DeploymentProgress';
import SandboxOptionsModal from './components/SandboxOptionsModal';
import { DevToolsSandboxOptions } from '../../shared/socket_types';
import LogSettingsModal, { LogSettings } from './components/LogSettingsModal';
import { SocketClientProvider } from './contexts/socket_client_context';
import { useSandboxClientService } from './contexts/socket_client_context';
import {
useSandboxClientService,
useDeploymentClientService,
} from './contexts/socket_client_context';
import { SandboxStatusData } from '../../shared/socket_types';
import { SandboxStatus } from '@aws-amplify/sandbox';

Expand Down Expand Up @@ -57,6 +61,7 @@ function AppContent() {
const [isDeletingLoading, setIsDeletingLoading] = useState(false);

const sandboxClientService = useSandboxClientService();
const deploymentClientService = useDeploymentClientService();

const deploymentInProgress = sandboxStatus === 'deploying';

Expand Down Expand Up @@ -509,7 +514,16 @@ function AppContent() {
{
id: 'logs',
label: 'Console Logs',
content: <ConsoleViewer logs={logs} />,
content: (
<SpaceBetween size="l">
<DeploymentProgress
deploymentClientService={deploymentClientService}
visible={true}
status={sandboxStatus}
/>
<ConsoleViewer logs={logs} />
</SpaceBetween>
),
},
{
id: 'resources',
Expand Down
Loading
Loading