Skip to content

Commit efef56f

Browse files
authored
Merge pull request #1579 from devtron-labs/approval-via-email
feat: Approval via email
2 parents b7eaf29 + a406482 commit efef56f

File tree

5 files changed

+84
-40
lines changed

5 files changed

+84
-40
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.56",
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/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/css/base.scss

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3220,6 +3220,10 @@ textarea,
32203220
min-height: 400px;
32213221
}
32223222

3223+
.mh-440 {
3224+
min-height: 440px;
3225+
}
3226+
32233227
.mh-500 {
32243228
min-height: 500px;
32253229
}

src/css/icons.scss

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -188,6 +188,11 @@
188188
height: 48px;
189189
}
190190

191+
.icon-dim-72 {
192+
width: 72px;
193+
height: 72px;
194+
}
195+
191196
.icon-dim-80 {
192197
width: 80px;
193198
height: 80px;

0 commit comments

Comments
 (0)