@devtron-labs/devtron-fe-common-lib 0.0.10-beta-8 → 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-8",
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,10 +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'
19
+ import Tippy from '@tippyjs/react'
20
+ import { ServerErrors } from './ServerError'
18
21
 
19
22
  export const ImageTagsContainer = ({
20
23
  ciPipelineId,
@@ -40,6 +43,8 @@ export const ImageTagsContainer = ({
40
43
  const [hardDeleteTags, setHardDeleteTags] = useState<ReleaseTag[]>([])
41
44
  const [isSuperAdmin, setSuperAdmin] = useState<boolean>(false)
42
45
  const [descriptionValidationMessage, setDescriptionValidationMessage] = useState<string>('')
46
+ const [textInput, setTextInput] = useState<string>('')
47
+ const [saveData, setSaveData] = useState<boolean>(false)
43
48
 
44
49
  useEffect(() => {
45
50
  initialise()
@@ -47,11 +52,18 @@ export const ImageTagsContainer = ({
47
52
 
48
53
  useEffect(() => {
49
54
  reInitState()
50
- }, [imageReleaseTags,imageComment,tagsEditable])
55
+ }, [imageReleaseTags, imageComment, tagsEditable])
51
56
 
52
57
  useEffect(() => {
53
58
  setExistingTags(appReleaseTagNames ? appReleaseTagNames : [])
54
- },[appReleaseTagNames])
59
+ }, [appReleaseTagNames])
60
+
61
+ useEffect(() => {
62
+ if(saveData){
63
+ handleSave()
64
+ setSaveData(false)
65
+ }
66
+ },[saveData])
55
67
 
56
68
  async function initialise() {
57
69
  try {
@@ -73,15 +85,6 @@ export const ImageTagsContainer = ({
73
85
  }
74
86
  }
75
87
 
76
- const CreatableComponents = useMemo(
77
- () => ({
78
- DropdownIndicator: () => null,
79
- IndicatorSeparator: () => null,
80
- Menu: () => null,
81
- }),
82
- [],
83
- )
84
-
85
88
  const handleEditClick = () => {
86
89
  setIsEditing(!isEditing)
87
90
  if (typeof toggleCardMode === 'function') {
@@ -91,7 +94,9 @@ export const ImageTagsContainer = ({
91
94
 
92
95
  const handleDescriptionChange = (e) => {
93
96
  const description = e.target.value
94
- description?.length > 500 ? setDescriptionValidationMessage('comment length cannot exceed 500 characters') : setDescriptionValidationMessage('')
97
+ description?.length > 500
98
+ ? setDescriptionValidationMessage('comment length cannot exceed 500 characters')
99
+ : setDescriptionValidationMessage('')
95
100
  setNewDescription(description)
96
101
  }
97
102
 
@@ -108,8 +113,13 @@ export const ImageTagsContainer = ({
108
113
 
109
114
  const handleTagCreate = (newValue) => {
110
115
  const lowercaseValue = newValue.toLowerCase().trim()
111
- if(lowercaseValue.length == 0 || lowercaseValue.length >= 128 || lowercaseValue[0] == '.' || lowercaseValue[0] == '-') {
112
- setTagErrorMessage("tag name cannot be empty or exceed 128 characters or cannot start with . or -")
116
+ if (
117
+ lowercaseValue.length == 0 ||
118
+ lowercaseValue.length >= 128 ||
119
+ lowercaseValue[0] == '.' ||
120
+ lowercaseValue[0] == '-'
121
+ ) {
122
+ setTagErrorMessage('tag name cannot be empty or exceed 128 characters or cannot start with . or -')
113
123
  return
114
124
  }
115
125
  setTagErrorMessage('')
@@ -119,7 +129,7 @@ export const ImageTagsContainer = ({
119
129
  if (displayedTags[i].tagName.toLowerCase() === lowercaseValue) isTagExistsInDisplayedTags = true
120
130
  }
121
131
  if (isTagExistsInExistingTags || isTagExistsInDisplayedTags || lowercaseValue === 'latest') {
122
- setTagErrorMessage(`This tag ${lowercaseValue} is already applied on same/another image in this application`)
132
+ setTagErrorMessage('This tag is already being used in this application')
123
133
  return
124
134
  }
125
135
  const newTag: ReleaseTag = {
@@ -128,10 +138,12 @@ export const ImageTagsContainer = ({
128
138
  appId: 0,
129
139
  deleted: false,
130
140
  artifactId: 0,
141
+ duplicateTag: false,
131
142
  }
132
143
  setCreateTags([...createTags, newTag])
133
144
  setDisplayedTags([...displayedTags, newTag])
134
145
  setShowTagsWarning(true)
146
+ setTextInput('')
135
147
  }
136
148
 
137
149
  const handleTagSoftDelete = (index) => {
@@ -169,7 +181,34 @@ export const ImageTagsContainer = ({
169
181
  }
170
182
  }
171
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
+
172
209
  const handleSave = async () => {
210
+ if(tagErrorMessage) return
211
+
173
212
  const payload = {
174
213
  createTags: createTags,
175
214
  softDeleteTags: softDeleteTags,
@@ -209,20 +248,33 @@ export const ImageTagsContainer = ({
209
248
  setTagErrorMessage('')
210
249
  })
211
250
  .catch((err) => {
212
- 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
+ }
213
267
  })
214
268
  }
215
269
 
216
-
217
270
  const renderInfoCard = (): JSX.Element => {
218
271
  return (
219
272
  <TippyCustomized
220
273
  theme={TippyTheme.white}
221
- className="w-300 h-250 fcv-5 dc__overflow-scroll"
274
+ className="w-300 fcv-5"
222
275
  placement="right"
223
276
  Icon={QuestionFilled}
224
277
  heading="Release tags"
225
- infoText="Release tags allow you to tag container images with readable and relatable tags eg. v1.0."
226
278
  showCloseButton={true}
227
279
  trigger="click"
228
280
  interactive={true}
@@ -238,8 +290,9 @@ export const ImageTagsContainer = ({
238
290
 
239
291
  const getBuildContextAdditionalContent = () => {
240
292
  return (
241
- <div className="p-12 fs-13">
242
- <ul>
293
+ <div className="h-250 fs-13 dc__overflow-scroll p-12">
294
+ <div>Release tags allow you to tag container images with readable and relatable tags eg. v1.0.</div>
295
+ <ul className="pl-20 mt-8">
243
296
  <li>
244
297
  A release tag can only be added if a workflow has CD pipelines deploying to Production
245
298
  environments.
@@ -259,7 +312,7 @@ export const ImageTagsContainer = ({
259
312
 
260
313
  if (newDescription === '' && displayedTags.length === 0 && !isEditing) {
261
314
  return tagsEditable ? (
262
- <div className="bcn-0 mt-8">
315
+ <div className="bcn-0 mt-8 add-image-button">
263
316
  <AddImageButton handleEditClick={handleEditClick} />
264
317
  </div>
265
318
  ) : (
@@ -267,6 +320,29 @@ export const ImageTagsContainer = ({
267
320
  )
268
321
  }
269
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
+
270
346
  return (
271
347
  <div className="mt-8">
272
348
  {isEditing && tagsEditable ? (
@@ -275,13 +351,30 @@ export const ImageTagsContainer = ({
275
351
  <span>Release tags (eg. v1.0)</span>
276
352
  <div className="flex row ml-0">{renderInfoCard()}</div>
277
353
  </div>
278
- <div className="mt-6" data-testid="add-tag-text-area">
279
- <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}
280
359
  placeholder="Type a tag and press enter"
281
- onCreateOption={handleTagCreate}
360
+ onKeyDown={handleKeydown}
282
361
  ref={creatableRef}
283
- components={CreatableComponents}
284
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
+ )}
285
378
  </div>
286
379
 
287
380
  {tagErrorMessage && (
@@ -308,6 +401,7 @@ export const ImageTagsContainer = ({
308
401
  tagId={tag.id}
309
402
  softDeleteTags={softDeleteTags}
310
403
  isSuperAdmin={isSuperAdmin}
404
+ duplicateTag={tag?.duplicateTag}
311
405
  />
312
406
  ))}
313
407
  </div>
@@ -319,7 +413,7 @@ export const ImageTagsContainer = ({
319
413
  )}
320
414
  <div className="cn-7 mt-12">Comment</div>
321
415
  <div
322
- className="flex left flex-wrap dc__gap-8 w-100 mt-6"
416
+ className="flex left flex-wrap dc__gap-8 w-100 mt-6 mb-12"
323
417
  data-testid="add-image-comment-text-area"
324
418
  >
325
419
  <textarea
@@ -329,7 +423,8 @@ export const ImageTagsContainer = ({
329
423
  style={{ height: '90px !important' }}
330
424
  />
331
425
  </div>
332
- { (descriptionValidationMessage !== '') && (<div className="flex left">
426
+ {descriptionValidationMessage !== '' && (
427
+ <div className="flex left">
333
428
  <Error className="form__icon form__icon--error" />
334
429
  <div className="form__error">{descriptionValidationMessage}</div>
335
430
  </div>
@@ -351,7 +446,7 @@ export const ImageTagsContainer = ({
351
446
  type="button"
352
447
  onClick={(e) => {
353
448
  stopPropagation(e)
354
- handleSave()
449
+ onClickSave()
355
450
  }}
356
451
  >
357
452
  Save
@@ -384,6 +479,7 @@ export const ImageTagsContainer = ({
384
479
  tagId={tag.id}
385
480
  softDeleteTags={softDeleteTags}
386
481
  isSuperAdmin={isSuperAdmin}
482
+ duplicateTag={tag?.duplicateTag}
387
483
  />
388
484
  ))}
389
485
  </div>
@@ -416,6 +512,7 @@ export const ImageTagButton = ({
416
512
  tagId,
417
513
  softDeleteTags,
418
514
  isSuperAdmin,
515
+ duplicateTag,
419
516
  }: ImageButtonType) => {
420
517
  const IconComponent = isSoftDeleted ? Redo : Minus
421
518
 
@@ -434,29 +531,52 @@ export const ImageTagButton = ({
434
531
  const isInSoftDeleteTags = isSoftDeleted && softDeleteTags.some((tag) => tag.tagName === text)
435
532
  const canTagBeHardDelete = tagId === 0 || isSuperAdmin
436
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
+
437
544
  return (
438
545
  <div
439
- className={`br-4 en-2 bw-1 dc__w-fit-content dc__word-wrap-anywhere mr-8 bcn-0 flex h-24 ${
440
- isSoftDeleted ? 'cy-7 bcy-1 dc__strike-through ey-2' : ''
441
- }`}
546
+ className={`br-4 en-2 bw-1 dc__w-fit-content dc__word-wrap-anywhere mr-8 bcn-0 flex mb-4 ${tabColor()}`}
442
547
  onMouseEnter={handleMouseEnter}
443
548
  onMouseLeave={handleMouseLeave}
444
549
  >
445
550
  <div className="flex pt-2 pl-8 pr-8 pb-2">
446
- {isHovered && isEditing && (isInSoftDeleteTags || (tagId !== 0 && !isSoftDeleted)) && (
447
- <IconComponent
448
- className={`icon-dim-12 mr-4 cursor ${isSoftDeleted ? 'scn-6' : 'fcn-6'}`}
449
- data-testid={`${text}-tag-soft-delete`}
450
- onClick={onSoftDeleteClick}
451
- />
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
+ )
452
570
  )}
453
571
  {text}
454
572
  {isHovered && isEditing && canTagBeHardDelete && (
455
- <Close
456
- className="icon-dim-12 ml-4 fcn-6 cn-5 cursor"
457
- data-testid={`${text}-tag-hard-delete`}
458
- onClick={onHardDeleteClick}
459
- />
573
+ <Tippy className="default-tt" arrow={true} placement="top" content="Remove tag">
574
+ <Close
575
+ className="icon-dim-12 ml-4 fcn-6 cn-5 cursor"
576
+ data-testid={`${text}-tag-hard-delete`}
577
+ onClick={onHardDeleteClick}
578
+ />
579
+ </Tippy>
460
580
  )}
461
581
  </div>
462
582
  </div>
@@ -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