Skip to content

Commit 61dc660

Browse files
UI - Load swagger api documentation
1 parent 76d9e24 commit 61dc660

File tree

4 files changed

+125
-40
lines changed

4 files changed

+125
-40
lines changed

src/ContentProcessorWeb/src/Components/Header/Header.tsx

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,9 @@ import "./Header.css";
2929
import { DocumentBulletListCubeRegular, InfoRegular, DocumentData16Regular } from "@fluentui/react-icons"
3030

3131
import useAuth from "../../msal-auth/useAuth.ts";
32+
import { useSelector, shallowEqual } from 'react-redux';
33+
import { RootState } from '../../store/index.ts';
34+
import useSwaggerPreview from "../../Hooks/useSwaggerPreview.ts";
3235

3336
interface HeaderPageProps {
3437
toggleTheme: () => void;
@@ -54,6 +57,10 @@ const HeaderPage: React.FC<HeaderPageProps> = ({ toggleTheme, isDarkMode }) => {
5457
const { shortcutLabel } = useHeaderHooks({ toggleTheme, isDarkMode });
5558
const { user, logout, getToken } = useAuth();
5659

60+
const { openSwaggerUI } = useSwaggerPreview();
61+
const store = useSelector((state: RootState) => ({
62+
swaggerJSON: state.leftPanel.swaggerJSON,
63+
}), shallowEqual);
5764

5865
const navigate = useNavigate();
5966
const location = useLocation();
@@ -76,8 +83,10 @@ const HeaderPage: React.FC<HeaderPageProps> = ({ toggleTheme, isDarkMode }) => {
7683
data: { value: TabValue }
7784
) => {
7885
if (data.value == 'api') {
79-
_.preventDefault(); // Prevents the default anchor click behavior
80-
window.open(process.env.REACT_APP_API_BASE_URL + "/docs", '_blank', 'noopener noreferrer');
86+
_.preventDefault();
87+
const apiUrl: string = process.env.REACT_APP_API_BASE_URL as string;
88+
const token = localStorage.getItem('token') ?? undefined;
89+
openSwaggerUI(store.swaggerJSON, apiUrl, token)
8190
} else {
8291
const newRoute = Object.keys(tabRoutes).find(
8392
(key) => tabRoutes[key] === data.value
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
// src/hooks/useSwaggerPreview.ts
2+
import { toast } from 'react-toastify';
3+
4+
const useSwaggerPreview = () => {
5+
const openSwaggerUI = (swaggerJSON: object | null, apiUrl: string, token?: string) => {
6+
if (!swaggerJSON) {
7+
toast.error('Swagger JSON is missing!');
8+
return;
9+
}
10+
11+
// Clone the Swagger JSON to avoid mutation
12+
const swaggerSpec = structuredClone(swaggerJSON) as any;
13+
swaggerSpec.servers = [{ url: apiUrl, description: 'API Endpoint' }];
14+
15+
const newWindow = window.open('', '_blank');
16+
if (!newWindow) return;
17+
18+
const htmlContent = `
19+
<!DOCTYPE html>
20+
<html>
21+
<head>
22+
<title>Swagger UI</title>
23+
<link rel="stylesheet" href="https://unpkg.com/swagger-ui-dist/swagger-ui.css" />
24+
</head>
25+
<body>
26+
<div id="swagger-ui"></div>
27+
<script src="https://unpkg.com/swagger-ui-dist/swagger-ui-bundle.js"></script>
28+
<script>
29+
window.onload = function () {
30+
const spec = ${JSON.stringify(swaggerSpec)};
31+
const token = "${token ?? ''}";
32+
33+
SwaggerUIBundle({
34+
spec: spec,
35+
dom_id: '#swagger-ui',
36+
requestInterceptor: function (req) {
37+
if (token) {
38+
req.headers['Authorization'] = 'Bearer ' + token;
39+
}
40+
return req;
41+
}
42+
});
43+
};
44+
</script>
45+
</body>
46+
</html>
47+
`;
48+
49+
newWindow.document.write(htmlContent);
50+
newWindow.document.close();
51+
};
52+
53+
return { openSwaggerUI };
54+
};
55+
56+
export default useSwaggerPreview;

src/ContentProcessorWeb/src/Pages/DefaultPage/PanelLeft.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import SchemaDropdown from './Components/SchemaDropdown/SchemaDropdown';
77
import UploadFilesModal from "../../Components/UploadContent/UploadFilesModal.tsx";
88

99
import { useDispatch, useSelector, shallowEqual } from 'react-redux';
10-
import { fetchSchemaData, fetchContentTableData, setRefreshGrid } from '../../store/slices/leftPanelSlice.ts';
10+
import { fetchSchemaData, fetchContentTableData, setRefreshGrid, fetchSwaggerData } from '../../store/slices/leftPanelSlice.ts';
1111
import { AppDispatch, RootState } from '../../store/index.ts';
1212
import { startLoader, stopLoader } from "../../store/slices/loaderSlice.ts";
1313
import { toast } from "react-toastify";
@@ -32,6 +32,7 @@ const PanelLeft: React.FC<PanelLeftProps> = () => {
3232
try {
3333
dispatch(startLoader("1"));
3434
await Promise.allSettled([
35+
dispatch(fetchSwaggerData()).unwrap(),
3536
dispatch(fetchSchemaData()).unwrap(),
3637
dispatch(fetchContentTableData({ pageSize: store.pageSize, pageNumber: 1 })).unwrap(),
3738
]);

src/ContentProcessorWeb/src/store/slices/leftPanelSlice.ts

Lines changed: 56 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,10 @@ interface LeftPanelState {
1313
gridLoader: boolean;
1414
processId: string | null;
1515
selectedItem: any;
16-
pageSize : number;
16+
pageSize: number;
1717
deleteFilesLoader: string[],
18-
isGridRefresh : boolean;
18+
isGridRefresh: boolean;
19+
swaggerJSON: any;
1920
}
2021

2122
interface UploadMetadata {
@@ -29,6 +30,11 @@ interface UploadFileResponse {
2930
data?: any; // You can specify a more precise type for the response data if needed
3031
}
3132

33+
export const fetchSwaggerData = createAsyncThunk<any, void>('/openapi', async (): Promise<any> => {
34+
const url = '/openapi.json';
35+
const response = await httpUtility.get(url);
36+
return response;
37+
});
3238

3339
// Async thunk for fetching data
3440
export const fetchSchemaData = createAsyncThunk<any, void>('/schemavault', async (): Promise<any> => {
@@ -50,15 +56,15 @@ interface DeleteApiResponse {
5056
process_id: string;
5157
status: string;
5258
message: string;
53-
}
54-
export const deleteProcessedFile = createAsyncThunk<any, { processId: string | null }>('/contentprocessor/deleteProcessedFile/', async ({ processId }, {rejectWithValue}) => {
59+
}
60+
export const deleteProcessedFile = createAsyncThunk<any, { processId: string | null }>('/contentprocessor/deleteProcessedFile/', async ({ processId }, { rejectWithValue }) => {
5561
if (!processId) {
5662
return rejectWithValue("Reset store");
5763
}
5864
const url = '/contentprocessor/processed/' + processId;
5965
const response = await httpUtility.delete(url);
6066
console.log("response", response);
61-
return response as DeleteApiResponse; ;
67+
return response as DeleteApiResponse;;
6268
});
6369

6470
export const uploadFile = createAsyncThunk<
@@ -106,15 +112,15 @@ const initialState: LeftPanelState = {
106112
schemaLoader: false,
107113
schemaError: null,
108114

109-
gridData: {...gridDefaultVal},
110-
gridLoader : false,
115+
gridData: { ...gridDefaultVal },
116+
gridLoader: false,
111117
processId: null,
112118
selectedItem: {},
113119
isGridRefresh: false,
114-
pageSize : 500,
120+
pageSize: 500,
115121

116-
deleteFilesLoader : [],
117-
122+
deleteFilesLoader: [],
123+
swaggerJSON: null
118124
};
119125

120126
const leftPanelSlice = createSlice({
@@ -134,6 +140,19 @@ const leftPanelSlice = createSlice({
134140
},
135141
extraReducers: (builder) => {
136142
//Fetch Dropdown values
143+
144+
builder
145+
.addCase(fetchSwaggerData.pending, (state) => {
146+
state.swaggerJSON = null;
147+
})
148+
.addCase(fetchSwaggerData.fulfilled, (state, action: PayloadAction<any>) => { // Adjust `any` to the response data type
149+
state.swaggerJSON = action.payload;
150+
})
151+
.addCase(fetchSwaggerData.rejected, (state, action) => {
152+
state.swaggerJSON = null;
153+
});
154+
155+
137156
builder
138157
.addCase(fetchSchemaData.pending, (state) => {
139158
state.schemaLoader = true; // You can manage loading state if necessary
@@ -153,7 +172,7 @@ const leftPanelSlice = createSlice({
153172
.addCase(fetchContentTableData.pending, (state) => {
154173
//state.schemaError = null;
155174
state.gridLoader = true;
156-
state.gridData = {...gridDefaultVal};
175+
state.gridData = { ...gridDefaultVal };
157176
})
158177
.addCase(fetchContentTableData.fulfilled, (state, action: PayloadAction<any>) => { // Adjust `any` to the response data type
159178
//state.schemaLoader = false;
@@ -182,33 +201,33 @@ const leftPanelSlice = createSlice({
182201
});
183202

184203

185-
//Fetch Grid Data
186-
builder
187-
.addCase(deleteProcessedFile.pending, (state, action) => {
188-
const processId = action.meta.arg.processId;
189-
if (processId) {
190-
state.deleteFilesLoader.push(processId);
191-
}
192-
})
193-
.addCase(deleteProcessedFile.fulfilled, (state, action) => {
194-
const processId = action.meta.arg.processId;
195-
if (processId) {
196-
state.deleteFilesLoader = state.deleteFilesLoader.filter(id => id !== processId);
197-
}
198-
if(action.payload.status === 'Success')
199-
toast.success("File deleted successfully.")
200-
else
201-
toast.error(action.payload.message)
202-
})
203-
.addCase(deleteProcessedFile.rejected, (state, action) => {
204-
const processId = action.meta.arg.processId;
205-
if (processId) {
206-
state.deleteFilesLoader = state.deleteFilesLoader.filter(id => id !== processId);
207-
toast.error("Failed to delete the file. Please try again.")
208-
}
209-
});
204+
//Fetch Grid Data
205+
builder
206+
.addCase(deleteProcessedFile.pending, (state, action) => {
207+
const processId = action.meta.arg.processId;
208+
if (processId) {
209+
state.deleteFilesLoader.push(processId);
210+
}
211+
})
212+
.addCase(deleteProcessedFile.fulfilled, (state, action) => {
213+
const processId = action.meta.arg.processId;
214+
if (processId) {
215+
state.deleteFilesLoader = state.deleteFilesLoader.filter(id => id !== processId);
216+
}
217+
if (action.payload.status === 'Success')
218+
toast.success("File deleted successfully.")
219+
else
220+
toast.error(action.payload.message)
221+
})
222+
.addCase(deleteProcessedFile.rejected, (state, action) => {
223+
const processId = action.meta.arg.processId;
224+
if (processId) {
225+
state.deleteFilesLoader = state.deleteFilesLoader.filter(id => id !== processId);
226+
toast.error("Failed to delete the file. Please try again.")
227+
}
228+
});
210229
},
211230
});
212231

213-
export const { setSchemaSelectedOption, setSelectedGridRow,setRefreshGrid } = leftPanelSlice.actions;
232+
export const { setSchemaSelectedOption, setSelectedGridRow, setRefreshGrid } = leftPanelSlice.actions;
214233
export default leftPanelSlice.reducer;

0 commit comments

Comments
 (0)