Skip to content

Commit c54dba7

Browse files
authored
Merge pull request #15 from oslabs-beta/paython-mcp
Paython mcp
2 parents 45aa23d + aafc43a commit c54dba7

17 files changed

+3404
-125
lines changed

client/src/App.tsx

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,12 @@ function NeedRepo({ children }: { children: JSX.Element }) {
1313
}
1414
function NeedPipeline({ children }: { children: JSX.Element }) {
1515
const { result } = usePipelineStore();
16-
return !result?.generated_yaml ? <Navigate to="/configure" replace /> : children;
16+
const hasYaml =
17+
result?.generated_yaml ||
18+
result?.yaml ||
19+
result?.data?.generated_yaml;
20+
21+
return !hasYaml ? <Navigate to="/configure" replace /> : children;
1722
}
1823

1924
export default function App() {

client/src/lib/api.ts

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
export const BASE =
2-
import.meta.env.VITE_API_BASE ?? "http://localhost:3333/api";
2+
import.meta.env.VITE_API_BASE ?? "http://localhost:3000/api";
33

44
// Derive the server base without any trailing "/api" for MCP calls
55
const SERVER_BASE = BASE.replace(/\/api$/, "");
@@ -37,7 +37,7 @@ export const api = {
3737
const data = await mcp<{
3838
repositories: { name: string; full_name: string; branches?: string[] }[];
3939
}>("repo_reader", {});
40-
const repos = (data?.repositories ?? []).map((r) => r.full_name);
40+
const repos = (data?.data?.repositories ?? []).map((r) => r.full_name);
4141
return { repos };
4242
},
4343

@@ -46,7 +46,7 @@ export const api = {
4646
const data = await mcp<{
4747
repositories: { name: string; full_name: string; branches?: string[] }[];
4848
}>("repo_reader", {});
49-
const item = (data?.repositories ?? []).find((r) => r.full_name === repo);
49+
const item = (data?.data?.repositories ?? []).find((r) => r.full_name === repo);
5050
return { branches: item?.branches ?? [] };
5151
},
5252

@@ -204,4 +204,3 @@ function writeSecrets(repo: string, env: string, obj: Record<string, string>) {
204204

205205
// in-memory job storage for mock deploys
206206
const JOBS: Map<string, any> = new Map();
207-

client/src/pages/ConfigurePage.tsx

Lines changed: 56 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,39 @@
11
import { useEffect, useState } from "react";
2-
import { Link } from "react-router-dom";
2+
import { useNavigate } from "react-router-dom";
33
import { useRepoStore } from "../store/useRepoStore";
44
import { usePipelineStore } from "../store/usePipelineStore";
55

66
export default function ConfigurePage() {
77
const { repo, branch } = useRepoStore();
88
const pipeline = usePipelineStore();
9+
const navigate = useNavigate();
910

10-
// Load available AWS roles once
11+
// Log on mount with repo, branch, and navigate check
1112
useEffect(() => {
12-
pipeline.loadAwsRoles?.().catch(console.error);
13+
console.log("[ConfigurePage] Mounted. Repo:", repo, "Branch:", branch);
14+
if (!navigate) console.warn("[ConfigurePage] ⚠️ navigate() not initialized!");
15+
}, [repo, branch, navigate]);
16+
17+
// Load available AWS roles once, safely
18+
useEffect(() => {
19+
let loaded = false;
20+
21+
async function init() {
22+
if (loaded) return;
23+
loaded = true;
24+
try {
25+
console.log("[ConfigurePage] Loading AWS roles once...");
26+
await pipeline.loadAwsRoles?.();
27+
28+
// Re-read roles from store after load completes
29+
const updatedRoles = usePipelineStore.getState().roles;
30+
console.log("[ConfigurePage] Roles (after load):", updatedRoles);
31+
} catch (err) {
32+
console.error("Failed to load AWS roles:", err);
33+
}
34+
}
35+
36+
init();
1337
// eslint-disable-next-line react-hooks/exhaustive-deps
1438
}, []);
1539

@@ -22,6 +46,7 @@ export default function ConfigurePage() {
2246
setBusy(false);
2347
}
2448

49+
console.log("[ConfigurePage] pipeline.result:", pipeline.result);
2550
return (
2651
<section style={{ display: "grid", gap: 16 }}>
2752
<h1>Configure Pipeline</h1>
@@ -60,23 +85,45 @@ export default function ConfigurePage() {
6085

6186
<label>
6287
AWS Role (OIDC)
63-
<select value={pipeline.options.awsRoleArn ?? ""} onChange={(e)=>pipeline.setOption("awsRoleArn", e.target.value)} style={{ display: "block", padding: 8 }}>
88+
<select disabled={busy} value={pipeline.options.awsRoleArn ?? ""} onChange={(e)=>pipeline.setOption("awsRoleArn", e.target.value)} style={{ display: "block", padding: 8 }}>
6489
<option value="">-- select --</option>
65-
{pipeline.roles?.map((r) => <option key={r} value={r}>{r}</option>)}
90+
{pipeline.roles?.map((r) => (
91+
<option key={r.arn} value={r.arn}>
92+
{r.name}
93+
</option>
94+
))}
6695
</select>
6796
</label>
6897

6998
<div style={{ display: "flex", gap: 8 }}>
7099
<button onClick={onGenerate} disabled={busy}>{busy ? "Generating…" : "Generate Pipeline"}</button>
71-
<Link to="/secrets">
72-
<button disabled={!pipeline.result?.generated_yaml}>Continue → Secrets</button>
73-
</Link>
100+
<button
101+
onClick={() => {
102+
console.log("[ConfigurePage] Navigate button clicked.");
103+
console.log("[ConfigurePage] Pipeline result before navigating:", pipeline.result);
104+
try {
105+
navigate("/secrets", { state: { pipeline: pipeline.result } });
106+
console.log("[ConfigurePage] ✅ Navigation triggered successfully.");
107+
} catch (err) {
108+
console.error("[ConfigurePage] ❌ Navigation failed:", err);
109+
}
110+
}}
111+
disabled={
112+
!(
113+
pipeline.result?.yaml ||
114+
pipeline.result?.generated_yaml ||
115+
pipeline.result?.data?.generated_yaml
116+
)
117+
}
118+
>
119+
Continue → Secrets
120+
</button>
74121
</div>
75122

76123
<div>
77124
<div>YAML Preview</div>
78125
<pre style={{ maxHeight: 400, overflow: "auto", background: "#f6f6f6", padding: 12 }}>
79-
{pipeline.result?.generated_yaml ?? "Click Generate Pipeline to preview YAML…"}
126+
{pipeline.result?.yaml ?? pipeline.result?.generated_yaml ?? "Click Generate Pipeline to preview YAML…"}
80127
</pre>
81128
</div>
82129
</section>

client/src/store/usePipelineStore.ts

Lines changed: 52 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ type PipelineState = {
2020
result?: McpPipeline;
2121

2222
// local UI state
23-
roles: string[];
23+
roles: { name: string; arn: string }[];
2424
editing: boolean;
2525
editedYaml?: string;
2626
status: "idle" | "loading" | "success" | "error";
@@ -67,22 +67,66 @@ export const usePipelineStore = create<PipelineState & PipelineActions>()((set,
6767
setOption: (k, v) => set({ options: { ...get().options, [k]: v } }),
6868

6969
async loadAwsRoles() {
70-
const { roles } = await api.listAwsRoles();
71-
set({ roles });
72-
const { options } = get();
73-
if (!options.awsRoleArn && roles[0]) set({ options: { ...options, awsRoleArn: roles[0] } });
70+
try {
71+
const res = await api.listAwsRoles();
72+
73+
// Normalize both fetch-style and axios-style responses
74+
const payload = res?.data ?? res; // if axios -> res.data, if fetch -> res
75+
// Roles can live at payload.data.roles or payload.roles depending on server/helper
76+
const roles =
77+
payload?.data?.roles ??
78+
payload?.roles ??
79+
payload?.data?.data?.roles ??
80+
[];
81+
82+
console.log("[usePipelineStore] Raw roles payload:", payload);
83+
console.log("[usePipelineStore] Loaded roles (final):", roles);
84+
85+
// Normalize to objects even if backend returned strings
86+
const normalizedRoles = roles.map((r: any) =>
87+
typeof r === "string" ? { name: r.split("/").pop(), arn: r } : r
88+
);
89+
90+
set({ roles: normalizedRoles });
91+
92+
const { options } = get();
93+
if (!options.awsRoleArn && normalizedRoles[0]) {
94+
set({ options: { ...options, awsRoleArn: normalizedRoles[0].arn } });
95+
}
96+
} catch (err) {
97+
console.error("[usePipelineStore] Failed to load AWS roles:", err);
98+
set({ roles: [] });
99+
}
74100
},
75101

76102
async regenerate({ repo, branch }) {
77103
set({ status: "loading", error: undefined });
78104
try {
79105
const { template, stages, options } = get();
80-
const data = await api.createPipeline({
81-
repo, branch, service: "ci-cd-generator", template,
106+
const res = await api.createPipeline({
107+
repo,
108+
branch,
109+
service: "ci-cd-generator",
110+
template,
82111
options: { ...options, stages },
83112
});
84-
set({ result: data, status: "success", editing: false, editedYaml: undefined });
113+
114+
const generated_yaml =
115+
res?.data?.data?.generated_yaml ||
116+
res?.data?.generated_yaml ||
117+
res?.generated_yaml ||
118+
"";
119+
120+
set({
121+
result: { ...res, yaml: generated_yaml },
122+
status: "success",
123+
editing: false,
124+
editedYaml: undefined,
125+
});
126+
127+
console.log("[usePipelineStore] YAML generated:", generated_yaml.slice(0, 80));
85128
} catch (e: any) {
129+
console.error("[usePipelineStore] regenerate error:", e);
86130
set({ status: "error", error: e.message });
87131
}
88132
},

0 commit comments

Comments
 (0)