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

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.
@@ -1,4 +1,4 @@
1
1
  /// <reference types="react" />
2
2
  import { ImageButtonType, ImageTaggingContainerType } from './Types';
3
- export declare const ImageTagsContainer: ({ ciPipelineId, artifactId, imageComment, imageReleaseTags, appReleaseTagNames, tagsEditable, toggleCardMode, }: ImageTaggingContainerType) => JSX.Element;
3
+ export declare const ImageTagsContainer: ({ ciPipelineId, artifactId, imageComment, imageReleaseTags, appReleaseTagNames, setAppReleaseTagNames, tagsEditable, setTagsEditable, toggleCardMode, }: ImageTaggingContainerType) => JSX.Element;
4
4
  export declare const ImageTagButton: ({ text, isSoftDeleted, isEditing, onSoftDeleteClick, onHardDeleteClick, tagId, softDeleteTags, isSuperAdmin, }: ImageButtonType) => JSX.Element;
package/dist/index.js CHANGED
@@ -22763,7 +22763,7 @@ var SvgIcMinus = function SvgIcMinus(props) {
22763
22763
  };
22764
22764
 
22765
22765
  var ImageTagsContainer = function (_a) {
22766
- var ciPipelineId = _a.ciPipelineId, artifactId = _a.artifactId, imageComment = _a.imageComment, imageReleaseTags = _a.imageReleaseTags, appReleaseTagNames = _a.appReleaseTagNames, tagsEditable = _a.tagsEditable, toggleCardMode = _a.toggleCardMode;
22766
+ var ciPipelineId = _a.ciPipelineId, artifactId = _a.artifactId, imageComment = _a.imageComment, imageReleaseTags = _a.imageReleaseTags, appReleaseTagNames = _a.appReleaseTagNames, setAppReleaseTagNames = _a.setAppReleaseTagNames, tagsEditable = _a.tagsEditable, setTagsEditable = _a.setTagsEditable, toggleCardMode = _a.toggleCardMode;
22767
22767
  var _b = __read(React.useState(imageReleaseTags ? imageReleaseTags : []), 2), initialTags = _b[0], setInitialTags = _b[1];
22768
22768
  var _c = __read(React.useState(imageComment ? imageComment.comment : ''), 2), initialDescription = _c[0], setInitialDescription = _c[1];
22769
22769
  var _d = __read(React.useState(appReleaseTagNames ? appReleaseTagNames : []), 2), existingTags = _d[0], setExistingTags = _d[1];
@@ -22776,12 +22776,16 @@ var ImageTagsContainer = function (_a) {
22776
22776
  var _l = __read(React.useState([]), 2), softDeleteTags = _l[0], setSoftDeleteTags = _l[1];
22777
22777
  var _m = __read(React.useState([]), 2), hardDeleteTags = _m[0], setHardDeleteTags = _m[1];
22778
22778
  var _o = __read(React.useState(false), 2), isSuperAdmin = _o[0], setSuperAdmin = _o[1];
22779
+ var _p = __read(React.useState(''), 2), descriptionValidationMessage = _p[0], setDescriptionValidationMessage = _p[1];
22779
22780
  React.useEffect(function () {
22780
22781
  initialise();
22781
22782
  }, []);
22782
22783
  React.useEffect(function () {
22783
22784
  reInitState();
22784
- }, [imageReleaseTags, imageComment, appReleaseTagNames, tagsEditable]);
22785
+ }, [imageReleaseTags, imageComment, tagsEditable]);
22786
+ React.useEffect(function () {
22787
+ setExistingTags(appReleaseTagNames ? appReleaseTagNames : []);
22788
+ }, [appReleaseTagNames]);
22785
22789
  function initialise() {
22786
22790
  var _a, _b;
22787
22791
  return __awaiter(this, void 0, void 0, function () {
@@ -22806,11 +22810,12 @@ var ImageTagsContainer = function (_a) {
22806
22810
  });
22807
22811
  }
22808
22812
  var reInitState = function () {
22809
- setInitialTags(imageReleaseTags ? imageReleaseTags : []);
22810
- setInitialDescription(imageComment ? imageComment.comment : '');
22811
- setExistingTags(appReleaseTagNames ? appReleaseTagNames : []);
22812
- setNewDescription(imageComment ? imageComment.comment : '');
22813
- setDisplayedTags(imageReleaseTags ? imageReleaseTags : []);
22813
+ if (isEditing === false) {
22814
+ setInitialTags(imageReleaseTags ? imageReleaseTags : []);
22815
+ setInitialDescription(imageComment ? imageComment.comment : '');
22816
+ setNewDescription(imageComment ? imageComment.comment : '');
22817
+ setDisplayedTags(imageReleaseTags ? imageReleaseTags : []);
22818
+ }
22814
22819
  };
22815
22820
  var CreatableComponents = React.useMemo(function () { return ({
22816
22821
  DropdownIndicator: function () { return null; },
@@ -22824,7 +22829,11 @@ var ImageTagsContainer = function (_a) {
22824
22829
  }
22825
22830
  };
22826
22831
  var handleDescriptionChange = function (e) {
22827
- setNewDescription(e.target.value);
22832
+ var description = e.target.value;
22833
+ (description === null || description === void 0 ? void 0 : description.length) > 500
22834
+ ? setDescriptionValidationMessage('comment length cannot exceed 500 characters')
22835
+ : setDescriptionValidationMessage('');
22836
+ setNewDescription(description);
22828
22837
  };
22829
22838
  var handleCancel = function () {
22830
22839
  setDisplayedTags(initialTags);
@@ -22837,7 +22846,14 @@ var ImageTagsContainer = function (_a) {
22837
22846
  setTagErrorMessage('');
22838
22847
  };
22839
22848
  var handleTagCreate = function (newValue) {
22840
- var lowercaseValue = newValue.toLowerCase();
22849
+ var lowercaseValue = newValue.toLowerCase().trim();
22850
+ if (lowercaseValue.length == 0 ||
22851
+ lowercaseValue.length >= 128 ||
22852
+ lowercaseValue[0] == '.' ||
22853
+ lowercaseValue[0] == '-') {
22854
+ setTagErrorMessage('tag name cannot be empty or exceed 128 characters or cannot start with . or -');
22855
+ return;
22856
+ }
22841
22857
  setTagErrorMessage('');
22842
22858
  var isTagExistsInExistingTags = existingTags.includes(lowercaseValue);
22843
22859
  var isTagExistsInDisplayedTags = false;
@@ -22845,8 +22861,8 @@ var ImageTagsContainer = function (_a) {
22845
22861
  if (displayedTags[i].tagName.toLowerCase() === lowercaseValue)
22846
22862
  isTagExistsInDisplayedTags = true;
22847
22863
  }
22848
- if (isTagExistsInExistingTags || isTagExistsInDisplayedTags) {
22849
- setTagErrorMessage('This tag is already applied on another image in this application');
22864
+ if (isTagExistsInExistingTags || isTagExistsInDisplayedTags || lowercaseValue === 'latest') {
22865
+ setTagErrorMessage('This tag is already being used in this application');
22850
22866
  return;
22851
22867
  }
22852
22868
  var newTag = {
@@ -22892,58 +22908,57 @@ var ImageTagsContainer = function (_a) {
22892
22908
  }
22893
22909
  };
22894
22910
  var handleSave = function () { return __awaiter(void 0, void 0, void 0, function () {
22895
- var payload, response, tags, err_2;
22896
- var _a, _b, _c, _d, _e, _f;
22897
- return __generator(this, function (_g) {
22898
- switch (_g.label) {
22899
- case 0:
22900
- payload = {
22901
- createTags: createTags,
22902
- softDeleteTags: softDeleteTags,
22903
- imageComment: {
22904
- id: 0,
22905
- comment: newDescription.trim(),
22906
- artifactId: 0,
22907
- },
22908
- hardDeleteTags: hardDeleteTags,
22909
- };
22910
- _g.label = 1;
22911
- case 1:
22912
- _g.trys.push([1, 3, , 4]);
22913
- return [4 /*yield*/, setImageTags(payload, ciPipelineId, artifactId)];
22914
- case 2:
22915
- response = _g.sent();
22916
- tags = (_b = (_a = response.result) === null || _a === void 0 ? void 0 : _a.imageReleaseTags) === null || _b === void 0 ? void 0 : _b.map(function (tag) { return ({
22917
- id: tag.id,
22918
- tagName: tag.tagName,
22919
- deleted: tag.deleted,
22920
- appId: 0,
22921
- artifactId: 0,
22922
- }); });
22923
- setInitialTags(tags);
22924
- setInitialDescription((_d = (_c = response.result) === null || _c === void 0 ? void 0 : _c.imageComment) === null || _d === void 0 ? void 0 : _d.comment);
22925
- setDisplayedTags(tags);
22926
- setNewDescription((_f = (_e = response.result) === null || _e === void 0 ? void 0 : _e.imageComment) === null || _f === void 0 ? void 0 : _f.comment);
22927
- setCreateTags([]);
22928
- setSoftDeleteTags([]);
22929
- setHardDeleteTags([]);
22930
- handleEditClick();
22931
- setShowTagsWarning(false);
22932
- setTagErrorMessage('');
22933
- return [3 /*break*/, 4];
22934
- case 3:
22935
- err_2 = _g.sent();
22936
- showError(err_2);
22937
- return [3 /*break*/, 4];
22938
- case 4: return [2 /*return*/];
22939
- }
22911
+ var payload;
22912
+ return __generator(this, function (_a) {
22913
+ payload = {
22914
+ createTags: createTags,
22915
+ softDeleteTags: softDeleteTags,
22916
+ imageComment: {
22917
+ id: 0,
22918
+ comment: newDescription.trim(),
22919
+ artifactId: 0,
22920
+ },
22921
+ hardDeleteTags: hardDeleteTags,
22922
+ };
22923
+ // set loading state true
22924
+ setImageTags(payload, ciPipelineId, artifactId)
22925
+ .then(function (res) {
22926
+ var _a, _b, _c, _d, _e, _f, _g, _h;
22927
+ var tags = (_b = (_a = res.result) === null || _a === void 0 ? void 0 : _a.imageReleaseTags) === null || _b === void 0 ? void 0 : _b.map(function (tag) { return ({
22928
+ id: tag.id,
22929
+ tagName: tag.tagName,
22930
+ deleted: tag.deleted,
22931
+ appId: 0,
22932
+ artifactId: 0,
22933
+ }); });
22934
+ if (setAppReleaseTagNames) {
22935
+ setAppReleaseTagNames((_c = res.result) === null || _c === void 0 ? void 0 : _c.appReleaseTags);
22936
+ }
22937
+ if (setTagsEditable) {
22938
+ setTagsEditable((_d = res.result) === null || _d === void 0 ? void 0 : _d.tagsEditable);
22939
+ }
22940
+ setInitialTags(tags);
22941
+ setInitialDescription((_f = (_e = res.result) === null || _e === void 0 ? void 0 : _e.imageComment) === null || _f === void 0 ? void 0 : _f.comment);
22942
+ setDisplayedTags(tags);
22943
+ setNewDescription((_h = (_g = res.result) === null || _g === void 0 ? void 0 : _g.imageComment) === null || _h === void 0 ? void 0 : _h.comment);
22944
+ setCreateTags([]);
22945
+ setSoftDeleteTags([]);
22946
+ setHardDeleteTags([]);
22947
+ handleEditClick();
22948
+ setShowTagsWarning(false);
22949
+ setTagErrorMessage('');
22950
+ })
22951
+ .catch(function (err) {
22952
+ showError(err);
22953
+ });
22954
+ return [2 /*return*/];
22940
22955
  });
22941
22956
  }); };
22942
22957
  var renderInfoCard = function () {
22943
- return (jsxRuntime.jsx(TippyCustomized, __assign({ theme: exports.TippyTheme.white, className: "w-300 h-250 fcv-5 dc__overflow-scroll", placement: "right", Icon: SvgIcHelp, heading: "Release tags", infoText: "Release tags allow you to tag container images with readable and relatable tags eg. v1.0.", showCloseButton: true, trigger: "click", interactive: true, documentationLinkText: "View Documentation", additionalContent: getBuildContextAdditionalContent() }, { children: jsxRuntime.jsx("div", __assign({ className: "icon-dim-16 fcn-5 ml-8 cursor" }, { children: jsxRuntime.jsx(SvgIcHelpOutline, {}) })) })));
22958
+ return (jsxRuntime.jsx(TippyCustomized, __assign({ theme: exports.TippyTheme.white, className: "w-300 fcv-5", placement: "right", Icon: SvgIcHelp, heading: "Release tags", showCloseButton: true, trigger: "click", interactive: true, documentationLinkText: "View Documentation", additionalContent: getBuildContextAdditionalContent() }, { children: jsxRuntime.jsx("div", __assign({ className: "icon-dim-16 fcn-5 ml-8 cursor" }, { children: jsxRuntime.jsx(SvgIcHelpOutline, {}) })) })));
22944
22959
  };
22945
22960
  var getBuildContextAdditionalContent = function () {
22946
- return (jsxRuntime.jsx("div", __assign({ className: "p-12 fs-13" }, { children: jsxRuntime.jsxs("ul", { children: [jsxRuntime.jsx("li", { children: "A release tag can only be added if a workflow has CD pipelines deploying to Production environments." }), jsxRuntime.jsx("li", { children: "Multiple tags can be added to an image." }), jsxRuntime.jsx("li", { children: "Multiple images in an application cannot have the same tag." }), jsxRuntime.jsx("li", { children: "Tags cannot be deleted once saved. Although, you can soft delete a tag if an unwanted tag has been added." })] }) })));
22961
+ return (jsxRuntime.jsxs("div", __assign({ className: "h-250 fs-13 dc__overflow-scroll p-12" }, { children: [jsxRuntime.jsx("div", { children: "Release tags allow you to tag container images with readable and relatable tags eg. v1.0." }), jsxRuntime.jsxs("ul", __assign({ className: 'pl-20 mt-8' }, { children: [jsxRuntime.jsx("li", { children: "A release tag can only be added if a workflow has CD pipelines deploying to Production environments." }), jsxRuntime.jsx("li", { children: "Multiple tags can be added to an image." }), jsxRuntime.jsx("li", { children: "Multiple images in an application cannot have the same tag." }), jsxRuntime.jsx("li", { children: "Tags cannot be deleted once saved. Although, you can soft delete a tag if an unwanted tag has been added." })] }))] })));
22947
22962
  };
22948
22963
  var creatableRef = React.useRef(null);
22949
22964
  if (newDescription === '' && displayedTags.length === 0 && !isEditing) {
@@ -22955,7 +22970,7 @@ var ImageTagsContainer = function (_a) {
22955
22970
  }, onHardDeleteClick: function (e) {
22956
22971
  stopPropagation(e);
22957
22972
  handleTagHardDelete(index);
22958
- }, tagId: tag.id, softDeleteTags: softDeleteTags, isSuperAdmin: isSuperAdmin }, tag.id)); }) })), showTagsWarning && (jsxRuntime.jsxs("div", __assign({ className: "cn-7 mb-8 flex left" }, { children: [jsxRuntime.jsx(SvgIcInfoFilled, { className: "form__icon--info icon-dim-16 mr-2" }), "Tags cannot be edited/removed later"] }))), jsxRuntime.jsx("div", __assign({ className: "cn-7 mt-12" }, { children: "Comment" })), jsxRuntime.jsx("div", __assign({ className: "flex left flex-wrap dc__gap-8 w-100 mt-6 mb-12", "data-testid": "add-image-comment-text-area" }, { children: jsxRuntime.jsx("textarea", { value: newDescription, onChange: handleDescriptionChange, className: "flex left flex-wrap dc__gap-8 dc__description-textarea", style: { height: '90px !important' } }) })), jsxRuntime.jsxs("div", __assign({ className: "w-100 flex right" }, { children: [jsxRuntime.jsx("button", __assign({ className: "cta flex cancel h-32 lh-32-imp", type: "button", onClick: function (e) {
22973
+ }, tagId: tag.id, softDeleteTags: softDeleteTags, isSuperAdmin: isSuperAdmin }, tag.id)); }) })), showTagsWarning && (jsxRuntime.jsxs("div", __assign({ className: "cn-7 mb-8 flex left" }, { children: [jsxRuntime.jsx(SvgIcInfoFilled, { className: "form__icon--info icon-dim-16 mr-2" }), "Tags cannot be edited/removed later"] }))), jsxRuntime.jsx("div", __assign({ className: "cn-7 mt-12" }, { children: "Comment" })), jsxRuntime.jsx("div", __assign({ className: "flex left flex-wrap dc__gap-8 w-100 mt-6 mb-12", "data-testid": "add-image-comment-text-area" }, { children: jsxRuntime.jsx("textarea", { value: newDescription, onChange: handleDescriptionChange, className: "flex left flex-wrap dc__gap-8 dc__description-textarea", style: { height: '90px !important' } }) })), descriptionValidationMessage !== '' && (jsxRuntime.jsxs("div", __assign({ className: "flex left" }, { children: [jsxRuntime.jsx(SvgIcWarning, { className: "form__icon form__icon--error" }), jsxRuntime.jsx("div", __assign({ className: "form__error" }, { children: descriptionValidationMessage }))] }))), jsxRuntime.jsxs("div", __assign({ className: "w-100 flex right mt-12" }, { children: [jsxRuntime.jsx("button", __assign({ className: "cta flex cancel h-32 lh-32-imp", type: "button", onClick: function (e) {
22959
22974
  stopPropagation(e);
22960
22975
  handleCancel();
22961
22976
  } }, { children: "Cancel" })), jsxRuntime.jsx("button", __assign({ className: "cta flex h-32 lh-32-imp ml-12", "data-testid": "save-tags-button", type: "button", onClick: function (e) {
@@ -22988,11 +23003,12 @@ var ImageTagButton = function (_a) {
22988
23003
  };
22989
23004
  var isInSoftDeleteTags = isSoftDeleted && softDeleteTags.some(function (tag) { return tag.tagName === text; });
22990
23005
  var canTagBeHardDelete = tagId === 0 || isSuperAdmin;
22991
- return (jsxRuntime.jsx("div", __assign({ className: "br-4 en-2 bw-1 dc__w-fit-content dc__word-wrap-anywhere mr-8 bcn-0 flex h-24 ".concat(isSoftDeleted ? 'cy-7 bcy-1 dc__strike-through ey-2' : ''), onMouseEnter: handleMouseEnter, onMouseLeave: handleMouseLeave }, { children: jsxRuntime.jsxs("div", __assign({ className: "flex pt-2 pl-8 pr-8 pb-2" }, { children: [isHovered && isEditing && (isInSoftDeleteTags || (tagId !== 0 && !isSoftDeleted)) && (jsxRuntime.jsx(IconComponent, { className: "icon-dim-12 mr-4 cursor ".concat(isSoftDeleted ? 'scn-6' : 'fcn-6'), "data-testid": "".concat(text, "-tag-soft-delete"), onClick: onSoftDeleteClick })), text, isHovered && isEditing && canTagBeHardDelete && (jsxRuntime.jsx(SvgIcCross, { className: "icon-dim-12 ml-4 fcn-6 cn-5 cursor", "data-testid": "".concat(text, "-tag-hard-delete"), onClick: onHardDeleteClick }))] })) })));
23006
+ return (jsxRuntime.jsx("div", __assign({ className: "br-4 en-2 bw-1 dc__w-fit-content dc__word-wrap-anywhere mr-8 bcn-0 flex h-24 ".concat(isSoftDeleted ? 'cy-7 bcy-1 dc__strike-through ey-2' : ''), onMouseEnter: handleMouseEnter, onMouseLeave: handleMouseLeave }, { children: jsxRuntime.jsxs("div", __assign({ className: "flex pt-2 pl-8 pr-8 pb-2" }, { children: [isHovered && isEditing && (isInSoftDeleteTags || (tagId !== 0 && !isSoftDeleted)) && (jsxRuntime.jsx(Tippy, __assign({ className: "default-tt", arrow: true, placement: "top", content: isInSoftDeleteTags ? 'Restore tag' : 'Soft delete tag' }, { children: jsxRuntime.jsx(IconComponent, { className: "icon-dim-12 mr-4 cursor ".concat(isSoftDeleted ? 'scn-6' : 'fcn-6'), "data-testid": "".concat(text, "-tag-soft-delete"), onClick: onSoftDeleteClick }) }))), text, isHovered && isEditing && canTagBeHardDelete && (jsxRuntime.jsx(Tippy, __assign({ className: "default-tt", arrow: true, placement: "top", content: "Remove tag" }, { children: jsxRuntime.jsx(SvgIcCross, { className: "icon-dim-12 ml-4 fcn-6 cn-5 cursor", "data-testid": "".concat(text, "-tag-hard-delete"), onClick: onHardDeleteClick }) })))] })) })));
22992
23007
  };
22993
23008
  var AddImageButton = function (_a) {
22994
23009
  var handleEditClick = _a.handleEditClick;
22995
- var handleClick = function () {
23010
+ var handleClick = function (e) {
23011
+ stopPropagation(e);
22996
23012
  handleEditClick();
22997
23013
  };
22998
23014
  return (jsxRuntime.jsx("div", __assign({ className: "add-tag-button flex pt-12 pr-12", "data-testid": "add-tags-button", onClick: handleClick }, { children: jsxRuntime.jsxs("div", __assign({ className: "lh-16 flex" }, { children: [jsxRuntime.jsx(SvgIcAdd, { className: "icon-dim-16 cn-6" }), jsxRuntime.jsx("span", __assign({ className: "cn-7" }, { children: "Add tags/comment" }))] })) })));
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-7",
3
+ "version": "0.0.10-beta-9",
4
4
  "description": "Supporting common component library",
5
5
  "main": "dist/index.js",
6
6
  "scripts": {
@@ -15,6 +15,7 @@ import { ImageButtonType, ImageTaggingContainerType, ReleaseTag, TippyTheme } fr
15
15
  import { showError, stopPropagation } from './Helper'
16
16
  import { TippyCustomized } from './TippyCustomized'
17
17
  import { setImageTags, getUserRole } from './Common.service'
18
+ import Tippy from '@tippyjs/react'
18
19
 
19
20
  export const ImageTagsContainer = ({
20
21
  ciPipelineId,
@@ -22,7 +23,9 @@ export const ImageTagsContainer = ({
22
23
  imageComment,
23
24
  imageReleaseTags,
24
25
  appReleaseTagNames,
26
+ setAppReleaseTagNames,
25
27
  tagsEditable,
28
+ setTagsEditable,
26
29
  toggleCardMode,
27
30
  }: ImageTaggingContainerType) => {
28
31
  const [initialTags, setInitialTags] = useState<ReleaseTag[]>(imageReleaseTags ? imageReleaseTags : [])
@@ -37,6 +40,7 @@ export const ImageTagsContainer = ({
37
40
  const [softDeleteTags, setSoftDeleteTags] = useState<ReleaseTag[]>([])
38
41
  const [hardDeleteTags, setHardDeleteTags] = useState<ReleaseTag[]>([])
39
42
  const [isSuperAdmin, setSuperAdmin] = useState<boolean>(false)
43
+ const [descriptionValidationMessage, setDescriptionValidationMessage] = useState<string>('')
40
44
 
41
45
  useEffect(() => {
42
46
  initialise()
@@ -44,7 +48,11 @@ export const ImageTagsContainer = ({
44
48
 
45
49
  useEffect(() => {
46
50
  reInitState()
47
- }, [imageReleaseTags, imageComment, appReleaseTagNames, tagsEditable])
51
+ }, [imageReleaseTags, imageComment, tagsEditable])
52
+
53
+ useEffect(() => {
54
+ setExistingTags(appReleaseTagNames ? appReleaseTagNames : [])
55
+ }, [appReleaseTagNames])
48
56
 
49
57
  async function initialise() {
50
58
  try {
@@ -58,11 +66,12 @@ export const ImageTagsContainer = ({
58
66
  }
59
67
 
60
68
  const reInitState = () => {
61
- setInitialTags(imageReleaseTags ? imageReleaseTags : [])
62
- setInitialDescription(imageComment ? imageComment.comment : '')
63
- setExistingTags(appReleaseTagNames ? appReleaseTagNames : [])
64
- setNewDescription(imageComment ? imageComment.comment : '')
65
- setDisplayedTags(imageReleaseTags ? imageReleaseTags : [])
69
+ if (isEditing === false) {
70
+ setInitialTags(imageReleaseTags ? imageReleaseTags : [])
71
+ setInitialDescription(imageComment ? imageComment.comment : '')
72
+ setNewDescription(imageComment ? imageComment.comment : '')
73
+ setDisplayedTags(imageReleaseTags ? imageReleaseTags : [])
74
+ }
66
75
  }
67
76
 
68
77
  const CreatableComponents = useMemo(
@@ -82,7 +91,11 @@ export const ImageTagsContainer = ({
82
91
  }
83
92
 
84
93
  const handleDescriptionChange = (e) => {
85
- setNewDescription(e.target.value)
94
+ const description = e.target.value
95
+ description?.length > 500
96
+ ? setDescriptionValidationMessage('comment length cannot exceed 500 characters')
97
+ : setDescriptionValidationMessage('')
98
+ setNewDescription(description)
86
99
  }
87
100
 
88
101
  const handleCancel = () => {
@@ -97,15 +110,26 @@ export const ImageTagsContainer = ({
97
110
  }
98
111
 
99
112
  const handleTagCreate = (newValue) => {
100
- const lowercaseValue = newValue.toLowerCase()
113
+ const lowercaseValue = newValue.toLowerCase().trim()
114
+ if (
115
+ lowercaseValue.length == 0 ||
116
+ lowercaseValue.length >= 128 ||
117
+ lowercaseValue[0] == '.' ||
118
+ lowercaseValue[0] == '-'
119
+ ) {
120
+ setTagErrorMessage('tag name cannot be empty or exceed 128 characters or cannot start with . or -')
121
+ return
122
+ }
101
123
  setTagErrorMessage('')
102
124
  const isTagExistsInExistingTags = existingTags.includes(lowercaseValue)
103
125
  let isTagExistsInDisplayedTags = false
104
126
  for (let i = 0; i < displayedTags?.length; i++) {
105
127
  if (displayedTags[i].tagName.toLowerCase() === lowercaseValue) isTagExistsInDisplayedTags = true
106
128
  }
107
- if (isTagExistsInExistingTags || isTagExistsInDisplayedTags) {
108
- setTagErrorMessage('This tag is already applied on another image in this application')
129
+ if (isTagExistsInExistingTags || isTagExistsInDisplayedTags || lowercaseValue === 'latest') {
130
+ setTagErrorMessage(
131
+ 'This tag is already being used in this application',
132
+ )
109
133
  return
110
134
  }
111
135
  const newTag: ReleaseTag = {
@@ -167,40 +191,46 @@ export const ImageTagsContainer = ({
167
191
  hardDeleteTags: hardDeleteTags,
168
192
  }
169
193
 
170
- try {
171
- // set loading state true
172
- let response = await setImageTags(payload, ciPipelineId, artifactId)
173
- const tags = response.result?.imageReleaseTags?.map((tag) => ({
174
- id: tag.id,
175
- tagName: tag.tagName,
176
- deleted: tag.deleted,
177
- appId: 0,
178
- artifactId: 0,
179
- }))
180
- setInitialTags(tags)
181
- setInitialDescription(response.result?.imageComment?.comment)
182
- setDisplayedTags(tags)
183
- setNewDescription(response.result?.imageComment?.comment)
184
- setCreateTags([])
185
- setSoftDeleteTags([])
186
- setHardDeleteTags([])
187
- handleEditClick()
188
- setShowTagsWarning(false)
189
- setTagErrorMessage('')
190
- } catch (err) {
191
- showError(err)
192
- }
194
+ // set loading state true
195
+ setImageTags(payload, ciPipelineId, artifactId)
196
+ .then((res) => {
197
+ const tags = res.result?.imageReleaseTags?.map((tag) => ({
198
+ id: tag.id,
199
+ tagName: tag.tagName,
200
+ deleted: tag.deleted,
201
+ appId: 0,
202
+ artifactId: 0,
203
+ }))
204
+ if (setAppReleaseTagNames) {
205
+ setAppReleaseTagNames(res.result?.appReleaseTags)
206
+ }
207
+ if (setTagsEditable) {
208
+ setTagsEditable(res.result?.tagsEditable)
209
+ }
210
+ setInitialTags(tags)
211
+ setInitialDescription(res.result?.imageComment?.comment)
212
+ setDisplayedTags(tags)
213
+ setNewDescription(res.result?.imageComment?.comment)
214
+ setCreateTags([])
215
+ setSoftDeleteTags([])
216
+ setHardDeleteTags([])
217
+ handleEditClick()
218
+ setShowTagsWarning(false)
219
+ setTagErrorMessage('')
220
+ })
221
+ .catch((err) => {
222
+ showError(err)
223
+ })
193
224
  }
194
225
 
195
226
  const renderInfoCard = (): JSX.Element => {
196
227
  return (
197
228
  <TippyCustomized
198
229
  theme={TippyTheme.white}
199
- className="w-300 h-250 fcv-5 dc__overflow-scroll"
230
+ className="w-300 fcv-5"
200
231
  placement="right"
201
232
  Icon={QuestionFilled}
202
233
  heading="Release tags"
203
- infoText="Release tags allow you to tag container images with readable and relatable tags eg. v1.0."
204
234
  showCloseButton={true}
205
235
  trigger="click"
206
236
  interactive={true}
@@ -216,8 +246,9 @@ export const ImageTagsContainer = ({
216
246
 
217
247
  const getBuildContextAdditionalContent = () => {
218
248
  return (
219
- <div className="p-12 fs-13">
220
- <ul>
249
+ <div className="h-250 fs-13 dc__overflow-scroll p-12">
250
+ <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'>
221
252
  <li>
222
253
  A release tag can only be added if a workflow has CD pipelines deploying to Production
223
254
  environments.
@@ -296,10 +327,7 @@ export const ImageTagsContainer = ({
296
327
  </div>
297
328
  )}
298
329
  <div className="cn-7 mt-12">Comment</div>
299
- <div
300
- className="flex left flex-wrap dc__gap-8 w-100 mt-6 mb-12"
301
- data-testid="add-image-comment-text-area"
302
- >
330
+ <div className="flex left flex-wrap dc__gap-8 w-100 mt-6 mb-12" data-testid="add-image-comment-text-area">
303
331
  <textarea
304
332
  value={newDescription}
305
333
  onChange={handleDescriptionChange}
@@ -307,7 +335,13 @@ export const ImageTagsContainer = ({
307
335
  style={{ height: '90px !important' }}
308
336
  />
309
337
  </div>
310
- <div className="w-100 flex right">
338
+ {descriptionValidationMessage !== '' && (
339
+ <div className="flex left">
340
+ <Error className="form__icon form__icon--error" />
341
+ <div className="form__error">{descriptionValidationMessage}</div>
342
+ </div>
343
+ )}
344
+ <div className="w-100 flex right mt-12">
311
345
  <button
312
346
  className="cta flex cancel h-32 lh-32-imp"
313
347
  type="button"
@@ -417,19 +451,28 @@ export const ImageTagButton = ({
417
451
  >
418
452
  <div className="flex pt-2 pl-8 pr-8 pb-2">
419
453
  {isHovered && isEditing && (isInSoftDeleteTags || (tagId !== 0 && !isSoftDeleted)) && (
420
- <IconComponent
421
- className={`icon-dim-12 mr-4 cursor ${isSoftDeleted ? 'scn-6' : 'fcn-6'}`}
422
- data-testid={`${text}-tag-soft-delete`}
423
- onClick={onSoftDeleteClick}
424
- />
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>
425
466
  )}
426
467
  {text}
427
468
  {isHovered && isEditing && canTagBeHardDelete && (
428
- <Close
429
- className="icon-dim-12 ml-4 fcn-6 cn-5 cursor"
430
- data-testid={`${text}-tag-hard-delete`}
431
- onClick={onHardDeleteClick}
432
- />
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>
433
476
  )}
434
477
  </div>
435
478
  </div>
@@ -437,7 +480,8 @@ export const ImageTagButton = ({
437
480
  }
438
481
 
439
482
  const AddImageButton = ({ handleEditClick }) => {
440
- const handleClick = () => {
483
+ const handleClick = (e) => {
484
+ stopPropagation(e)
441
485
  handleEditClick()
442
486
  }
443
487