@devtron-labs/devtron-fe-common-lib 0.0.10-beta-9 → 0.0.10-beta-13

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@devtron-labs/devtron-fe-common-lib",
3
- "version": "0.0.10-beta-9",
3
+ "version": "0.0.10-beta-13",
4
4
  "description": "Supporting common component library",
5
5
  "main": "dist/index.js",
6
6
  "scripts": {
@@ -0,0 +1,3 @@
1
+ <svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
2
+ <path d="M12.6667 4C12.4899 4 12.3203 4.07024 12.1953 4.19526C12.0703 4.32029 12.0001 4.48986 12.0001 4.66667V7.33333C12.0001 7.51014 11.9298 7.67971 11.8048 7.80474C11.6798 7.92976 11.5102 8 11.3334 8H4.94005L5.80672 7.14C5.93226 7.01446 6.00278 6.8442 6.00278 6.66667C6.00278 6.48913 5.93226 6.31887 5.80672 6.19333C5.68118 6.0678 5.51092 5.99727 5.33339 5.99727C5.15585 5.99727 4.98559 6.0678 4.86005 6.19333L2.86005 8.19333C2.79936 8.25674 2.75178 8.3315 2.72005 8.41333C2.65337 8.57564 2.65337 8.75769 2.72005 8.92C2.75178 9.00183 2.79936 9.0766 2.86005 9.14L4.86005 11.14C4.92203 11.2025 4.99576 11.2521 5.077 11.2859C5.15824 11.3198 5.24538 11.3372 5.33339 11.3372C5.42139 11.3372 5.50853 11.3198 5.58977 11.2859C5.67101 11.2521 5.74474 11.2025 5.80672 11.14C5.86921 11.078 5.9188 11.0043 5.95265 10.9231C5.98649 10.8418 6.00392 10.7547 6.00392 10.6667C6.00392 10.5787 5.98649 10.4915 5.95265 10.4103C5.9188 10.329 5.86921 10.2553 5.80672 10.1933L4.94005 9.33333H11.3334C11.8638 9.33333 12.3725 9.12262 12.7476 8.74755C13.1227 8.37247 13.3334 7.86377 13.3334 7.33333V4.66667C13.3334 4.48986 13.2631 4.32029 13.1381 4.19526C13.0131 4.07024 12.8435 4 12.6667 4Z" fill="#3B444C"/>
3
+ </svg>
@@ -0,0 +1,6 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 20 20">
2
+ <g fill="none" fill-rule="evenodd">
3
+ <path fill="#FFF" d="M5 5H15V15H5z"/>
4
+ <path fill="#F33E3E" d="M10 18.333c-4.6 0-8.333-3.733-8.333-8.333S5.4 1.667 10 1.667 18.333 5.4 18.333 10 14.6 18.333 10 18.333zm0-12.5c-.46 0-.833.373-.833.834V10c0 .46.373.833.833.833.46 0 .833-.373.833-.833V6.667c0-.46-.373-.834-.833-.834zm0 6.667c-.46 0-.833.373-.833.833 0 .46.373.834.833.834.46 0 .833-.373.833-.834 0-.46-.373-.833-.833-.833z"/>
5
+ </g>
6
+ </svg>
@@ -1,6 +1,5 @@
1
1
  import React, { useEffect, useMemo, useRef, useState } from 'react'
2
2
  import { ReactComponent as Add } from '../Assets/Icon/ic-add.svg'
3
- import Creatable from 'react-select/creatable'
4
3
  import { ReactComponent as Close } from '../Assets/Icon/ic-cross.svg'
5
4
  import { ReactComponent as QuestionFilled } from '../Assets/Icon/ic-help.svg'
6
5
  import { ReactComponent as Question } from '../Assets/Icon/ic-help-outline.svg'
@@ -11,11 +10,14 @@ import { ReactComponent as Minus } from '../Assets/Icon/ic-minus.svg'
11
10
 
12
11
  import { ReactComponent as Info } from '../Assets/Icon/ic-info-filled.svg'
13
12
  import { ReactComponent as Error } from '../Assets/Icon/ic-warning.svg'
13
+ import { ReactComponent as Warning } from '../Assets/Icon/ic-error-exclamation.svg'
14
+ import { ReactComponent as Enter } from '../Assets/Icon/ic-enter.svg'
14
15
  import { ImageButtonType, ImageTaggingContainerType, ReleaseTag, TippyTheme } from './Types'
15
16
  import { showError, stopPropagation } from './Helper'
16
17
  import { TippyCustomized } from './TippyCustomized'
17
18
  import { setImageTags, getUserRole } from './Common.service'
18
19
  import Tippy from '@tippyjs/react'
20
+ import { ServerErrors } from './ServerError'
19
21
 
20
22
  export const ImageTagsContainer = ({
21
23
  ciPipelineId,
@@ -41,6 +43,8 @@ export const ImageTagsContainer = ({
41
43
  const [hardDeleteTags, setHardDeleteTags] = useState<ReleaseTag[]>([])
42
44
  const [isSuperAdmin, setSuperAdmin] = useState<boolean>(false)
43
45
  const [descriptionValidationMessage, setDescriptionValidationMessage] = useState<string>('')
46
+ const [textInput, setTextInput] = useState<string>('')
47
+ const [saveData, setSaveData] = useState<boolean>(false)
44
48
 
45
49
  useEffect(() => {
46
50
  initialise()
@@ -54,6 +58,13 @@ export const ImageTagsContainer = ({
54
58
  setExistingTags(appReleaseTagNames ? appReleaseTagNames : [])
55
59
  }, [appReleaseTagNames])
56
60
 
61
+ useEffect(() => {
62
+ if (saveData) {
63
+ handleSave()
64
+ setSaveData(false)
65
+ }
66
+ }, [saveData])
67
+
57
68
  async function initialise() {
58
69
  try {
59
70
  const userRole = await getUserRole()
@@ -74,15 +85,6 @@ export const ImageTagsContainer = ({
74
85
  }
75
86
  }
76
87
 
77
- const CreatableComponents = useMemo(
78
- () => ({
79
- DropdownIndicator: () => null,
80
- IndicatorSeparator: () => null,
81
- Menu: () => null,
82
- }),
83
- [],
84
- )
85
-
86
88
  const handleEditClick = () => {
87
89
  setIsEditing(!isEditing)
88
90
  if (typeof toggleCardMode === 'function') {
@@ -127,9 +129,7 @@ export const ImageTagsContainer = ({
127
129
  if (displayedTags[i].tagName.toLowerCase() === lowercaseValue) isTagExistsInDisplayedTags = true
128
130
  }
129
131
  if (isTagExistsInExistingTags || isTagExistsInDisplayedTags || lowercaseValue === 'latest') {
130
- setTagErrorMessage(
131
- 'This tag is already being used in this application',
132
- )
132
+ setTagErrorMessage('This tag is already being used in this application')
133
133
  return
134
134
  }
135
135
  const newTag: ReleaseTag = {
@@ -138,10 +138,12 @@ export const ImageTagsContainer = ({
138
138
  appId: 0,
139
139
  deleted: false,
140
140
  artifactId: 0,
141
+ duplicateTag: false,
141
142
  }
142
143
  setCreateTags([...createTags, newTag])
143
144
  setDisplayedTags([...displayedTags, newTag])
144
145
  setShowTagsWarning(true)
146
+ setTextInput('')
145
147
  }
146
148
 
147
149
  const handleTagSoftDelete = (index) => {
@@ -179,7 +181,34 @@ export const ImageTagsContainer = ({
179
181
  }
180
182
  }
181
183
 
184
+ const errorStateHandling = (err) => {
185
+ err.forEach(({ userMessage }) => {
186
+ const tagList = createTags.map((tags) => {
187
+ if (userMessage?.appReleaseTags?.includes(tags.tagName)) {
188
+ return {
189
+ ...tags,
190
+ duplicateTag: true,
191
+ }
192
+ }
193
+ return tags
194
+ })
195
+ setCreateTags(tagList)
196
+
197
+ const displayList = displayedTags.map((obj) => {
198
+ const matchingTag = tagList.find((tag) => tag.tagName === obj.tagName)
199
+ if (matchingTag) {
200
+ return { ...obj, duplicateTag: matchingTag.duplicateTag }
201
+ }
202
+ return obj
203
+ })
204
+
205
+ setDisplayedTags(displayList)
206
+ })
207
+ }
208
+
182
209
  const handleSave = async () => {
210
+ if (tagErrorMessage) return
211
+
183
212
  const payload = {
184
213
  createTags: createTags,
185
214
  softDeleteTags: softDeleteTags,
@@ -219,7 +248,22 @@ export const ImageTagsContainer = ({
219
248
  setTagErrorMessage('')
220
249
  })
221
250
  .catch((err) => {
222
- showError(err)
251
+ // Fix toast message
252
+ if (err.errors?.[0]?.userMessage?.appReleaseTags?.length) {
253
+ const customError: ServerErrors = {
254
+ ...err,
255
+ errors: [
256
+ {
257
+ ...err.errors?.[0],
258
+ userMessage: err.errors?.[0]?.internalMessage,
259
+ },
260
+ ],
261
+ }
262
+ showError(customError)
263
+ errorStateHandling(err.errors)
264
+ } else {
265
+ showError(err)
266
+ }
223
267
  })
224
268
  }
225
269
 
@@ -248,7 +292,7 @@ export const ImageTagsContainer = ({
248
292
  return (
249
293
  <div className="h-250 fs-13 dc__overflow-scroll p-12">
250
294
  <div>Release tags allow you to tag container images with readable and relatable tags eg. v1.0.</div>
251
- <ul className='pl-20 mt-8'>
295
+ <ul className="pl-20 mt-8">
252
296
  <li>
253
297
  A release tag can only be added if a workflow has CD pipelines deploying to Production
254
298
  environments.
@@ -268,7 +312,7 @@ export const ImageTagsContainer = ({
268
312
 
269
313
  if (newDescription === '' && displayedTags.length === 0 && !isEditing) {
270
314
  return tagsEditable ? (
271
- <div className="bcn-0 mt-8">
315
+ <div className="bcn-0 mt-8 add-image-button">
272
316
  <AddImageButton handleEditClick={handleEditClick} />
273
317
  </div>
274
318
  ) : (
@@ -276,6 +320,29 @@ export const ImageTagsContainer = ({
276
320
  )
277
321
  }
278
322
 
323
+ const handleKeydown = (event) => {
324
+ if (event.key === 'Enter' && event.target.value.trim()) {
325
+ handleTagCreate(event.target.value.trim())
326
+ }
327
+ }
328
+
329
+ const setInputValue = (e) => {
330
+ setTextInput(e.target.value)
331
+ }
332
+
333
+ const enterTagCreate = () => {
334
+ if (textInput.trim()) {
335
+ handleTagCreate(textInput.trim())
336
+ }
337
+ }
338
+
339
+ const onClickSave = () => {
340
+ if (textInput.trim()) {
341
+ handleTagCreate(textInput)
342
+ }
343
+ setSaveData(true)
344
+ }
345
+
279
346
  return (
280
347
  <div className="mt-8">
281
348
  {isEditing && tagsEditable ? (
@@ -284,13 +351,30 @@ export const ImageTagsContainer = ({
284
351
  <span>Release tags (eg. v1.0)</span>
285
352
  <div className="flex row ml-0">{renderInfoCard()}</div>
286
353
  </div>
287
- <div className="mt-6" data-testid="add-tag-text-area">
288
- <Creatable
354
+ <div className="mt-6 dc__position-rel-imp" data-testid="add-tag-text-area">
355
+ <input
356
+ className="form__input"
357
+ value={textInput}
358
+ onChange={setInputValue}
289
359
  placeholder="Type a tag and press enter"
290
- onCreateOption={handleTagCreate}
360
+ onKeyDown={handleKeydown}
291
361
  ref={creatableRef}
292
- components={CreatableComponents}
293
362
  />
363
+ {textInput.length > 0 && (
364
+ <div
365
+ onClick={enterTagCreate}
366
+ className="dc__position-abs bcn-0 flex cursor pt-2 pb-2 pr-4 pl-4 br-2 cn-7 dc__border dc__border-bottom-2"
367
+ style={{
368
+ top: '50%',
369
+ fontFamily: 'monospace',
370
+ right: '8px',
371
+ transform: 'translateY(-50%)',
372
+ }}
373
+ >
374
+ <Enter className="icon-dim-16 mr-4" />
375
+ Enter
376
+ </div>
377
+ )}
294
378
  </div>
295
379
 
296
380
  {tagErrorMessage && (
@@ -317,6 +401,7 @@ export const ImageTagsContainer = ({
317
401
  tagId={tag.id}
318
402
  softDeleteTags={softDeleteTags}
319
403
  isSuperAdmin={isSuperAdmin}
404
+ duplicateTag={tag?.duplicateTag}
320
405
  />
321
406
  ))}
322
407
  </div>
@@ -327,7 +412,10 @@ export const ImageTagsContainer = ({
327
412
  </div>
328
413
  )}
329
414
  <div className="cn-7 mt-12">Comment</div>
330
- <div className="flex left flex-wrap dc__gap-8 w-100 mt-6 mb-12" data-testid="add-image-comment-text-area">
415
+ <div
416
+ className="flex left flex-wrap dc__gap-8 w-100 mt-6 mb-12"
417
+ data-testid="add-image-comment-text-area"
418
+ >
331
419
  <textarea
332
420
  value={newDescription}
333
421
  onChange={handleDescriptionChange}
@@ -358,7 +446,7 @@ export const ImageTagsContainer = ({
358
446
  type="button"
359
447
  onClick={(e) => {
360
448
  stopPropagation(e)
361
- handleSave()
449
+ onClickSave()
362
450
  }}
363
451
  >
364
452
  Save
@@ -391,6 +479,7 @@ export const ImageTagsContainer = ({
391
479
  tagId={tag.id}
392
480
  softDeleteTags={softDeleteTags}
393
481
  isSuperAdmin={isSuperAdmin}
482
+ duplicateTag={tag?.duplicateTag}
394
483
  />
395
484
  ))}
396
485
  </div>
@@ -423,58 +512,60 @@ export const ImageTagButton = ({
423
512
  tagId,
424
513
  softDeleteTags,
425
514
  isSuperAdmin,
515
+ duplicateTag,
426
516
  }: ImageButtonType) => {
427
517
  const IconComponent = isSoftDeleted ? Redo : Minus
518
+ const isInSoftDeleteTags = isSoftDeleted && softDeleteTags.some((tag) => tag.tagName === text)
519
+ const canTagBeHardDelete = tagId === 0 || isSuperAdmin
428
520
 
429
- const [isHovered, setIsHovered] = useState(false)
430
- const handleMouseEnter = () => {
431
- if (isEditing) {
432
- setIsHovered(true)
433
- }
434
- }
435
- const handleMouseLeave = () => {
436
- if (isEditing) {
437
- setIsHovered(false)
521
+ const tabColor = () => {
522
+ if (duplicateTag) {
523
+ return 'cr-5 bcr-1 er-2'
524
+ } else if (isSoftDeleted) {
525
+ return 'cy-7 bcy-1 dc__strike-through ey-2'
526
+ } else {
527
+ return 'cn-9'
438
528
  }
439
529
  }
440
530
 
441
- const isInSoftDeleteTags = isSoftDeleted && softDeleteTags.some((tag) => tag.tagName === text)
442
- const canTagBeHardDelete = tagId === 0 || isSuperAdmin
443
-
444
531
  return (
445
532
  <div
446
- className={`br-4 en-2 bw-1 dc__w-fit-content dc__word-wrap-anywhere mr-8 bcn-0 flex h-24 ${
447
- isSoftDeleted ? 'cy-7 bcy-1 dc__strike-through ey-2' : ''
448
- }`}
449
- onMouseEnter={handleMouseEnter}
450
- onMouseLeave={handleMouseLeave}
533
+ className={`br-4 en-2 bw-1 mr-8 bcn-0 mb-4 pt-2 pr-8 pb-2 pl-8 tag-class flex ${
534
+ isEditing ? 'icon-hover' : ''
535
+ } ${tabColor()}`}
451
536
  >
452
- <div className="flex pt-2 pl-8 pr-8 pb-2">
453
- {isHovered && isEditing && (isInSoftDeleteTags || (tagId !== 0 && !isSoftDeleted)) && (
454
- <Tippy
455
- className="default-tt"
456
- arrow={true}
457
- placement="top"
458
- content={isInSoftDeleteTags ? 'Restore tag' : 'Soft delete tag'}
537
+ {duplicateTag ? (
538
+ <Warning className="icon-dim-12 mr-4" />
539
+ ) : (
540
+ <Tippy
541
+ className="default-tt"
542
+ arrow={true}
543
+ placement="top"
544
+ content={isInSoftDeleteTags ? 'Restore tag' : 'Soft delete tag'}
545
+ >
546
+ <div
547
+ className={`action-icon mr-4 lh-16 pt-3 ${
548
+ isInSoftDeleteTags || (tagId !== 0 && !isSoftDeleted) ? 'show-icon' : ''
549
+ }`}
459
550
  >
460
551
  <IconComponent
461
- className={`icon-dim-12 mr-4 cursor ${isSoftDeleted ? 'scn-6' : 'fcn-6'}`}
552
+ className={`icon-dim-12 cursor${isSoftDeleted ? 'scn-6' : 'fcn-6'} `}
462
553
  data-testid={`${text}-tag-soft-delete`}
463
554
  onClick={onSoftDeleteClick}
464
555
  />
465
- </Tippy>
466
- )}
467
- {text}
468
- {isHovered && isEditing && canTagBeHardDelete && (
469
- <Tippy className="default-tt" arrow={true} placement="top" content="Remove tag">
470
- <Close
471
- className="icon-dim-12 ml-4 fcn-6 cn-5 cursor"
472
- data-testid={`${text}-tag-hard-delete`}
473
- onClick={onHardDeleteClick}
474
- />
475
- </Tippy>
476
- )}
477
- </div>
556
+ </div>
557
+ </Tippy>
558
+ )}
559
+ {text}
560
+ <Tippy className="default-tt" arrow={true} placement="top" content="Remove tag">
561
+ <div className={`action-icon ml-4 lh-16 pt-3 ${canTagBeHardDelete ? 'show-icon' : ''}`}>
562
+ <Close
563
+ className="icon-dim-12 fcn-6 cursor"
564
+ data-testid={`${text}-tag-hard-delete`}
565
+ onClick={onHardDeleteClick}
566
+ />
567
+ </div>
568
+ </Tippy>
478
569
  </div>
479
570
  )
480
571
  }
@@ -341,6 +341,7 @@ export interface ReleaseTag {
341
341
  appId: number
342
342
  deleted: boolean
343
343
  artifactId: number
344
+ duplicateTag: boolean
344
345
  }
345
346
 
346
347
  export interface ImageComment {
@@ -371,6 +372,7 @@ export interface ImageButtonType {
371
372
  tagId: number
372
373
  softDeleteTags: any
373
374
  isSuperAdmin: any
375
+ duplicateTag?: boolean
374
376
  }
375
377
 
376
378