Skip to content

Commit 14f8f68

Browse files
committed
Merge branch 'main' into feat-secret-decode
2 parents 87f366a + 1a8b52d commit 14f8f68

File tree

25 files changed

+294
-232
lines changed

25 files changed

+294
-232
lines changed

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
"private": true,
55
"homepage": "/dashboard",
66
"dependencies": {
7-
"@devtron-labs/devtron-fe-common-lib": "0.0.53",
7+
"@devtron-labs/devtron-fe-common-lib": "0.0.58",
88
"@rjsf/core": "^5.13.3",
99
"@rjsf/utils": "^5.13.3",
1010
"@rjsf/validator-ajv8": "^5.13.3",

src/App.tsx

Lines changed: 63 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -17,14 +17,16 @@ import {
1717
ToastBody,
1818
ToastBody3 as UpdateToast,
1919
ErrorBoundary,
20+
importComponentFromFELibrary,
2021
} from './components/common'
21-
import { showError, BreadcrumbStore, Reload, DevtronProgressing } from '@devtron-labs/devtron-fe-common-lib'
22+
import { showError, BreadcrumbStore, Reload, DevtronProgressing, APPROVAL_MODAL_TYPE } from '@devtron-labs/devtron-fe-common-lib'
2223
import * as serviceWorker from './serviceWorker'
2324
import Hotjar from './components/Hotjar/Hotjar'
2425
import { validateToken } from './services/service'
2526

2627
const NavigationRoutes = lazy(() => import('./components/common/navigation/NavigationRoutes'))
2728
const Login = lazy(() => import('./components/login/Login'))
29+
const GenericDirectApprovalModal = importComponentFromFELibrary('GenericDirectApprovalModal')
2830

2931
toast.configure({
3032
autoClose: 3000,
@@ -46,9 +48,12 @@ export default function App() {
4648
const [bgUpdated, setBGUpdated] = useState(false)
4749
const [validating, setValidating] = useState(true)
4850
const [forceUpdateOnLocationChange, setForceUpdateOnLocationChange] = useState(false)
51+
const [approvalToken, setApprovalToken] = useState<string>('')
52+
const [approvalType, setApprovalType] = useState<APPROVAL_MODAL_TYPE>(APPROVAL_MODAL_TYPE.CONFIG)
4953
const location = useLocation()
5054
const { push } = useHistory()
5155
const didMountRef = useRef(false)
56+
const isDirectApprovalNotification = location.pathname && location.pathname.includes('approve') && location.search && location.search.includes(`?token=${approvalToken}`)
5257

5358
function onlineToast(toastBody: JSX.Element, options) {
5459
if (onlineToastRef.current && toast.isActive(onlineToastRef.current)) {
@@ -84,30 +89,52 @@ export default function App() {
8489
}
8590
}
8691

87-
useEffect(() => {
88-
async function validation() {
89-
try {
90-
await validateToken()
91-
defaultRedirection()
92-
} catch (err: any) {
93-
// push to login without breaking search
94-
if (err?.code === 401) {
95-
const loginPath = URLS.LOGIN_SSO
96-
const newSearch = location.pathname.includes(URLS.LOGIN_SSO)
97-
? location.search
98-
: `?continue=${location.pathname}`
99-
push(`${loginPath}${newSearch}`)
100-
} else {
101-
setErrorPage(true)
102-
showError(err)
103-
}
104-
} finally {
105-
setValidating(false)
92+
const redirectToDirectApprovalNotification = (): void => {
93+
setValidating(false)
94+
if (location.pathname && location.pathname.includes('deployment')) {
95+
setApprovalType(APPROVAL_MODAL_TYPE.DEPLOYMENT)
96+
} else {
97+
setApprovalType(APPROVAL_MODAL_TYPE.CONFIG)
98+
}
99+
100+
let queryString = new URLSearchParams(location.search)
101+
let token = queryString.get('token')
102+
if (token) {
103+
setApprovalToken(token)
104+
}
105+
}
106+
107+
async function validation() {
108+
try {
109+
await validateToken()
110+
defaultRedirection()
111+
} catch (err: any) {
112+
// push to login without breaking search
113+
if (err?.code === 401) {
114+
const loginPath = URLS.LOGIN_SSO
115+
const newSearch = location.pathname.includes(URLS.LOGIN_SSO)
116+
? location.search
117+
: `?continue=${location.pathname}`
118+
push(`${loginPath}${newSearch}`)
119+
} else {
120+
setErrorPage(true)
121+
showError(err)
106122
}
123+
} finally {
124+
setValidating(false)
107125
}
126+
}
127+
128+
129+
useEffect(() => {
108130
// If not K8S_CLIENT then validateToken otherwise directly redirect
109131
if (!window._env_.K8S_CLIENT) {
132+
// By Passing validations for direct email approval notifications
133+
if (isDirectApprovalNotification) {
134+
redirectToDirectApprovalNotification()
135+
} else {
110136
validation()
137+
}
111138
} else {
112139
setValidating(false)
113140
defaultRedirection()
@@ -207,7 +234,7 @@ export default function App() {
207234
<Suspense fallback={null}>
208235
{validating ? (
209236
<div className="full-height-width">
210-
<DevtronProgressing parentClasses="h-100 flex bcn-0" classes="icon-dim-80"/>
237+
<DevtronProgressing parentClasses="h-100 flex bcn-0" classes="icon-dim-80" />
211238
</div>
212239
) : (
213240
<>
@@ -218,13 +245,21 @@ export default function App() {
218245
) : (
219246
<ErrorBoundary>
220247
<BreadcrumbStore>
221-
<Switch>
222-
{!window._env_.K8S_CLIENT && <Route path={`/login`} component={Login} />}
223-
<Route path="/" render={() => <NavigationRoutes />} />
224-
<Redirect
225-
to={window._env_.K8S_CLIENT ? '/' : `${URLS.LOGIN_SSO}${location.search}`}
226-
/>
227-
</Switch>
248+
<Switch>
249+
{isDirectApprovalNotification && GenericDirectApprovalModal && (
250+
<Route exact path={`/${approvalType?.toLocaleLowerCase()}/approve`}>
251+
<GenericDirectApprovalModal
252+
approvalType={approvalType}
253+
approvalToken={approvalToken}
254+
/>
255+
</Route>
256+
)}
257+
{!window._env_.K8S_CLIENT && <Route path={`/login`} component={Login} />}
258+
<Route path="/" render={() => <NavigationRoutes />} />
259+
<Redirect
260+
to={window._env_.K8S_CLIENT ? '/' : `${URLS.LOGIN_SSO}${location.search}`}
261+
/>
262+
</Switch>
228263
<div id="full-screen-modal"></div>
229264
<div id="visible-modal"></div>
230265
<div id="visible-modal-2"></div>

src/components/ApplicationGroup/CreateAppGroup.tsx

Lines changed: 5 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import {
33
Checkbox,
44
CHECKBOX_VALUE,
55
ConditionalWrap,
6+
CustomInput,
67
Drawer,
78
GenericEmptyState,
89
Progressing,
@@ -327,25 +328,17 @@ export default function CreateAppGroup({
327328
return (
328329
<div className="p-20 bcn-0 dc__overflow-auto" style={{ height: 'calc(100vh - 128px)' }}>
329330
<div className="form__row mb-16">
330-
<span className="form__label dc__required-field">Name</span>
331-
<input
331+
<CustomInput
332+
label="Name"
332333
tabIndex={1}
333-
className="form__input"
334-
autoComplete="off"
335334
placeholder="Enter filter name"
336-
type="text"
337335
value={appGroupName}
338336
name="name"
339337
onChange={onInputChange}
340338
disabled={selectedAppGroup && !!selectedAppGroup.value}
339+
isRequiredField={true}
340+
error={showErrorMsg && nameErrorMessage()}
341341
/>
342-
343-
{showErrorMsg && (
344-
<span className="form__error">
345-
<Error className="form__icon form__icon--error" />
346-
{nameErrorMessage()}
347-
</span>
348-
)}
349342
</div>
350343
<div className="form__row mb-16">
351344
<span className="form__label">Description (Max 50 characters)</span>

src/components/ApplicationGroup/Details/TriggerView/EnvTriggerView.tsx

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -140,12 +140,14 @@ export default function EnvTriggerView({ filteredAppIds, isVirtualEnv }: AppGrou
140140
const handledLocation = useRef(false)
141141
const abortControllerRef = useRef(new AbortController())
142142

143-
useEffect(
144-
() => () => {
145-
handledLocation.current = false
146-
},
147-
[],
148-
)
143+
useEffect(() => {
144+
if (ApprovalMaterialModal) {
145+
getConfigs()
146+
}
147+
return () => {
148+
handledLocation.current = false
149+
}
150+
}, [])
149151

150152
useEffect(() => {
151153
if (envId) {
@@ -165,20 +167,18 @@ export default function EnvTriggerView({ filteredAppIds, isVirtualEnv }: AppGrou
165167
// URL Encoding for Bulk is not planned as of now
166168
setShowBulkCDModal(false)
167169
if (location.search.includes('approval-node')) {
168-
getConfigs()
169170
const searchParams = new URLSearchParams(location.search)
170171
const nodeId = Number(searchParams.get('approval-node'))
171172
if (!isNaN(nodeId)) {
172173
onClickCDMaterial(nodeId, DeploymentNodeType.CD, true)
173-
}
174-
else {
174+
} else {
175175
toast.error('Invalid node id')
176176
history.push({
177177
search: '',
178178
})
179179
}
180-
}
181-
else if (location.search.includes('rollback-node')) {
180+
}
181+
else if (location.search.includes('rollback-node')) {
182182
const searchParams = new URLSearchParams(location.search)
183183
const nodeId = Number(searchParams.get('rollback-node'))
184184
if (!isNaN(nodeId)) {

src/components/CustomChart/UploadChartModal.tsx

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import React, { useRef, useState } from 'react'
22
import { ButtonWithLoader } from '../common'
3-
import { showError, VisibleModal } from '@devtron-labs/devtron-fe-common-lib'
3+
import { CustomInput, noop, showError, VisibleModal } from '@devtron-labs/devtron-fe-common-lib'
44
import { ReactComponent as CloseIcon } from '../../assets/icons/ic-close.svg'
55
import { uploadChart, validateChart } from './customChart.service'
66
import errorImage from '../../assets/img/ic_upload_chart_error.png'
@@ -121,14 +121,14 @@ export default function UploadChartModal({ closeUploadPopup }: UploadChartModalT
121121
)}
122122
<div>
123123
<div>
124-
<span className="fs-13 fw-4 cn-7 dc__required-field">
125-
Chart Name
126-
</span>
127-
<input
128-
type="text"
129-
className="w-100 br-4 en-2 bw-1 mt-6 form__input"
124+
<CustomInput
125+
label="Chart Name"
126+
name="chartName"
127+
onChange={noop}
128+
rootClassName="w-100 br-4 en-2 bw-1 mt-6 form__input"
130129
disabled
131130
value={chartDetail.chartName}
131+
isRequiredField={true}
132132
/>
133133
</div>
134134
<div className="mt-16">

src/components/app/create/CreateApp.tsx

Lines changed: 5 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import {
1414
RadioGroup,
1515
RadioGroupItem,
1616
noop,
17+
CustomInput,
1718
} from '@devtron-labs/devtron-fe-common-lib'
1819
import { AddNewAppProps, AddNewAppState } from '../types'
1920
import { ViewType, getAppComposeURL, APP_COMPOSE_STAGE, AppCreationType } from '../../../config'
@@ -307,31 +308,19 @@ export class AddNewApp extends Component<AddNewAppProps, AddNewAppState> {
307308
<div className="scrollable-content p-20">
308309
<div className="form__row">
309310
<div className={`${this.props.isJobView ? 'mb-12' : ''}`}>
310-
<span className="form__label dc__required-field">
311-
{this.props.isJobView ? 'Job' : 'App'} Name
312-
</span>
313-
<input
311+
<CustomInput
314312
ref={(node) => (this._inputAppName = node)}
315313
data-testid={`${this.props.isJobView ? 'job' : 'app'}-name-textbox`}
316-
className="form__input"
317-
type="text"
318314
name="app-name"
315+
label={`${this.props.isJobView ? 'Job' : 'App'} Name`}
319316
value={this.state.form.appName}
320317
placeholder={`e.g. my-first-${this.props.isJobView ? 'job' : 'app'}`}
321-
autoComplete="off"
322318
autoFocus={true}
323319
tabIndex={1}
324320
onChange={this.handleAppname}
325-
required
321+
isRequiredField={true}
322+
error={appNameErrors && !this.state.isValid.appName && errorObject[0].message}
326323
/>
327-
<span className="form__error">
328-
{appNameErrors && !this.state.isValid.appName ? (
329-
<>
330-
<Error className="form__icon form__icon--error" />
331-
{errorObject[0].message} <br />
332-
</>
333-
) : null}
334-
</span>
335324
</div>
336325
{!this.props.isJobView && (
337326
<span className="form__text-field-info form__text-field-info--create-app">

src/components/cdPipeline/DeleteCDNode.tsx

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import React, { useState } from 'react'
2-
import { DeleteDialog, DeploymentAppTypes, ForceDeleteDialog } from '@devtron-labs/devtron-fe-common-lib'
2+
import { CustomInput, DeleteDialog, DeploymentAppTypes, ForceDeleteDialog } from '@devtron-labs/devtron-fe-common-lib'
33
import ClusterNotReachableDailog from '../common/ClusterNotReachableDailog/ClusterNotReachableDialog'
44
import { DELETE_ACTION } from '../../config'
55
import { DeleteCDNodeProps, DeleteDialogType } from './types'
@@ -83,13 +83,13 @@ export default function DeleteCDNode({
8383
disabled={showConfirmationBar && deleteInput !== deleteTitleName}
8484
>
8585
{showConfirmationBar && (
86-
<input
87-
type="text"
86+
<CustomInput
8887
disabled={isLoading}
89-
className="form__input mt-12"
88+
rootClassName="mt-12"
9089
data-testId="delete-dialog-input"
9190
placeholder={`Please type ${deleteTitleName} to confirm`}
9291
value={deleteInput}
92+
name="delete-input"
9393
onChange={handleDeleteInputChange}
9494
/>
9595
)}

src/components/charts/AdvancedConfig.tsx

Lines changed: 14 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import React, { useState, useEffect, useMemo } from 'react'
2-
import { showError, Progressing, VisibleModal, useAsync } from '@devtron-labs/devtron-fe-common-lib'
2+
import { showError, Progressing, VisibleModal, useAsync, CustomInput } from '@devtron-labs/devtron-fe-common-lib'
33
import { Select, mapByKey, useKeyDown, Info, Pencil } from '../common'
44
import CodeEditor from '../CodeEditor/CodeEditor'
55
import { getEnvironmentListMin } from '../../services/service'
@@ -11,6 +11,7 @@ import { ReactComponent as LockIcon } from '../../assets/icons/ic-locked.svg'
1111
import { ReactComponent as WarningIcon } from '../../assets/icons/ic-alert-triangle.svg';
1212
import { useHistory } from 'react-router'
1313
import { getSavedValuesListURL } from './charts.helper'
14+
import { renderAdditionalErrorInfo } from './charts.util'
1415

1516
interface AdvancedConfig extends AdvancedConfigHelpers {
1617
chart: ChartGroupEntry;
@@ -141,14 +142,18 @@ const AdvancedConfig: React.FC<AdvancedConfig> = ({ chart, index, fetchChartValu
141142
<div className="advanced-config flex">
142143
<form action="" className="advanced-config__form">
143144
<h1 className="form__title form__title--mb-24" data-testid="advanced-option-heading">{chartName}</h1>
144-
{handleNameChange && <div className="flex column left top mb-16">
145-
<label htmlFor="" className="form__label" data-testid="advanced-option-app-name">App name*</label>
146-
<input type="text" autoComplete="off" className={`form__input ${appName?.error ? 'form__input--error' : ''}`} value={appName.value} onChange={e => handleNameChange(index, e.target.value)} data-testid="advanced-option-app-name-box"/>
147-
{appName?.error &&
148-
<span className="form__error flex left">
149-
<WarningIcon className="mr-5 icon-dim-16" />{appName?.error || ""}
150-
{appName.suggestedName && <span>. Suggested name: <span className="anchor pointer" onClick={e => handleNameChange(index, appName.suggestedName)}>{appName.suggestedName}</span></span>}
151-
</span>}
145+
{handleNameChange && <div className="mb-16">
146+
<CustomInput
147+
name="appName"
148+
label="App name"
149+
rootClassName={`${appName?.error ? 'form__input--error' : ''}`}
150+
value={appName.value}
151+
onChange={(e) => handleNameChange(index, e.target.value)}
152+
data-testid="advanced-option-app-name-box"
153+
isRequiredField={true}
154+
error={appName?.error}
155+
additionalErrorInfo={renderAdditionalErrorInfo(handleNameChange, appName.suggestedName, index)}
156+
/>
152157
</div>}
153158
{handleEnvironmentChange && <div className="flex top mb-16">
154159
<div className="flex column half left top">

src/components/charts/charts.util.tsx

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -99,4 +99,21 @@ export const QueryParams = {
9999
export const PaginationParams = {
100100
pageOffset: 0,
101101
pageSize: 20
102-
}
102+
}
103+
104+
export const renderAdditionalErrorInfo = (
105+
handleNameChange: (index: number, suggestedName: string) => void,
106+
suggestedName: string,
107+
index: number,
108+
): JSX.Element => {
109+
return (
110+
suggestedName && (
111+
<>
112+
. Suggested Name:
113+
<span className="anchor pointer" onClick={(e) => handleNameChange(index, suggestedName)}>
114+
{suggestedName}
115+
</span>
116+
</>
117+
)
118+
)
119+
}

0 commit comments

Comments
 (0)