Skip to content

Commit

Permalink
feat: add openGraphTags to pages
Browse files Browse the repository at this point in the history
- Adds a new component that combines all meta tags for OpenGraph

- Refactors GroupedTestStatus to receive a precalculated status count in order to reuse it for the TreeDetails description

- Refactors TestDetails so that it receives the testId directly instead of as a component parameter

- Fixes buildDetails general section title to consider null configs

- Adds a function that returns the culprit code of an issue as a string so that it can be used in the description

Closes #935
  • Loading branch information
MarceloRobert committed Feb 25, 2025
1 parent 9ae9e92 commit dca0452
Show file tree
Hide file tree
Showing 21 changed files with 635 additions and 137 deletions.
Binary file added dashboard/public/kernelci-logo-card.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
46 changes: 32 additions & 14 deletions dashboard/src/components/BuildDetails/BuildDetails.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { useIntl } from 'react-intl';
import { ErrorBoundary } from 'react-error-boundary';
import { useCallback, useMemo, useState, type JSX } from 'react';

import type { LinkProps } from '@tanstack/react-router';
import { useParams, type LinkProps } from '@tanstack/react-router';

import SectionGroup from '@/components/Section/SectionGroup';
import type { ISection } from '@/components/Section/Section';
Expand Down Expand Up @@ -40,6 +40,8 @@ import { StatusIcon } from '@/components/Icons/StatusIcons';

import PageWithTitle from '@/components/PageWithTitle';

import { MemoizedBuildDetailsOGTags } from '@/components/OpenGraphTags/BuildDetailsOGTags';

import BuildDetailsTestSection from './BuildDetailsTestSection';

