Skip to content

Commit b116d45

Browse files
authored
fix(admin-ui): Use typescript generated client for Scopes pages (#2414)
* fix(admin-ui): Use typescript generated client for Scopes pages #2413 * fix(admin-ui): Apply code review suggestions #2413
1 parent 1c3cc6b commit b116d45

20 files changed

+1272
-620
lines changed

admin-ui/plugins/auth-server/components/Scopes/ScopeAddPage.js

Lines changed: 0 additions & 99 deletions
This file was deleted.

admin-ui/plugins/auth-server/components/Scopes/ScopeAddPage.test.js renamed to admin-ui/plugins/auth-server/components/Scopes/ScopeAddPage.test.tsx

Lines changed: 21 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,26 @@ import scopes from './scopes.test'
66
import AppTestWrapper from 'Routes/Apps/Gluu/Tests/Components/AppTestWrapper.test'
77
import { combineReducers, configureStore } from '@reduxjs/toolkit'
88

9+
// Mock orval hooks
10+
jest.mock('JansConfigApi', () => ({
11+
usePostOauthScopes: jest.fn(() => ({
12+
mutateAsync: jest.fn(),
13+
isPending: false,
14+
isSuccess: false,
15+
isError: false,
16+
})),
17+
}))
18+
19+
// Mock audit logger
20+
jest.mock('./hooks', () => ({
21+
useScopeActions: jest.fn(() => ({
22+
logScopeCreation: jest.fn(),
23+
navigateToScopeList: jest.fn(),
24+
navigateToScopeAdd: jest.fn(),
25+
navigateToScopeEdit: jest.fn(),
26+
})),
27+
}))
28+
929
const permissions = [
1030
'https://jans.io/oauth/config/openid/clients.readonly',
1131
'https://jans.io/oauth/config/openid/clients.write',
@@ -14,11 +34,6 @@ const permissions = [
1434
const INIT_STATE = {
1535
permissions: permissions,
1636
}
17-
const SCPOPES_STATE = {
18-
items: scopes,
19-
item: {},
20-
loading: false,
21-
}
2237
const STATE = {
2338
scopes: [],
2439
scripts: [],
@@ -29,12 +44,11 @@ const store = configureStore({
2944
reducer: combineReducers({
3045
authReducer: (state = INIT_STATE) => state,
3146
initReducer: (state = STATE) => state,
32-
scopeReducer: (state = SCPOPES_STATE) => state,
3347
noReducer: (state = {}) => state,
3448
}),
3549
})
3650

37-
const Wrapper = ({ children }) => (
51+
const Wrapper = ({ children }: { children: React.ReactNode }) => (
3852
<AppTestWrapper>
3953
<Provider store={store}>{children}</Provider>
4054
</AppTestWrapper>
Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
import React, { useEffect, useState } from 'react'
2+
import { useSelector, useDispatch } from 'react-redux'
3+
import { CardBody, Card } from 'Components'
4+
import ScopeForm from './ScopeForm'
5+
import GluuLoader from 'Routes/Apps/Gluu/GluuLoader'
6+
import { getAttributes, getScripts } from 'Redux/features/initSlice'
7+
import { buildPayload } from 'Utils/PermChecker'
8+
import GluuAlert from 'Routes/Apps/Gluu/GluuAlert'
9+
import { useTranslation } from 'react-i18next'
10+
import applicationStyle from 'Routes/Apps/Gluu/styles/applicationstyle'
11+
import { usePostOauthScopes } from 'JansConfigApi'
12+
import type { Scope } from 'JansConfigApi'
13+
import type { ExtendedScope, ScopeScript, ScopeClaim, ModifiedFields } from './types'
14+
import { EMPTY_SCOPE } from './types'
15+
import { useScopeActions } from './hooks'
16+
17+
interface InitState {
18+
scripts: ScopeScript[]
19+
attributes: ScopeClaim[]
20+
}
21+
22+
interface RootState {
23+
initReducer: InitState
24+
}
25+
26+
const ScopeAddPage: React.FC = () => {
27+
const { t } = useTranslation()
28+
const dispatch = useDispatch()
29+
const { logScopeCreation, navigateToScopeList } = useScopeActions()
30+
31+
const scripts = useSelector((state: RootState) => state.initReducer.scripts)
32+
const attributes = useSelector((state: RootState) => state.initReducer.attributes)
33+
34+
const [modifiedFields, setModifiedFields] = useState<ModifiedFields>({})
35+
const [errorMessage, setErrorMessage] = useState<string | null>(null)
36+
37+
const createScope = usePostOauthScopes()
38+
39+
useEffect(() => {
40+
if (attributes.length === 0) {
41+
const attributeOptions: Record<string, unknown> = {}
42+
buildPayload(attributeOptions, 'Fetch attributes', { limit: 100 })
43+
dispatch(getAttributes({ options: attributeOptions }))
44+
}
45+
if (scripts.length === 0) {
46+
const scriptAction: Record<string, unknown> = {}
47+
buildPayload(scriptAction, 'Fetch custom scripts', {})
48+
dispatch(getScripts({ action: scriptAction }))
49+
}
50+
}, [dispatch, attributes.length, scripts.length])
51+
52+
async function handleSubmit(data: string) {
53+
if (!data) return
54+
55+
setErrorMessage(null)
56+
57+
let parsedData: Scope
58+
try {
59+
parsedData = JSON.parse(data) as Scope
60+
} catch (error) {
61+
console.error('Error parsing scope data:', error)
62+
setErrorMessage(t('messages.error_in_parsing_data'))
63+
return
64+
}
65+
66+
try {
67+
const message = (parsedData as Record<string, unknown>).action_message as string
68+
delete (parsedData as Record<string, unknown>).action_message
69+
70+
await createScope.mutateAsync({ data: parsedData })
71+
72+
try {
73+
await logScopeCreation(parsedData, message, modifiedFields)
74+
} catch (auditError) {
75+
console.error('Error logging audit:', auditError)
76+
}
77+
78+
navigateToScopeList()
79+
} catch (error) {
80+
console.error('Error creating scope:', error)
81+
setErrorMessage(error instanceof Error ? error.message : t('messages.error_in_saving'))
82+
}
83+
}
84+
85+
const handleSearch = (value: string) => {
86+
const option = {
87+
pattern: value,
88+
}
89+
dispatch(getAttributes({ options: option }))
90+
}
91+
92+
const scope: ExtendedScope = {
93+
...EMPTY_SCOPE,
94+
claims: [],
95+
dynamicScopeScripts: [],
96+
defaultScope: false,
97+
attributes: {
98+
spontaneousClientId: undefined,
99+
spontaneousClientScopes: [],
100+
showInConfigurationEndpoint: false,
101+
},
102+
}
103+
104+
return (
105+
<GluuLoader blocking={createScope.isPending}>
106+
<GluuAlert
107+
severity={t('titles.error')}
108+
message={errorMessage || t('messages.error_in_saving')}
109+
show={!!errorMessage}
110+
/>
111+
<Card className="mb-3" style={applicationStyle.mainCard}>
112+
<CardBody>
113+
<ScopeForm
114+
scope={scope}
115+
scripts={scripts}
116+
attributes={attributes}
117+
handleSubmit={handleSubmit}
118+
onSearch={handleSearch}
119+
modifiedFields={modifiedFields}
120+
setModifiedFields={setModifiedFields}
121+
/>
122+
</CardBody>
123+
</Card>
124+
</GluuLoader>
125+
)
126+
}
127+
128+
export default ScopeAddPage

admin-ui/plugins/auth-server/components/Scopes/ScopeDetailPage.test.js renamed to admin-ui/plugins/auth-server/components/Scopes/ScopeDetailPage.test.tsx

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,17 +3,16 @@ import { render, screen } from '@testing-library/react'
33
import ScopeDetailPage from './ScopeDetailPage'
44
import scopes from './scopes.test'
55
import AppTestWrapper from 'Routes/Apps/Gluu/Tests/Components/AppTestWrapper.test'
6+
import type { Scope } from './types'
67

7-
const Wrapper = ({ children }) => <AppTestWrapper>{children}</AppTestWrapper>
8-
const permissions = [
9-
'https://jans.io/oauth/config/scopes.readonly',
10-
'https://jans.io/oauth/config/scopes.write',
11-
'https://jans.io/oauth/config/scopes.delete',
12-
]
13-
const scope = scopes[0]
8+
const Wrapper = ({ children }: { children: React.ReactNode }) => (
9+
<AppTestWrapper>{children}</AppTestWrapper>
10+
)
11+
12+
const scope = scopes[0] as Scope
1413

1514
it('Should render the scope detail page properly', () => {
16-
render(<ScopeDetailPage row={scope} permissions={permissions} />, {
15+
render(<ScopeDetailPage row={scope} />, {
1716
wrapper: Wrapper,
1817
})
1918
screen.getByText(/Display Name/)

admin-ui/plugins/auth-server/components/Scopes/ScopeDetailPage.js renamed to admin-ui/plugins/auth-server/components/Scopes/ScopeDetailPage.tsx

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,19 +5,21 @@ import { SCOPE } from 'Utils/ApiResources'
55
import { useTranslation } from 'react-i18next'
66
import { ThemeContext } from 'Context/theme/themeContext'
77
import customColors from '@/customColors'
8+
import type { ScopeDetailPageProps } from './types'
89

9-
function ScopeDetailPage({ row }) {
10+
const ScopeDetailPage: React.FC<ScopeDetailPageProps> = ({ row }) => {
1011
const { t } = useTranslation()
1112
const theme = useContext(ThemeContext)
12-
const selectedTheme = theme.state.theme
13+
const selectedTheme = theme?.state?.theme || 'light'
1314

14-
function getBadgeTheme(status) {
15+
function getBadgeTheme(status: boolean | undefined): string {
1516
if (status) {
1617
return `primary-${selectedTheme}`
1718
} else {
1819
return 'dimmed'
1920
}
2021
}
22+
2123
return (
2224
<React.Fragment>
2325
<Container style={{ backgroundColor: customColors.whiteSmoke }}>
@@ -81,13 +83,13 @@ function ScopeDetailPage({ row }) {
8183
<Row>
8284
<Col sm={3}>{t('fields.attributes')}:</Col>
8385
<Col sm={9}>
84-
{Object.keys(row.attributes || []).map((item, key) => {
86+
{Object.keys(row.attributes || {}).map((item, key) => {
8587
return (
8688
<GluuFormDetailRow
8789
key={key}
8890
label={item}
8991
isBadge={true}
90-
value={String(row.attributes[item])}
92+
value={String(row.attributes?.[item as keyof typeof row.attributes])}
9193
doc_category={SCOPE}
9294
doc_entry={`attributes.${item}`}
9395
/>

0 commit comments

Comments
 (0)