/**
* Notebook Service
* Manages Spark notebooks in Microsoft Fabric
*/
import * as vscode from 'vscode';
import { FabricApiService } from '../core/api/fabric-api-service';
export interface Notebook {
id: string;
displayName: string;
description?: string;
workspaceId: string;
defaultLakehouseId?: string;
defaultLakehouseName?: string;
lastModifiedTime?: Date;
createdTime?: Date;
}
export interface NotebookContent {
cells: NotebookCell[];
metadata: NotebookMetadata;
}
export interface NotebookCell {
cellType: 'code' | 'markdown';
source: string;
outputs?: CellOutput[];
executionCount?: number;
metadata?: Record<string, unknown>;
}
export interface CellOutput {
outputType: 'stream' | 'execute_result' | 'display_data' | 'error';
text?: string;
data?: Record<string, unknown>;
errorName?: string;
errorValue?: string;
traceback?: string[];
}
export interface NotebookMetadata {
language: 'python' | 'scala' | 'sql' | 'r';
sparkVersion?: string;
kernelspec?: {
name: string;
displayName: string;
};
}
export interface NotebookSession {
id: string;
notebookId: string;
state: 'starting' | 'running' | 'idle' | 'busy' | 'error' | 'dead';
sparkApplicationId?: string;
startTime: Date;
}
export class NotebookService {
private api: FabricApiService;
constructor() {
this.api = FabricApiService.getInstance();
}
async getNotebooks(workspaceId: string): Promise<Notebook[]> {
try {
const result = await this.api.get<{ value: Notebook[] }>(
`/workspaces/${workspaceId}/notebooks`
);
return result?.value || [];
} catch (error) {
console.error('Failed to get notebooks:', error);
return [];
}
}
async getNotebook(workspaceId: string, notebookId: string): Promise<Notebook | null> {
try {
return await this.api.get<Notebook>(
`/workspaces/${workspaceId}/notebooks/${notebookId}`
);
} catch (error) {
console.error('Failed to get notebook:', error);
return null;
}
}
async getNotebookContent(workspaceId: string, notebookId: string): Promise<NotebookContent | null> {
try {
return await this.api.get<NotebookContent>(
`/workspaces/${workspaceId}/notebooks/${notebookId}/content`
);
} catch (error) {
console.error('Failed to get notebook content:', error);
return null;
}
}
async createNotebook(
workspaceId: string,
name: string,
content?: NotebookContent,
lakehouseId?: string
): Promise<Notebook | null> {
try {
return await this.api.post<Notebook>(
`/workspaces/${workspaceId}/notebooks`,
{
displayName: name,
definition: content || {
cells: [],
metadata: { language: 'python' }
},
defaultLakehouseId: lakehouseId
}
);
} catch (error) {
console.error('Failed to create notebook:', error);
return null;
}
}
async updateNotebook(workspaceId: string, notebookId: string, updates: Partial<Notebook>): Promise<Notebook | null> {
try {
return await this.api.patch<Notebook>(
`/workspaces/${workspaceId}/notebooks/${notebookId}`,
updates
);
} catch (error) {
console.error('Failed to update notebook:', error);
return null;
}
}
async updateNotebookContent(workspaceId: string, notebookId: string, content: NotebookContent): Promise<boolean> {
try {
await this.api.put(
`/workspaces/${workspaceId}/notebooks/${notebookId}/content`,
content
);
return true;
} catch (error) {
console.error('Failed to update notebook content:', error);
return false;
}
}
async deleteNotebook(workspaceId: string, notebookId: string): Promise<boolean> {
try {
await this.api.delete(`/workspaces/${workspaceId}/notebooks/${notebookId}`);
return true;
} catch (error) {
console.error('Failed to delete notebook:', error);
return false;
}
}
async runNotebook(workspaceId: string, notebookId: string, parameters?: Record<string, unknown>): Promise<string | null> {
try {
const result = await this.api.post<{ id: string }>(
`/workspaces/${workspaceId}/notebooks/${notebookId}/jobs/instances`,
{ parameters }
);
return result?.id || null;
} catch (error) {
console.error('Failed to run notebook:', error);
return null;
}
}
async startSession(workspaceId: string, notebookId: string): Promise<NotebookSession | null> {
try {
return await this.api.post<NotebookSession>(
`/workspaces/${workspaceId}/notebooks/${notebookId}/sessions`,
{}
);
} catch (error) {
console.error('Failed to start session:', error);
return null;
}
}
async getSession(workspaceId: string, notebookId: string, sessionId: string): Promise<NotebookSession | null> {
try {
return await this.api.get<NotebookSession>(
`/workspaces/${workspaceId}/notebooks/${notebookId}/sessions/${sessionId}`
);
} catch (error) {
console.error('Failed to get session:', error);
return null;
}
}
async stopSession(workspaceId: string, notebookId: string, sessionId: string): Promise<boolean> {
try {
await this.api.delete(
`/workspaces/${workspaceId}/notebooks/${notebookId}/sessions/${sessionId}`
);
return true;
} catch (error) {
console.error('Failed to stop session:', error);
return false;
}
}
async executeCell(
workspaceId: string,
notebookId: string,
sessionId: string,
code: string
): Promise<CellOutput[] | null> {
try {
return await this.api.post<CellOutput[]>(
`/workspaces/${workspaceId}/notebooks/${notebookId}/sessions/${sessionId}/execute`,
{ code }
);
} catch (error) {
console.error('Failed to execute cell:', error);
return null;
}
}
async setDefaultLakehouse(workspaceId: string, notebookId: string, lakehouseId: string): Promise<boolean> {
try {
await this.api.patch(
`/workspaces/${workspaceId}/notebooks/${notebookId}`,
{ defaultLakehouseId: lakehouseId }
);
return true;
} catch (error) {
console.error('Failed to set default lakehouse:', error);
return false;
}
}
async exportToIpynb(workspaceId: string, notebookId: string): Promise<string | null> {
try {
const content = await this.getNotebookContent(workspaceId, notebookId);
if (content) {
return JSON.stringify(this.convertToJupyterFormat(content), null, 2);
}
return null;
} catch (error) {
console.error('Failed to export notebook:', error);
return null;
}
}
private convertToJupyterFormat(content: NotebookContent): object {
return {
nbformat: 4,
nbformat_minor: 4,
metadata: {
kernelspec: content.metadata.kernelspec || {
name: 'python3',
display_name: 'Python 3'
},
language_info: {
name: content.metadata.language || 'python'
}
},
cells: content.cells.map(cell => ({
cell_type: cell.cellType === 'code' ? 'code' : 'markdown',
source: cell.source.split('\n'),
metadata: cell.metadata || {},
outputs: cell.outputs || [],
execution_count: cell.executionCount || null
}))
};
}
}