interface BuildDetailsProps {
Expand Down Expand Up @@ -96,17 +98,20 @@ const BuildDetails = ({
[setSheetType],
);

const buildDetailsTitle: string = useMemo(() => {
if (data?.git_commit_name && data.config_name) {
return `${data.git_commit_name}${data.config_name}`;
}
return data?.git_commit_name ?? data?.config_name ?? buildId;
}, [buildId, data]);

const generalSections: ISection[] = useMemo(() => {
if (!data) {
return [];
}
return [
{
title: valueOrEmpty(
data.git_commit_name
? `${data.git_commit_name}${data.config_name}`
: data.config_name,
),
title: buildDetailsTitle,
leftIcon: <StatusIcon status={data?.valid} />,
eyebrow: formatMessage({ id: 'buildDetails.buildDetails' }),
subsections: [
Expand Down Expand Up @@ -205,23 +210,36 @@ const BuildDetails = ({
],
},
];
}, [data, formatMessage, hasUsefulLogInfo, buildId, setSheetToLog]);
}, [
data,
buildDetailsTitle,
formatMessage,
buildId,
hasUsefulLogInfo,
setSheetToLog,
]);

const sectionsData: ISection[] = useMemo(() => {
return [...generalSections, miscSection, filesSection].filter(
section => section !== undefined,
);
}, [generalSections, miscSection, filesSection]);

const buildTitle = `${data?.tree_name} ${data?.git_commit_name}`;
const buildDetailsTabTitle: string = useMemo(() => {
const buildTitle = `${data?.tree_name} ${data?.git_commit_name}`;
return formatMessage(
{ id: 'title.buildDetails' },
{ buildName: getTitle(buildTitle, isLoading) },
);
}, [data?.git_commit_name, data?.tree_name, formatMessage, isLoading]);

return (
<PageWithTitle
title={formatMessage(
{ id: 'title.buildDetails' },
{ buildName: getTitle(buildTitle, isLoading) },
)}
>
<PageWithTitle title={buildDetailsTabTitle}>
<MemoizedBuildDetailsOGTags
tabTitle={buildDetailsTabTitle}
descriptionTitle={buildDetailsTitle}
data={data}
/>
<QuerySwitcher
status={status}
data={data}
Expand Down
48 changes: 33 additions & 15 deletions dashboard/src/components/IssueDetails/IssueDetails.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,9 @@ import PageWithTitle from '@/components/PageWithTitle';

import { getTitle } from '@/utils/utils';

import { IssueCulprit } from '@/components/Issue/IssueCulprit';
import { getIssueCulprit } from '@/lib/issue';

import { MemoizedIssueDetailsOGTags } from '@/components/OpenGraphTags/IssueDetailsOGTags';

import { IssueDetailsTestSection } from './IssueDetailsTestSection';

Expand Down Expand Up @@ -112,6 +114,20 @@ export const IssueDetails = ({
}
}, [data, issueId]);

const issueCulprit = useMemo(() => {
return getIssueCulprit({
culprit_code: data?.culprit_code,
culprit_harness: data?.culprit_harness,
culprit_tool: data?.culprit_tool,
formatMessage: formatMessage,
});
}, [
data?.culprit_code,
data?.culprit_harness,
data?.culprit_tool,
formatMessage,
]);

const generalSections: ISection[] = useMemo(() => {
if (!data) {
return [];
Expand Down Expand Up @@ -151,13 +167,7 @@ export const IssueDetails = ({
},
{
title: 'issueDetails.culpritTitle',
linkText: (
<IssueCulprit
culprit_code={data.culprit_code}
culprit_harness={data.culprit_harness}
culprit_tool={data.culprit_tool}
/>
),
linkText: issueCulprit,
},
{
title: 'issueDetails.id',
Expand All @@ -172,7 +182,7 @@ export const IssueDetails = ({
],
},
];
}, [data, tagPills, formatMessage]);
}, [data, tagPills, formatMessage, issueCulprit]);

const sectionsData: ISection[] = useMemo(() => {
return [
Expand All @@ -183,13 +193,21 @@ export const IssueDetails = ({
].filter(section => !!section);
}, [generalSections, logspecSection, miscSection, firstIncidentSection]);

const issueDetailsTabTitle = useMemo(() => {
return formatMessage(
{ id: 'title.issueDetails' },
{ issueName: getTitle(data?.comment, isLoading) },
);
}, [data?.comment, formatMessage, isLoading]);

return (
<PageWithTitle
title={formatMessage(
{ id: 'title.issueDetails' },
{ issueName: getTitle(data?.comment, isLoading) },
)}
>
<PageWithTitle title={issueDetailsTabTitle}>
<MemoizedIssueDetailsOGTags
title={issueDetailsTabTitle}
issueCulprit={issueCulprit}
issueId={issueId}
data={data}
/>
<QuerySwitcher
status={status}
data={data}
Expand Down
53 changes: 53 additions & 0 deletions dashboard/src/components/OpenGraphTags/BuildDetailsOGTags.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import type { JSX } from 'react';
import { memo, useMemo } from 'react';

import { useIntl } from 'react-intl';

import { getBuildStatus } from '@/utils/utils';
import type { TBuildDetails } from '@/types/tree/BuildDetails';

import { OpenGraphTags } from './OpenGraphTags';

const BuildDetailsOGTags = ({
descriptionTitle,
tabTitle,
data,
}: {
descriptionTitle: string;
tabTitle: string;
data?: TBuildDetails;
}): JSX.Element => {
const { formatMessage } = useIntl();

const buildDetailsDescription: string = useMemo(() => {
if (!data) {
return formatMessage({ id: 'buildDetails.buildDetails' });
}

const statusDescription =
formatMessage({ id: 'global.status' }) +
': ' +
getBuildStatus(data.valid).toUpperCase();

const treeDescription =
formatMessage({ id: 'global.treeBranch' }) +
': ' +
data.tree_name +
' / ' +
data.git_repository_branch;

const descriptionChunks = [
descriptionTitle,
statusDescription,
treeDescription,
];

return descriptionChunks.join(';\n');
}, [descriptionTitle, data, formatMessage]);

return (
<OpenGraphTags title={tabTitle} description={buildDetailsDescription} />
);
};

export const MemoizedBuildDetailsOGTags = memo(BuildDetailsOGTags);
51 changes: 51 additions & 0 deletions dashboard/src/components/OpenGraphTags/IssueDetailsOGTags.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import { memo, useMemo, type JSX } from 'react';

import { useIntl } from 'react-intl';

import type { TIssueDetails } from '@/types/issueDetails';

import { OpenGraphTags } from './OpenGraphTags';

const IssueDetailsOGTags = ({
title,
issueCulprit,
issueId,
data,
}: {
title: string;
issueCulprit: string;
issueId: string;
data?: TIssueDetails;
}): JSX.Element => {
const { formatMessage } = useIntl();

const issueDetailsDescription: string = useMemo(() => {
if (!data) {
return formatMessage({ id: 'issueDetails.issueDetails' });
}
const versionDescription =
formatMessage({ id: 'issueDetails.version' }) + ': ' + data?.version;

const culpritDescription =
formatMessage({ id: 'issueDetails.culpritTitle' }) + ': ' + issueCulprit;

const firstSeen = data.extra?.[issueId]?.first_incident.first_seen;
const firstSeenDescription = firstSeen
? formatMessage({ id: 'issue.firstSeen' }) +
': ' +
new Date(firstSeen).toLocaleDateString()
: '';

const descriptionChunks = [
versionDescription,
culpritDescription,
firstSeenDescription,
].filter(chunk => chunk !== '');

return descriptionChunks.join(';\n');
}, [data, formatMessage, issueCulprit, issueId]);

return <OpenGraphTags title={title} description={issueDetailsDescription} />;
};

export const MemoizedIssueDetailsOGTags = memo(IssueDetailsOGTags);
58 changes: 58 additions & 0 deletions dashboard/src/components/OpenGraphTags/ListingOGTags.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import type { JSX } from 'react';
import { memo, useMemo } from 'react';

import { useIntl } from 'react-intl';

import type { PossibleMonitorPath } from '@/types/general';
import type { MessagesKey } from '@/locales/messages';

import { OpenGraphTags } from './OpenGraphTags';

const ListingOGTags = ({
monitor,
search,
}: {
monitor: PossibleMonitorPath;
search: string;
}): JSX.Element => {
const { formatMessage } = useIntl();

const listingDescription = useMemo(() => {
let descriptionId: MessagesKey;

switch (monitor) {
case '/tree':
descriptionId = 'treeListing.description';
break;
case '/hardware':
descriptionId = 'hardwareListing.description';
break;
case '/issue':
descriptionId = 'issueListing.description';
break;
}
return (
formatMessage({ id: descriptionId }) +
(search !== ''
? ';\n' + formatMessage({ id: 'global.search' }) + ': ' + search
: '')
);
}, [formatMessage, monitor, search]);

const listingTitle = useMemo(() => {
switch (monitor) {
case '/tree':
return formatMessage({ id: 'treeListing.title' });
case '/hardware':
return formatMessage({ id: 'hardwareListing.title' });
case '/issue':
return formatMessage({ id: 'issueListing.title' });
}
}, [formatMessage, monitor]);

return (
<OpenGraphTags title={listingTitle} description={listingDescription} />
);
};

export const MemoizedListingOGTags = memo(ListingOGTags);
29 changes: 29 additions & 0 deletions dashboard/src/components/OpenGraphTags/OpenGraphTags.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import type { JSX } from 'react';

export const OpenGraphTags = ({
title,
url,
description,
imageUrl = 'https://dashboard.kernelci.org/kernelci-logo-card.png',
type = 'website',
}: {
title: string;
url?: string;
description: string;
imageUrl?: string;
type?: string;
}): JSX.Element => {
return (
<>
<meta property="og:title" content={title} />
<meta property="og:url" content={url ?? window.location.href} />
<meta property="og:description" content={description} />
<meta property="og:image" content={imageUrl} />
<meta property="og:type" content={type} />
<meta property="twitter:title" content={title} />
<meta property="twitter:site" content={url ?? window.location.href} />
<meta property="twitter:description" content={description} />
<meta property="twitter:image" content={imageUrl} />
</>
);
};
Loading

0 comments on commit dca0452

Please sign in to comment.