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

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-12",
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,6 +512,7 @@ export const ImageTagButton = ({
423
512
  tagId,
424
513
  softDeleteTags,
425
514
  isSuperAdmin,
515
+ duplicateTag,
426
516
  }: ImageButtonType) => {
427
517
  const IconComponent = isSoftDeleted ? Redo : Minus
428
518
 
@@ -441,28 +531,42 @@ export const ImageTagButton = ({
441
531
  const isInSoftDeleteTags = isSoftDeleted && softDeleteTags.some((tag) => tag.tagName === text)
442
532
  const canTagBeHardDelete = tagId === 0 || isSuperAdmin
443
533
 
534
+ const tabColor = () => {
535
+ if (duplicateTag) {
536
+ return 'cr-5 bcr-1 er-2'
537
+ } else if (isSoftDeleted) {
538
+ return 'cy-7 bcy-1 dc__strike-through ey-2'
539
+ } else {
540
+ return 'cn-9'
541
+ }
542
+ }
543
+
444
544
  return (
445
545
  <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
- }`}
546
+ className={`br-4 en-2 bw-1 dc__w-fit-content dc__word-wrap-anywhere mr-8 bcn-0 flex mb-4 ${tabColor()}`}
449
547
  onMouseEnter={handleMouseEnter}
450
548
  onMouseLeave={handleMouseLeave}
451
549
  >
452
550
  <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'}
459
- >
460
- <IconComponent
461
- className={`icon-dim-12 mr-4 cursor ${isSoftDeleted ? 'scn-6' : 'fcn-6'}`}
462
- data-testid={`${text}-tag-soft-delete`}
463
- onClick={onSoftDeleteClick}
464
- />
465
- </Tippy>
551
+ {duplicateTag ? (
552
+ <Warning className="icon-dim-12 mr-4" />
553
+ ) : (
554
+ isHovered &&
555
+ isEditing &&
556
+ (isInSoftDeleteTags || (tagId !== 0 && !isSoftDeleted)) && (
557
+ <Tippy
558
+ className="default-tt"
559
+ arrow={true}
560
+ placement="top"
561
+ content={isInSoftDeleteTags ? 'Restore tag' : 'Soft delete tag'}
562
+ >
563
+ <IconComponent
564
+ className={`icon-dim-12 mr-4 cursor ${isSoftDeleted ? 'scn-6' : 'fcn-6'}`}
565
+ data-testid={`${text}-tag-soft-delete`}
566
+ onClick={onSoftDeleteClick}
567
+ />
568
+ </Tippy>
569
+ )
466
570
  )}
467
571
  {text}
468
572
  {isHovered && isEditing && canTagBeHardDelete && (
@@ -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