Skip to content

Commit

Permalink
fix(ui): Include application name in status badge (argoproj#17126)
Browse files Browse the repository at this point in the history
* Added application name to badge

Signed-off-by: sshenoy6 <[email protected]>

* Rever svg change

Signed-off-by: sshenoy6 <[email protected]>

* Doc for disabling application name

Signed-off-by: sshenoy6 <[email protected]>

* Flag to not display application name

Signed-off-by: sshenoy6 <[email protected]>

* Added tests

Signed-off-by: sshenoy6 <[email protected]>

* Make no app name the default

Signed-off-by: sshenoy6 <[email protected]>

* Have enable app name as a query parameter

Signed-off-by: sshenoy6 <[email protected]>

* Have enable app name as a query parameter

Signed-off-by: sshenoy6 <[email protected]>

* argocd to original

Signed-off-by: sshenoy6 <[email protected]>

* Update docs/user-guide/status-badge.md

Signed-off-by: Dan Garfield <[email protected]>

Signed-off-by: Dan Garfield <[email protected]>

---------

Signed-off-by: sshenoy6 <[email protected]>
Signed-off-by: Dan Garfield <[email protected]>
Co-authored-by: sshenoy6 <[email protected]>
Co-authored-by: Dan Garfield <[email protected]>
  • Loading branch information
3 people committed Feb 24, 2024
1 parent 5bc1850 commit 7fe1263
Show file tree
Hide file tree
Showing 6 changed files with 122 additions and 9 deletions.
2 changes: 2 additions & 0 deletions assets/badge.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
8 changes: 4 additions & 4 deletions docs/operator-manual/argocd-cm.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -308,9 +308,9 @@ data:
# have either a permanent banner or a regular closeable banner, and NOT both. eg. A user can't dismiss a
# notification message (closeable) banner, to then immediately see a permanent banner.
# ui.bannerpermanent: "true"
# An option to specify the position of the banner, either the top or bottom of the page, or both. The valid values
# are: "top", "bottom" and "both". The default (if the option is not provided), is "top". If "both" is specified, then
# the content appears both at the top and the bottom of the page. Uncomment the following line to make the banner appear
# An option to specify the position of the banner, either the top or bottom of the page, or both. The valid values
# are: "top", "bottom" and "both". The default (if the option is not provided), is "top". If "both" is specified, then
# the content appears both at the top and the bottom of the page. Uncomment the following line to make the banner appear
# at the bottom of the page. Change the value as needed.
# ui.bannerposition: "bottom"

Expand Down Expand Up @@ -413,4 +413,4 @@ data:
# Mandatory if multiple services are specified.
cluster:
name: some-cluster
server: https://some-cluster
server: https://some-cluster
11 changes: 8 additions & 3 deletions docs/user-guide/status-badge.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,12 @@ To show this badge, use the following URL format `${argoCdBaseUrl}/api/badge?nam
The URLs for status image are available on application details page:

1. Navigate to application details page and click on 'Details' button.
1. Scroll down to 'Status Badge' section.
1. Select required template such as URL, Markdown etc.
2. Scroll down to 'Status Badge' section.
3. Select required template such as URL, Markdown etc.
for the status image URL in markdown, html, etc are available .
1. Copy the text and paste it into your README or website.
4. Copy the text and paste it into your README or website.

The application name may optionally be displayed in the status badge by adding the `?showAppName=true` query parameter.

For example, `${argoCdBaseUrl}/api/badge?name=${appName}&showAppName=true`.
To remove the application name from the badge, remove the query parameter from the URL or set it to `false`.
48 changes: 47 additions & 1 deletion server/badge/badge.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,10 +42,28 @@ var (
leftTextPattern = regexp.MustCompile(`id="leftText" [^>]*>([^<]*)`)
rightTextPattern = regexp.MustCompile(`id="rightText" [^>]*>([^<]*)`)
revisionTextPattern = regexp.MustCompile(`id="revisionText" [^>]*>([^<]*)`)
titleTextPattern = regexp.MustCompile(`id="titleText" [^>]*>([^<]*)`)
titleRectWidthPattern = regexp.MustCompile(`(id="titleRect" .* width=)("0")`)
rightRectWidthPattern = regexp.MustCompile(`(id="rightRect" .* width=)("\d*")`)
leftRectYCoodPattern = regexp.MustCompile(`(id="leftRect" .* y=)("\d*")`)
rightRectYCoodPattern = regexp.MustCompile(`(id="rightRect" .* y=)("\d*")`)
revisionRectYCoodPattern = regexp.MustCompile(`(id="revisionRect" .* y=)("\d*")`)
leftTextYCoodPattern = regexp.MustCompile(`(id="leftText" .* y=)("\d*")`)
rightTextYCoodPattern = regexp.MustCompile(`(id="rightText" .* y=)("\d*")`)
revisionTextYCoodPattern = regexp.MustCompile(`(id="revisionText" .* y=)("\d*")`)
svgHeightPattern = regexp.MustCompile(`^(<svg .* height=)("\d*")`)
logoYCoodPattern = regexp.MustCompile(`(<image .* y=)("\d*")`)
)

const (
svgWidthWithRevision = 192
svgWidthWithRevision = 192
svgWidthWithoutRevision = 131
svgHeightWithAppName = 40
badgeRowHeight = 20
statusRowYCoodWithAppName = 330
logoYCoodWithAppName = 22
leftRectWidth = 77
widthPerChar = 6
)

func replaceFirstGroupSubMatch(re *regexp.Regexp, str string, repl string) string {
Expand All @@ -71,9 +89,12 @@ func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
health := healthutil.HealthStatusUnknown
status := appv1.SyncStatusCodeUnknown
revision := ""
applicationName := ""
revisionEnabled := false
enabled := false
displayAppName := false
notFound := false
svgWidth := svgWidthWithoutRevision
if sets, err := h.settingsMgr.GetSettings(); err == nil {
enabled = sets.StatusBadgeEnabled
}
Expand All @@ -100,6 +121,7 @@ func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
if app, err := h.appClientset.ArgoprojV1alpha1().Applications(reqNs).Get(context.Background(), name[0], v1.GetOptions{}); err == nil {
health = app.Status.Health.Status
status = app.Status.Sync.Status
applicationName = name[0]
if app.Status.OperationState != nil && app.Status.OperationState.SyncResult != nil {
revision = app.Status.OperationState.SyncResult.Revision
}
Expand Down Expand Up @@ -175,6 +197,7 @@ func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
if !notFound && revisionEnabled && revision != "" {
// Increase width of SVG and enable display of revision components
badge = svgWidthPattern.ReplaceAllString(badge, fmt.Sprintf(`<svg width="%d" $2`, svgWidthWithRevision))
svgWidth = svgWidthWithRevision
badge = displayNonePattern.ReplaceAllString(badge, `display="inline"`)
badge = revisionRectColorPattern.ReplaceAllString(badge, fmt.Sprintf(`id="revisionRect" fill="%s" $2`, rightColorString))
shortRevision := revision
Expand All @@ -184,6 +207,29 @@ func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
badge = replaceFirstGroupSubMatch(revisionTextPattern, badge, fmt.Sprintf("(%s)", shortRevision))
}

if showAppNameParam, ok := r.URL.Query()["showAppName"]; ok && enabled && strings.EqualFold(showAppNameParam[0], "true") {
displayAppName = true
}

if displayAppName && applicationName != "" {
titleRectWidth := len(applicationName) * widthPerChar
var longerWidth int = max(titleRectWidth, svgWidth)
rightRectWidth := longerWidth - leftRectWidth
fmt.Println(len(applicationName))
badge = titleRectWidthPattern.ReplaceAllString(badge, fmt.Sprintf(`$1"%d"`, longerWidth))
badge = rightRectWidthPattern.ReplaceAllString(badge, fmt.Sprintf(`$1"%d"`, rightRectWidth))
badge = replaceFirstGroupSubMatch(titleTextPattern, badge, applicationName)
badge = leftRectYCoodPattern.ReplaceAllString(badge, fmt.Sprintf(`$1"%d"`, badgeRowHeight))
badge = rightRectYCoodPattern.ReplaceAllString(badge, fmt.Sprintf(`$1"%d"`, badgeRowHeight))
badge = revisionRectYCoodPattern.ReplaceAllString(badge, fmt.Sprintf(`$1"%d"`, badgeRowHeight))
badge = leftTextYCoodPattern.ReplaceAllString(badge, fmt.Sprintf(`$1"%d"`, statusRowYCoodWithAppName))
badge = rightTextYCoodPattern.ReplaceAllString(badge, fmt.Sprintf(`$1"%d"`, statusRowYCoodWithAppName))
badge = revisionTextYCoodPattern.ReplaceAllString(badge, fmt.Sprintf(`$1"%d"`, statusRowYCoodWithAppName))
badge = svgHeightPattern.ReplaceAllString(badge, fmt.Sprintf(`$1"%d"`, svgHeightWithAppName))
badge = logoYCoodPattern.ReplaceAllString(badge, fmt.Sprintf(`$1"%d"`, logoYCoodWithAppName))
badge = svgWidthPattern.ReplaceAllString(badge, fmt.Sprintf(`<svg width="%d" $2`, longerWidth))
}

w.Header().Set("Content-Type", "image/svg+xml")

//Ask cache's to not cache the contents in order prevent the badge from becoming stale
Expand Down
60 changes: 60 additions & 0 deletions server/badge/badge_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@ func TestHandlerFeatureIsEnabled(t *testing.T) {
assert.Equal(t, toRGBString(Green), rightRectColorPattern.FindStringSubmatch(response)[1])
assert.Equal(t, "Healthy", leftTextPattern.FindStringSubmatch(response)[1])
assert.Equal(t, "Synced", rightTextPattern.FindStringSubmatch(response)[1])
assert.NotContains(t, response, "test-app")
assert.NotContains(t, response, "(aa29b85)")
}

Expand Down Expand Up @@ -148,6 +149,7 @@ func TestHandlerFeatureProjectIsEnabled(t *testing.T) {
assert.Equal(t, toRGBString(tt.statusColor), rightRectColorPattern.FindStringSubmatch(response)[1])
assert.Equal(t, tt.health, leftTextPattern.FindStringSubmatch(response)[1])
assert.Equal(t, tt.status, rightTextPattern.FindStringSubmatch(response)[1])
assert.Equal(t, "\"20\"", svgHeightPattern.FindStringSubmatch(response)[2])
}
}
}
Expand All @@ -170,6 +172,7 @@ func TestHandlerNamespacesIsEnabled(t *testing.T) {
assert.Equal(t, toRGBString(Green), rightRectColorPattern.FindStringSubmatch(response)[1])
assert.Equal(t, "Healthy", leftTextPattern.FindStringSubmatch(response)[1])
assert.Equal(t, "Synced", rightTextPattern.FindStringSubmatch(response)[1])
assert.NotContains(t, response, "test-app")
assert.NotContains(t, response, "(aa29b85)")
})

Expand Down Expand Up @@ -268,6 +271,7 @@ func TestHandlerFeatureIsEnabledRevisionIsEnabled(t *testing.T) {
assert.Equal(t, toRGBString(Green), rightRectColorPattern.FindStringSubmatch(response)[1])
assert.Equal(t, "Healthy", leftTextPattern.FindStringSubmatch(response)[1])
assert.Equal(t, "Synced", rightTextPattern.FindStringSubmatch(response)[1])
assert.NotContains(t, response, "test-app")
assert.Contains(t, response, "(aa29b85)")
}

Expand All @@ -291,6 +295,7 @@ func TestHandlerRevisionIsEnabledNoOperationState(t *testing.T) {
assert.Equal(t, toRGBString(Green), rightRectColorPattern.FindStringSubmatch(response)[1])
assert.Equal(t, "Healthy", leftTextPattern.FindStringSubmatch(response)[1])
assert.Equal(t, "Synced", rightTextPattern.FindStringSubmatch(response)[1])
assert.NotContains(t, response, "test-app")
assert.NotContains(t, response, "(aa29b85)")
}

Expand Down Expand Up @@ -331,4 +336,59 @@ func TestHandlerFeatureIsDisabled(t *testing.T) {
assert.Equal(t, toRGBString(Purple), rightRectColorPattern.FindStringSubmatch(response)[1])
assert.Equal(t, "Unknown", leftTextPattern.FindStringSubmatch(response)[1])
assert.Equal(t, "Unknown", rightTextPattern.FindStringSubmatch(response)[1])
assert.NotContains(t, response, "test-app")
assert.Equal(t, "\"20\"", svgHeightPattern.FindStringSubmatch(response)[2])
}

func TestHandlerApplicationNameInBadgeIsEnabled(t *testing.T) {
settingsMgr := settings.NewSettingsManager(context.Background(), fake.NewSimpleClientset(&argoCDCm, &argoCDSecret), "default")
handler := NewHandler(appclientset.NewSimpleClientset(&testApp), settingsMgr, "default", []string{})
req, err := http.NewRequest(http.MethodGet, "/api/badge?name=test-app&showAppName=true", nil)
assert.NoError(t, err)

rr := httptest.NewRecorder()
handler.ServeHTTP(rr, req)

assert.Equal(t, "private, no-store", rr.Header().Get("Cache-Control"))
assert.Equal(t, "*", rr.Header().Get("Access-Control-Allow-Origin"))

response := rr.Body.String()
assert.Equal(t, toRGBString(Green), leftRectColorPattern.FindStringSubmatch(response)[1])
assert.Equal(t, toRGBString(Green), rightRectColorPattern.FindStringSubmatch(response)[1])
assert.Equal(t, "Healthy", leftTextPattern.FindStringSubmatch(response)[1])
assert.Equal(t, "Synced", rightTextPattern.FindStringSubmatch(response)[1])
assert.NotContains(t, response, "(aa29b85)")

assert.Equal(t, "test-app", titleTextPattern.FindStringSubmatch(response)[1])
assert.Equal(t, fmt.Sprintf("\"%d\"", svgHeightWithAppName), svgHeightPattern.FindStringSubmatch(response)[2])
assert.Equal(t, fmt.Sprintf("\"%d\"", badgeRowHeight), leftRectYCoodPattern.FindStringSubmatch(response)[2])
assert.Equal(t, fmt.Sprintf("\"%d\"", badgeRowHeight), rightRectYCoodPattern.FindStringSubmatch(response)[2])
assert.Equal(t, fmt.Sprintf("\"%d\"", badgeRowHeight), revisionRectYCoodPattern.FindStringSubmatch(response)[2])
assert.Equal(t, fmt.Sprintf("\"%d\"", logoYCoodWithAppName), logoYCoodPattern.FindStringSubmatch(response)[2])
}

func TestHandlerApplicationNameInBadgeIsDisabled(t *testing.T) {

settingsMgr := settings.NewSettingsManager(context.Background(), fake.NewSimpleClientset(&argoCDCm, &argoCDSecret), "default")
handler := NewHandler(appclientset.NewSimpleClientset(&testApp), settingsMgr, "default", []string{})
req, err := http.NewRequest(http.MethodGet, "/api/badge?name=test-app", nil)
assert.NoError(t, err)

rr := httptest.NewRecorder()
handler.ServeHTTP(rr, req)

assert.Equal(t, "private, no-store", rr.Header().Get("Cache-Control"))
assert.Equal(t, "*", rr.Header().Get("Access-Control-Allow-Origin"))

response := rr.Body.String()
assert.Equal(t, toRGBString(Green), leftRectColorPattern.FindStringSubmatch(response)[1])
assert.Equal(t, toRGBString(Green), rightRectColorPattern.FindStringSubmatch(response)[1])
assert.Equal(t, "Healthy", leftTextPattern.FindStringSubmatch(response)[1])
assert.Equal(t, "Synced", rightTextPattern.FindStringSubmatch(response)[1])
assert.Equal(t, "\"20\"", svgHeightPattern.FindStringSubmatch(response)[2])
assert.Equal(t, "\"0\"", leftRectYCoodPattern.FindStringSubmatch(response)[2])
assert.Equal(t, "\"0\"", rightRectYCoodPattern.FindStringSubmatch(response)[2])
assert.Equal(t, "\"0\"", revisionRectYCoodPattern.FindStringSubmatch(response)[2])
assert.Equal(t, "\"2\"", logoYCoodPattern.FindStringSubmatch(response)[2])
assert.NotContains(t, response, "test-app")
}
2 changes: 1 addition & 1 deletion ui/src/app/shared/components/badge-panel/badge-panel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ export const BadgePanel = ({app, project, appNamespace, nsEnabled}: {app?: strin
let entityURL = '';
let alt = '';
if (app) {
badgeURL = `${root}api/badge?name=${app}&revision=true`;
badgeURL = `${root}api/badge?name=${app}&revision=true&showAppName=true`;
if (nsEnabled) {
badgeURL += `&namespace=${appNamespace}`;
}
Expand Down

0 comments on commit 7fe1263

Please sign in to comment.