Skip to content

Commit 401342b

Browse files
Merge pull request #1345 from devtron-labs/fix/modal-env_filter
feat(app_env_filter): empty state & name validation
2 parents 5cc247b + eeeaf2f commit 401342b

File tree

3 files changed

+94
-57
lines changed

3 files changed

+94
-57
lines changed

src/components/ApplicationGroup/Constants.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -183,4 +183,6 @@ export const GetBranchChangeStatus = (statusText: string): BulkResponseStatus =>
183183
default:
184184
return
185185
}
186-
}
186+
}
187+
188+
export const FILTER_NAME_REGEX = /^[a-z][a-z0-9-]{1,}[a-z0-9]$/

src/components/ApplicationGroup/CreateAppGroup.tsx

Lines changed: 89 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import {
44
CHECKBOX_VALUE,
55
ConditionalWrap,
66
Drawer,
7+
GenericEmptyState,
78
Progressing,
89
showError,
910
stopPropagation,
@@ -12,9 +13,10 @@ import { ReactComponent as Close } from '../../assets/icons/ic-close.svg'
1213
import { ReactComponent as Error } from '../../assets/icons/ic-warning.svg'
1314
import { ReactComponent as CheckIcon } from '../../assets/icons/ic-check.svg'
1415
import { ReactComponent as Abort } from '../../assets/icons/ic-abort.svg'
16+
import Info from '../../assets/icons/ic-info-outline-grey.svg'
1517
import { CreateGroupType, CreateTypeOfAppListType, FilterParentType } from './AppGroup.types'
1618
import SearchBar from './SearchBar'
17-
import { CreateGroupTabs, CREATE_GROUP_TABS } from './Constants'
19+
import { CreateGroupTabs, CREATE_GROUP_TABS, FILTER_NAME_REGEX } from './Constants'
1820
import { toast } from 'react-toastify'
1921
import { createEnvGroup } from './AppGroup.service'
2022
import { useParams } from 'react-router-dom'
@@ -57,6 +59,18 @@ export default function CreateAppGroup({
5759
}
5860
}
5961

62+
const validateName = (name: string) => {
63+
if (!name) {
64+
return
65+
}
66+
if (!FILTER_NAME_REGEX.test(name) || name.length > 30) {
67+
// regex doesnt check of max length = 30
68+
setShowErrorMsg(true)
69+
} else {
70+
setShowErrorMsg(false)
71+
}
72+
}
73+
6074
useEffect(() => {
6175
document.addEventListener('click', outsideClickHandler)
6276
return (): void => {
@@ -97,8 +111,8 @@ export default function CreateAppGroup({
97111
}
98112

99113
const onInputChange = (event): void => {
100-
setShowErrorMsg(true)
101114
if (event.target.name === 'name') {
115+
validateName(event.target.value)
102116
setAppGroupName(event.target.value)
103117
} else {
104118
setAppGroupDescription(event.target.value)
@@ -157,7 +171,22 @@ export default function CreateAppGroup({
157171
setAuthorizedAppList(_authorizedAppList)
158172
}
159173

174+
const renderEmptyState = (title?: string): JSX.Element => {
175+
return (
176+
<GenericEmptyState
177+
title={title}
178+
image={Info}
179+
imageClassName="h-20"
180+
styles={{ height: 'calc(100vh - 420px)' }}
181+
/>
182+
)
183+
}
184+
160185
const renderSelectedApps = (): JSX.Element => {
186+
const filteredAuthList = authorizedAppList.filter(
187+
(app) =>
188+
selectedAppsMap[app.id] && (!selectedAppSearchText || app.appName.indexOf(selectedAppSearchText) >= 0),
189+
)
161190
return (
162191
<div>
163192
<SearchBar
@@ -168,26 +197,22 @@ export default function CreateAppGroup({
168197
setSearchApplied={setSelectedAppSearchApplied}
169198
/>
170199
<div>
171-
{authorizedAppList
172-
.filter(
173-
(app) =>
174-
selectedAppsMap[app.id] &&
175-
(!selectedAppSearchText || app.appName.indexOf(selectedAppSearchText) >= 0),
176-
)
177-
.map((app) => {
178-
return (
179-
<div
180-
key={`selected-app-${app.id}`}
181-
className="flex left dc__hover-n50 p-8 fs-13 fw-4 cn-9 selected-app-row cursor"
182-
data-app-id={app.id}
183-
onClick={removeAppSelection}
184-
>
185-
<CheckIcon className="icon-dim-16 cursor check-icon scn-6 mr-8" />
186-
<Close className="icon-dim-16 cursor delete-icon mr-8" />
187-
<span>{app.appName}</span>
188-
</div>
189-
)
190-
})}
200+
{filteredAuthList.length <= 0
201+
? renderEmptyState('No matching results')
202+
: filteredAuthList.map((app) => {
203+
return (
204+
<div
205+
key={`selected-app-${app.id}`}
206+
className="flex left dc__hover-n50 p-8 fs-13 fw-4 cn-9 selected-app-row cursor"
207+
data-app-id={app.id}
208+
onClick={removeAppSelection}
209+
>
210+
<CheckIcon className="icon-dim-16 cursor check-icon scn-6 mr-8" />
211+
<Close className="icon-dim-16 cursor delete-icon mr-8" />
212+
<span>{app.appName}</span>
213+
</div>
214+
)
215+
})}
191216
{unauthorizedAppList.length > 0 && (
192217
<div className="dc__bold ml-4">
193218
{`You don't have admin/manager pemission for the following ${filterParentTypeMsg}.`}
@@ -217,6 +242,7 @@ export default function CreateAppGroup({
217242
}
218243

219244
const renderAllApps = (): JSX.Element => {
245+
const filteredAllApps = appList.filter((app) => !allAppSearchText || app.appName.indexOf(allAppSearchText) >= 0)
220246
return (
221247
<div>
222248
<SearchBar
@@ -227,36 +253,36 @@ export default function CreateAppGroup({
227253
setSearchApplied={setAllAppSearchApplied}
228254
/>
229255
<div>
230-
{appList
231-
.filter((app) => !allAppSearchText || app.appName.indexOf(allAppSearchText) >= 0)
232-
.map((app) => (
233-
<ConditionalWrap
234-
condition={unAuthorizedApps.get(app.appName) === true}
235-
wrap={(children) => (
236-
<Tippy
237-
key={`selected-app-${app.id}`}
238-
data-testid="env-tippy"
239-
className="default-tt w-200"
240-
arrow={false}
241-
placement="bottom-start"
242-
content={`You don't have admin/manager pemission for this ${filterParentTypeMsg}.`}
243-
>
244-
<div>{children}</div>
245-
</Tippy>
246-
)}
247-
>
248-
<Checkbox
249-
key={`app-${app.id}`}
250-
rootClassName="fs-13 pt-8 pr-8 pb-8 mb-0-imp dc__hover-n50"
251-
isChecked={unAuthorizedApps.get(app.appName) ? false : selectedAppsMap[app.id]}
252-
value={CHECKBOX_VALUE.CHECKED}
253-
onChange={() => toggleAppSelection(app.id)}
254-
disabled={unAuthorizedApps.get(app.appName) ? true : false}
255-
>
256-
{app.appName}
257-
</Checkbox>
258-
</ConditionalWrap>
259-
))}
256+
{filteredAllApps.length <= 0
257+
? renderEmptyState('No matching results')
258+
: filteredAllApps.map((app) => (
259+
<ConditionalWrap
260+
condition={unAuthorizedApps.get(app.appName) === true}
261+
wrap={(children) => (
262+
<Tippy
263+
key={`selected-app-${app.id}`}
264+
data-testid="env-tippy"
265+
className="default-tt w-200"
266+
arrow={false}
267+
placement="bottom-start"
268+
content={`You don't have admin/manager pemission for this ${filterParentTypeMsg}.`}
269+
>
270+
<div>{children}</div>
271+
</Tippy>
272+
)}
273+
>
274+
<Checkbox
275+
key={`app-${app.id}`}
276+
rootClassName="fs-13 pt-8 pr-8 pb-8 mb-0-imp dc__hover-n50"
277+
isChecked={unAuthorizedApps.get(app.appName) ? false : selectedAppsMap[app.id]}
278+
value={CHECKBOX_VALUE.CHECKED}
279+
onChange={() => toggleAppSelection(app.id)}
280+
disabled={unAuthorizedApps.get(app.appName) ? true : false}
281+
>
282+
{app.appName}
283+
</Checkbox>
284+
</ConditionalWrap>
285+
))}
260286
</div>
261287
</div>
262288
)
@@ -282,12 +308,17 @@ export default function CreateAppGroup({
282308
)
283309
}
284310

311+
// called when showErrorMsg is true
285312
const nameErrorMessage = (): string => {
286313
if (!appGroupName) {
287314
return 'Group name is required field'
288-
} else {
315+
}
316+
if (appGroupName.length > 30) {
289317
return 'Max 30 char is allowed in name'
290318
}
319+
if (!FILTER_NAME_REGEX.test(appGroupName)) {
320+
return 'Min 3 chars; Start with alphabet; End with alphanumeric; Use only lowercase; Allowed:(-); Do not use spaces'
321+
}
291322
}
292323

293324
const renderBodySection = (): JSX.Element => {
@@ -310,7 +341,7 @@ export default function CreateAppGroup({
310341
disabled={selectedAppGroup && !!selectedAppGroup.value}
311342
/>
312343

313-
{showErrorMsg && (!appGroupName || appGroupName.length > 30) && (
344+
{showErrorMsg && (
314345
<span className="form__error">
315346
<Error className="form__icon form__icon--error" />
316347
{nameErrorMessage()}
@@ -360,6 +391,10 @@ export default function CreateAppGroup({
360391

361392
const handleSave = async (e): Promise<void> => {
362393
e.preventDefault()
394+
if (showErrorMsg) {
395+
toast.error('Please fix the errors')
396+
return
397+
}
363398
if (!appGroupName || appGroupDescription?.length > 50) {
364399
setShowErrorMsg(true)
365400
return

src/css/base.scss

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3719,8 +3719,8 @@ textarea::placeholder {
37193719
}
37203720

37213721
.dc__disabled {
3722-
cursor: not-allowed !important;
3723-
opacity: 0.5;
3722+
cursor: not-allowed !important;
3723+
opacity: 0.5;
37243724
}
37253725

37263726
.dc__grabbable {

0 commit comments

Comments
 (0)