Skip to content

Fix missing Publisher/Admin favicon and Admin copyright year (#4972)#1332

Open
Tharsanan1 wants to merge 3 commits intowso2:mainfrom
Tharsanan1:fix/issue-4972
Open

Fix missing Publisher/Admin favicon and Admin copyright year (#4972)#1332
Tharsanan1 wants to merge 3 commits intowso2:mainfrom
Tharsanan1:fix/issue-4972

Conversation

@Tharsanan1
Copy link
Copy Markdown
Contributor

Summary

Fixes two independent cosmetic UI bugs reported in wso2/api-manager#4972:

  • Bug 1 — Publisher/Admin favicon not visible: Publisher and Admin entry templates emitted only the legacy <link rel="shortcut icon"> tag. Modern browsers prefer <link rel="icon" type="image/png"> and some contexts do not reliably fall back to the legacy form, so the browser tab icon did not render. DevPortal already emits both tags; this change mirrors that pattern on Publisher and Admin.
  • Bug 2 — Admin footer year is 2025: The Admin Copyright component's defaultMessage still said © 2025 while Publisher and DevPortal were already rolled forward to 2026 in commit d93574b ("Update Footers and Headers"). The Admin JSX was missed in that commit.

Issue: wso2/api-manager#4972

Changes

File Change
portals/publisher/src/main/webapp/site/public/pages/index.jsp.hbs Add <link rel="icon" type="image/png" href="<%= context%>/site/public/images/_favicon.png">
portals/publisher/src/main/webapp/publisher/index.html Add <link rel="icon" type="image/png" href="/site/public/images/_favicon.png"> (static webpack-dev variant)
portals/admin/src/main/webapp/site/public/pages/index.jsp.hbs Add <link rel="icon" type="image/png" href="<%= context%>/site/public/images/_favicon.png">
portals/admin/src/main/webapp/admin/index.ejs Add <link rel="icon" type="image/png" href="/site/public/images/_favicon.png">
portals/admin/src/main/webapp/source/src/app/components/Base/index.jsx © 2025© 2026 in the Copyright defaultMessage

Asset dependency: _favicon.png already exists alongside favicon.png in all three portals' site/public/images/ directories with identical bytes (md5 0ca49db64a600f55344ab6970935859d). No binary asset change is needed.

Verification

Built the rebuilt admin.war and publisher.war from source, patched them into a fresh wso2am-4.7.0 pack, and ran a Playwright + curl regression check. Both bugs pass; DevPortal is unchanged.

Bug 1 — favicon tags now emitted on all three portals (HTML head):

PUBLISHER:  <link rel="shortcut icon" href="/publisher/site/public/images/favicon.png">
            <link rel="icon" type="image/png" href="/publisher/site/public/images/_favicon.png">
ADMIN:      <link rel="shortcut icon" href="/admin/site/public/images/favicon.png">
            <link rel="icon" type="image/png" href="/admin/site/public/images/_favicon.png">
DEVPORTAL:  <link rel="shortcut icon" href="/devportal/site/public/images/favicon.png">
            <link rel="icon" type="image/png" href="/devportal/site/public/images/_favicon.png">

GET /{portal}/site/public/images/_favicon.png returns HTTP 200 image/png 36131 bytes on all three.

Bug 2 — Admin footer renders correctly:

footerText: "WSO2 API-M v4.7.0 | © 2026 WSO2 LLC"

Server log: no ERROR / FATAL / stack-trace lines after startup.

Regression tests

This PR also adds four regression test suites under portals/publisher/src/main/webapp/source/Tests/Unit/Issue4972/ plus an un-skipped rewrite of the Publisher Footer.test.jsx. Tests are colocated under Publisher because the Admin webapp does not currently have a working jest configuration. Coverage:

  • favicon.test.js — asserts all six portal entry templates (index.jsp.hbs + index.html / index.ejs across Publisher, Admin, DevPortal) emit both <link rel="shortcut icon"> and <link rel="icon" type="image/png"> referencing _favicon.png, and that the _favicon.png asset actually exists in every portal's site/public/images/ directory.
  • copyrightYear.test.js — derives the expected year from Publisher's Footer.jsx defaultMessage (known-correct reference) and asserts the Admin and DevPortal Base/index.jsx Copyright components carry the same year. Explicit negative assertion on © 2025.
  • localeYear.test.js — scans every portal's locales/*.json for the Base.Footer.Footer.product_details key and asserts any non-empty translation carries the current year.
  • Footer.test.jsx — un-skipped; asserts the rendered footer text via IntlProvider + ThemeProvider.

Regression-catches-the-bug check: reverting the fix and re-running the tests produces 2 suites failed / 6 tests failed — precisely the six assertions that act as regression guards. Restoring the fix returns the suite to 48 passed / 48 total.

Test plan

  • Dev test: rebuilt admin.war + publisher.war, patched into fresh wso2am-4.7.0 pack, verified via Playwright that both bugs are resolved and DevPortal is unchanged
  • Regression tests added and passing (48/48)
  • Server log clean after redeployment (no ERROR / FATAL / stack-trace)
  • Reviewer: confirm non-English portals/admin/src/main/webapp/site/public/locales/*.json files do not still carry © 2025 at the Base.Footer.Footer.product_details key (the JSX fallback is now 2026 and en.json / fr.json in source are already 2026, but other locales were not audited in this change)

🤖 Generated with Claude Code

Tharsanan1 and others added 3 commits April 22, 2026 15:50
The Publisher and Admin entry templates emitted only the legacy
<link rel="shortcut icon"> tag. Modern browsers prefer
<link rel="icon" type="image/png"> and some contexts do not fall back,
so the tab icon did not render. DevPortal already emits both tags;
this change mirrors that pattern on Publisher and Admin.

Fixes wso2/api-manager#4972 (bug 1)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The Admin Copyright component's defaultMessage still said "© 2025"
while Publisher and DevPortal were already rolled forward to 2026.
Updated the JSX defaultMessage so the rebuilt admin bundle carries
the correct year.

Fixes wso2/api-manager#4972 (bug 2)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Un-skip and rewrite the dormant Publisher Footer test to assert the
  rendered text carries the expected year.
- Add Issue4972/favicon.test.js: scans all six portal entry templates
  (Publisher/Admin/DevPortal x index.jsp.hbs + index.html/index.ejs)
  and asserts each emits both <link rel="shortcut icon"> and
  <link rel="icon" type="image/png"> referencing _favicon.png, and that
  the _favicon.png asset exists in each portal's site/public/images/.
- Add Issue4972/copyrightYear.test.js: source-scan regression guard
  derives the expected year from Publisher's Footer.jsx and asserts
  Admin and DevPortal Copyright components carry the same year.
- Add Issue4972/localeYear.test.js: locale-bundle regression guard
  scans every portal's locales/*.json for the product_details key.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 22, 2026

📝 Walkthrough

Overview

This PR fixes two UI issues in the WSO2 API Manager Publisher and Admin portals:

  1. Missing favicon: The Publisher and Admin portals were missing the modern PNG favicon declaration, only having the legacy shortcut icon link
  2. Outdated copyright year: The Admin portal footer incorrectly displayed "© 2025" instead of "© 2026"

Changes

HTML/Template Files

  • Publisher and Admin (index.jsp.hbs and index.html): Added <link rel="icon" type="image/png" href=".../_favicon.png"> alongside existing shortcut icon declarations to enable modern favicon display

Copyright Year Update

  • Admin Footer (Base/index.jsx): Updated the copyright year from 2025 to 2026 in the footer's defaultMessage

Test Coverage

  • Favicon regression test (favicon.test.js): Validates that both legacy and modern favicon tags are present in Publisher, Admin, and DevPortal templates, and confirms the PNG favicon asset exists
  • Copyright year test (copyrightYear.test.js): Ensures footer copyright year matches across Publisher, Admin, and DevPortal, preventing year mismatch regressions
  • Locale year test (localeYear.test.js): Scans all locale JSON bundles across portals to verify copyright year consistency in translated messages
  • Footer component test (Footer.test.jsx): Updated test suite to validate footer rendering with correct year (2026) and proper message override handling

Verification

  • Confirmed _favicon.png asset exists and is consistent across all portals (36131 bytes)
  • All HTTP requests for _favicon.png return 200 status with image/png content type
  • Footer renders as "WSO2 API-M v4.7.0 | © 2026 WSO2 LLC"
  • 48 regression test cases added, all passing

Walkthrough

This pull request adds favicon support enhancements and updates copyright year information across multiple portals. Changes include: adding PNG favicon declarations (<link rel="icon" type="image/png"> pointing to _favicon.png) to HTML templates in the admin, publisher, and devportal portals; updating the copyright year in the Base Footer component from 2025 to 2026; and introducing three new Jest test suites to enforce favicon presence across portal templates, validate copyright year consistency across source files and locale bundles, and verify the Footer component renders the correct copyright year. Additionally, an existing Footer component test was refactored from a skipped snapshot test to an active React Testing Library test with explicit assertions.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 16.67% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title directly and clearly summarizes the main changes: fixing missing favicon tags in Publisher/Admin and updating the copyright year in Admin footer.
Description check ✅ Passed The description is comprehensive and directly related to the changeset, clearly documenting both bugs fixed, the changes made, verification steps, and regression tests added.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@sonarqubecloud
Copy link
Copy Markdown

Quality Gate Failed Quality Gate failed

Failed conditions
8.0% Duplication on New Code (required ≤ 3%)

See analysis details on SonarQube Cloud

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In
`@portals/publisher/src/main/webapp/source/Tests/Unit/Issue4972/favicon.test.js`:
- Around line 82-91: The two tests ("emits the modern <link rel=\"icon\"
type=\"image/png\"> tag" and "points the modern <link rel=\"icon\"> at
_favicon.png") are too loose because they allow `_favicon.png` to appear
anywhere; tighten the second assertion to match a single <link ...> tag that
contains rel="icon", type="image/png", and the expectedHref together. Replace
the toContain(expectedHref) check on markup with a toMatch using a RegExp that
looks for a single <link[^>]* rel=["']icon["'][^>]* type=["']image\/png["'][^>]*
href=["']<expectedHref>["'][^>]*>, and if expectedHref may contain
regex-significant chars create/use an escapeRegExp utility to escape
expectedHref before interpolating into the RegExp.

In
`@portals/publisher/src/main/webapp/source/Tests/Unit/Issue4972/localeYear.test.js`:
- Around line 55-80: The test silently skips missing portal locale directories
in collectLocaleFiles (portals array in collectLocaleFiles) which can hide
missing admin/devportal coverage; update the test or collectLocaleFiles so it
asserts per-portal presence: for each portal in portals (publisher, admin,
devportal) check the directory at path.join(portalsRoot,
`${portal}/src/main/webapp/site/public/locales`) exists and contains at least
one .json, and fail the test if any portal directory is missing or empty
(reference collectLocaleFiles, portals array, and localeFiles variable to
implement this per-portal existence/coverage check).
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: d935594a-38cf-4183-b60a-2fbde5221fe0

📥 Commits

Reviewing files that changed from the base of the PR and between 4722713 and 4cfdb61.

⛔ Files ignored due to path filters (1)
  • portals/publisher/src/main/webapp/source/src/app/components/Base/Footer/__snapshots__/Footer.test.jsx.snap is excluded by !**/*.snap
📒 Files selected for processing (9)
  • portals/admin/src/main/webapp/admin/index.ejs
  • portals/admin/src/main/webapp/site/public/pages/index.jsp.hbs
  • portals/admin/src/main/webapp/source/src/app/components/Base/index.jsx
  • portals/publisher/src/main/webapp/publisher/index.html
  • portals/publisher/src/main/webapp/site/public/pages/index.jsp.hbs
  • portals/publisher/src/main/webapp/source/Tests/Unit/Issue4972/copyrightYear.test.js
  • portals/publisher/src/main/webapp/source/Tests/Unit/Issue4972/favicon.test.js
  • portals/publisher/src/main/webapp/source/Tests/Unit/Issue4972/localeYear.test.js
  • portals/publisher/src/main/webapp/source/src/app/components/Base/Footer/Footer.test.jsx

Comment on lines +82 to +91
it('emits the modern <link rel="icon" type="image/png"> tag', () => {
expect(markup).toMatch(/rel=["']icon["']\s+type=["']image\/png["']/);
});

it('points the modern <link rel="icon"> at _favicon.png', () => {
// The surrounding <link ...> may embed templating tokens like
// <%= context%> so we don't try to match the whole tag — we
// just assert _favicon.png is referenced in the file alongside
// a rel="icon" tag (asserted separately above).
expect(markup).toContain(expectedHref);
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Tie _favicon.png to the modern favicon link.

The current assertions can pass if _favicon.png appears elsewhere while the modern <link rel="icon"> points to a different asset. Match a single link tag containing rel, type, and the expected href.

Proposed test tightening
-            it('emits the modern <link rel="icon" type="image/png"> tag', () => {
-                expect(markup).toMatch(/rel=["']icon["']\s+type=["']image\/png["']/);
-            });
-
-            it('points the modern <link rel="icon"> at _favicon.png', () => {
-                // The surrounding <link ...> may embed templating tokens like
-                // <%= context%> so we don't try to match the whole tag — we
-                // just assert _favicon.png is referenced in the file alongside
-                // a rel="icon" tag (asserted separately above).
-                expect(markup).toContain(expectedHref);
+            it('emits the modern <link rel="icon" type="image/png"> tag pointing at _favicon.png', () => {
+                const escapedHref = expectedHref.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
+                expect(markup).toMatch(new RegExp(
+                    `<link\\b(?=[^>]*\\brel=["']icon["'])(?=[^>]*\\btype=["']image/png["'])`
+                    + `(?=[^>]*\\bhref=["'][^"']*${escapedHref}["'])[^>]*>`,
+                ));
             });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@portals/publisher/src/main/webapp/source/Tests/Unit/Issue4972/favicon.test.js`
around lines 82 - 91, The two tests ("emits the modern <link rel=\"icon\"
type=\"image/png\"> tag" and "points the modern <link rel=\"icon\"> at
_favicon.png") are too loose because they allow `_favicon.png` to appear
anywhere; tighten the second assertion to match a single <link ...> tag that
contains rel="icon", type="image/png", and the expectedHref together. Replace
the toContain(expectedHref) check on markup with a toMatch using a RegExp that
looks for a single <link[^>]* rel=["']icon["'][^>]* type=["']image\/png["'][^>]*
href=["']<expectedHref>["'][^>]*>, and if expectedHref may contain
regex-significant chars create/use an escapeRegExp utility to escape
expectedHref before interpolating into the RegExp.

Comment on lines +55 to +80
function collectLocaleFiles() {
const portals = ['publisher', 'admin', 'devportal'];
const out = [];
portals.forEach((portal) => {
const dir = path.join(portalsRoot, `${portal}/src/main/webapp/site/public/locales`);
if (!fs.existsSync(dir)) return;
fs.readdirSync(dir).forEach((f) => {
if (f.endsWith('.json')) {
out.push({ portal, locale: f, file: path.join(dir, f) });
}
});
});
return out;
}

describe('Issue #4972 — locale files for Base.Footer.Footer.product_details', () => {
const year = expectedYear();
const localeFiles = collectLocaleFiles();

it('the publisher defaultMessage exposes an expected year', () => {
expect(year).toMatch(/^\d{4}$/);
expect(Number(year)).toBeGreaterThanOrEqual(2026);
});

it('locale directories were discovered', () => {
expect(localeFiles.length).toBeGreaterThan(0);
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Fail when an expected portal locale directory is missing.

Line 60 silently skips missing portal locale dirs, and Line 80 only proves that at least one locale file exists. That can drop admin/devportal coverage without failing this regression test.

Proposed test hardening
+const PORTALS = ['publisher', 'admin', 'devportal'];
+
 function collectLocaleFiles() {
-    const portals = ['publisher', 'admin', 'devportal'];
     const out = [];
-    portals.forEach((portal) => {
+    PORTALS.forEach((portal) => {
         const dir = path.join(portalsRoot, `${portal}/src/main/webapp/site/public/locales`);
-        if (!fs.existsSync(dir)) return;
+        if (!fs.existsSync(dir)) {
+            throw new Error(`Missing locale directory for ${portal}: ${dir}`);
+        }
         fs.readdirSync(dir).forEach((f) => {
             if (f.endsWith('.json')) {
                 out.push({ portal, locale: f, file: path.join(dir, f) });
             }
         });
@@
     it('locale directories were discovered', () => {
         expect(localeFiles.length).toBeGreaterThan(0);
+        expect(new Set(localeFiles.map(({ portal }) => portal))).toEqual(new Set(PORTALS));
     });
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
function collectLocaleFiles() {
const portals = ['publisher', 'admin', 'devportal'];
const out = [];
portals.forEach((portal) => {
const dir = path.join(portalsRoot, `${portal}/src/main/webapp/site/public/locales`);
if (!fs.existsSync(dir)) return;
fs.readdirSync(dir).forEach((f) => {
if (f.endsWith('.json')) {
out.push({ portal, locale: f, file: path.join(dir, f) });
}
});
});
return out;
}
describe('Issue #4972 — locale files for Base.Footer.Footer.product_details', () => {
const year = expectedYear();
const localeFiles = collectLocaleFiles();
it('the publisher defaultMessage exposes an expected year', () => {
expect(year).toMatch(/^\d{4}$/);
expect(Number(year)).toBeGreaterThanOrEqual(2026);
});
it('locale directories were discovered', () => {
expect(localeFiles.length).toBeGreaterThan(0);
const PORTALS = ['publisher', 'admin', 'devportal'];
function collectLocaleFiles() {
const out = [];
PORTALS.forEach((portal) => {
const dir = path.join(portalsRoot, `${portal}/src/main/webapp/site/public/locales`);
if (!fs.existsSync(dir)) {
throw new Error(`Missing locale directory for ${portal}: ${dir}`);
}
fs.readdirSync(dir).forEach((f) => {
if (f.endsWith('.json')) {
out.push({ portal, locale: f, file: path.join(dir, f) });
}
});
});
return out;
}
describe('Issue `#4972` — locale files for Base.Footer.Footer.product_details', () => {
const year = expectedYear();
const localeFiles = collectLocaleFiles();
it('the publisher defaultMessage exposes an expected year', () => {
expect(year).toMatch(/^\d{4}$/);
expect(Number(year)).toBeGreaterThanOrEqual(2026);
});
it('locale directories were discovered', () => {
expect(localeFiles.length).toBeGreaterThan(0);
expect(new Set(localeFiles.map(({ portal }) => portal))).toEqual(new Set(PORTALS));
});
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@portals/publisher/src/main/webapp/source/Tests/Unit/Issue4972/localeYear.test.js`
around lines 55 - 80, The test silently skips missing portal locale directories
in collectLocaleFiles (portals array in collectLocaleFiles) which can hide
missing admin/devportal coverage; update the test or collectLocaleFiles so it
asserts per-portal presence: for each portal in portals (publisher, admin,
devportal) check the directory at path.join(portalsRoot,
`${portal}/src/main/webapp/site/public/locales`) exists and contains at least
one .json, and fail the test if any portal directory is missing or empty
(reference collectLocaleFiles, portals array, and localeFiles variable to
implement this per-portal existence/coverage check).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant