Skip to content

Commit d6c3b33

Browse files
Merge pull request #77 from Azure-Samples/mirza/fe/external-cta-logic-change
Refactor button text logic and resource type tag handling
2 parents f2e2cd7 + 4b5f4bf commit d6c3b33

7 files changed

Lines changed: 91 additions & 160 deletions

File tree

src/components/gallery/ShowcaseCard/index.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ function ShowcaseCard({
6060
const [isOpen, { setTrue: openDialog, setFalse: dismissDialog }] =
6161
useBoolean(false);
6262

63-
const buttonText = getButtonText(user.website, user.tags);
63+
const buttonText = getButtonText(user.tags, user.website);
6464
const ctaStyles = getButtonStyles(buttonText);
6565

6666
const [githubData, setGithubData] = useState<GitHubRepoInfo>(null);

src/components/gallery/ShowcaseCardPanel/index.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ export default function ShowcaseCardPanel({
4949
key={"tag_" + user.title}
5050
tags={user.tags}
5151
cardPanel={true}
52-
buttonText={getButtonText(user.website || user.source, user.tags)}
52+
buttonText={getButtonText(user.tags, user.website || user.source)}
5353
/>
5454
</div>
5555
{user.image && (
@@ -152,7 +152,7 @@ export default function ShowcaseCardPanel({
152152
>
153153
<div className={styles.buttonContent}>
154154
<span className={styles.buttonText}>
155-
{getButtonText(user.website || user.source, user.tags)}
155+
{getButtonText(user.tags, user.website || user.source)}
156156
</span>
157157
</div>
158158
</PrimaryButton>

src/components/gallery/ShowcaseListTile/index.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ export default function ShowcaseListTile({
2323
}) {
2424
const [isOpen, { setTrue: openDialog, setFalse: dismissDialog }] =
2525
useBoolean(false);
26-
const buttonText = getButtonText(user.website, user.tags);
26+
const buttonText = getButtonText(user.tags, user.website);
2727
const ctaStyles = getButtonStyles(buttonText);
2828
const shouldUseLearningPathContent =
2929
!!user.learningPathTitle && !!user.learningPathDescription;

src/utils/buttonStyleUtils.ts

Lines changed: 19 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -10,13 +10,25 @@ export interface ButtonStyles {
1010
borderRadius: string;
1111
}
1212

13+
/**
14+
* Documentation blue — shared by the parent Documentation tag and all its
15+
* sub-types (Tutorial, How-To Guide, Concepts) so they feel visually unified.
16+
*/
17+
const DOC_STYLE: ButtonStyles = {
18+
backgroundColor: "#ECF9FF",
19+
border: "1px solid #AED4F2",
20+
color: "#0078D4",
21+
borderRadius: "3px 3px 14px 14px",
22+
};
23+
1324
const BUTTON_STYLE_MAP: Record<string, ButtonStyles> = {
14-
Documentation: {
15-
backgroundColor: "#ECF9FF",
16-
border: "1px solid #AED4F2",
17-
color: "#0078D4",
18-
borderRadius: "3px 3px 14px 14px",
19-
},
25+
Documentation: DOC_STYLE,
26+
27+
// Documentation sub-types — same visual language as the parent
28+
Tutorial: DOC_STYLE,
29+
"How-To Guide": DOC_STYLE,
30+
Concepts: DOC_STYLE,
31+
2032
"Solution Accelerator": {
2133
backgroundColor: "#F1FBF1",
2234
border: "1px solid #B7D8B7",
@@ -49,12 +61,7 @@ const BUTTON_STYLE_MAP: Record<string, ButtonStyles> = {
4961
},
5062
};
5163

52-
const DEFAULT_BUTTON_STYLES: ButtonStyles = {
53-
backgroundColor: "#ECF9FF",
54-
border: "1px solid #AED4F2",
55-
color: "#0078D4",
56-
borderRadius: "3px 3px 14px 14px",
57-
};
64+
const DEFAULT_BUTTON_STYLES: ButtonStyles = DOC_STYLE;
5865

5966
/**
6067
* Returns CTA button styles (background, border, text color, border-radius) based on button text.

src/utils/buttonTextUtils.ts

Lines changed: 35 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -3,72 +3,50 @@
33
* Licensed under the MIT License.
44
*/
55

6-
import type { TagType } from '../data/tags';
6+
import { Tags, type TagType } from '../data/tags';
77

88
/**
9-
* Configuration interface for button text rules
9+
* Resource type tag priority order for CTA determination.
10+
* Tags listed first have higher priority. Blog is always last (secondary to all others).
11+
* This is the single source of truth for both CTA text and tag display ordering.
1012
*/
11-
interface ButtonRule {
12-
match: (url: string) => boolean;
13-
text: string;
14-
}
13+
export const RESOURCE_TYPE_PRIORITY: TagType[] = [
14+
'solution-accelerator',
15+
'workshop',
16+
'training',
17+
'video',
18+
'tutorial',
19+
'how-to',
20+
'concepts',
21+
'documentation',
22+
'blog',
23+
];
1524

1625
/**
17-
* Button text rules configuration
18-
* Rules are evaluated in order - first match wins
26+
* Tags that are sub-types of Documentation and should always display
27+
* "Documentation" as the CTA label rather than their own tag label.
1928
*/
20-
const BUTTON_RULES: ButtonRule[] = [
21-
{
22-
match: (url) => url.includes("github.com"),
23-
text: "Workshop",
24-
},
25-
{
26-
match: (url) => url.includes("youtube.com") || url.includes("youtu.be"),
27-
text: "Video",
28-
},
29-
{
30-
match: (url) => url.includes("techcommunity.microsoft.com"),
31-
text: "Blog",
32-
},
33-
{
34-
match: (url) => url.includes("training") || url.includes("/training/"),
35-
text: "Training",
36-
},
37-
{
38-
match: (url) => url.includes("learn.microsoft.com"),
39-
text: "Documentation",
40-
},
41-
{
42-
match: (url) => url.includes("aka.ms"),
43-
text: "Solution Accelerator",
44-
},
45-
];
29+
const DOCUMENTATION_SUB_TYPES = new Set<TagType>(['tutorial', 'how-to', 'concepts']);
4630

4731
/**
48-
* Function to get dynamic button text based on URL and tags
49-
* @param url - The URL to analyze
50-
* @param tags - Optional array of tags to consider for button text
51-
* @returns Appropriate button text based on URL patterns and tags
32+
* Returns the CTA button text for a card, determined purely by its resource type tags
33+
* in priority order. Blog is always secondary to every other resource type.
34+
* tutorial, how-to, and concepts are documentation sub-types and always show "Documentation".
35+
* Cards with no resource type tags return "More".
36+
*
37+
* @param tags - The card's full tag array
38+
* @param url - Unused, kept for interface compatibility
39+
* @returns CTA button text
5240
*/
53-
export function getButtonText(url?: string, tags?: TagType[]): string {
54-
// Guard clause
55-
if (!url) return "No URL";
56-
57-
const lowerUrl = url.toLowerCase();
58-
59-
// Special case: if URL matches blog pattern AND has solution accelerator tag, prioritize solution accelerator CTA
60-
if (lowerUrl.includes("techcommunity.microsoft.com") && tags?.includes('solution-accelerator' as TagType)) {
61-
return "Solution Accelerator";
41+
export function getButtonText(tags: TagType[], _url?: string): string {
42+
// Walk priority list and return the first tag present on the card
43+
for (const priorityTag of RESOURCE_TYPE_PRIORITY) {
44+
if (tags.includes(priorityTag)) {
45+
// Sub-types of Documentation collapse to the parent CTA label
46+
if (DOCUMENTATION_SUB_TYPES.has(priorityTag)) return "Documentation";
47+
return Tags[priorityTag].buttonText ?? priorityTag;
48+
}
6249
}
6350

64-
// If this is a blog link but the card also has a documentation tag,
65-
// prefer showing the documentation CTA instead of the blog CTA.
66-
if (lowerUrl.includes("techcommunity.microsoft.com") && tags?.includes('documentation' as TagType)) {
67-
return "Documentation";
68-
}
69-
70-
// .find is more idiomatic than a loop or if/else chain
71-
const rule = BUTTON_RULES.find(r => r.match(lowerUrl));
72-
73-
return rule?.text ?? "More";
51+
return "More";
7452
}

src/utils/tagPriorityUtils.ts

Lines changed: 30 additions & 85 deletions
Original file line numberDiff line numberDiff line change
@@ -4,97 +4,42 @@
44
*/
55

66
import { TagType } from "../data/tags";
7+
import { RESOURCE_TYPE_PRIORITY } from "./buttonTextUtils";
78

89
/**
9-
* Maps CTA button text to preferred resource type tag priority
10-
* This ensures the most relevant tag appears first based on the card's action
11-
* Content hierarchy: Primary content type (video, workshop, training, etc.)
12-
* comes before delivery format (blog)
13-
* Example: Blog about video → video tag first, then blog tag
10+
* Sorts resource type tags so that the tag matching the card's CTA appears first,
11+
* followed by the remaining resource type tags in priority order.
12+
*
13+
* Since CTA is now determined purely by RESOURCE_TYPE_PRIORITY (highest-priority
14+
* tag wins), sorting by the same order keeps the displayed tag badge consistent
15+
* with the CTA button text.
16+
*
17+
* @param resourceTypeTags - Array of resource type tag objects to sort
18+
* @param _buttonText - Kept for API compatibility; no longer used internally
19+
* @returns Sorted array with the CTA-relevant tag first
1420
*/
15-
export const CTA_TO_RESOURCE_TYPE_PRIORITY: Record<string, TagType[]> = {
16-
// Workshop CTAs - workshop comes first when both workshop and documentation are present
17-
"Workshop": ["workshop", "documentation", "tutorial", "concepts", "how-to", "solution-accelerator", "video", "training", "blog"],
18-
19-
// Training CTAs - training comes first when both training and documentation are present
20-
"Training": ["training", "documentation", "tutorial", "concepts", "how-to", "solution-accelerator", "video", "workshop", "blog"],
21-
22-
// Tutorial CTAs - documentation first, then tutorial content (as it's a doc sub-type)
23-
"Tutorial": ["documentation", "tutorial", "concepts", "how-to", "solution-accelerator", "video", "workshop", "training", "blog"],
24-
25-
// Documentation CTAs - documentation and its sub-types take priority
26-
"Documentation": ["documentation", "tutorial", "concepts", "how-to", "solution-accelerator", "video", "workshop", "training", "blog"],
27-
"Concepts": ["documentation", "concepts", "tutorial", "how-to", "solution-accelerator", "video", "workshop", "training", "blog"],
28-
"How-To Guide": ["documentation", "how-to", "tutorial", "concepts", "solution-accelerator", "video", "workshop", "training", "blog"],
29-
30-
// Video CTAs - documentation first, then doc sub-types, then video content
31-
"Video": ["documentation", "tutorial", "concepts", "how-to", "video", "solution-accelerator", "workshop", "training", "blog"],
32-
33-
// Blog CTAs - if blog is about workshop/training, those come first before documentation
34-
// Priority: workshop → training → documentation → tutorial/concepts/how-to → other content → blog
35-
"Blog": ["workshop", "training", "documentation", "tutorial", "concepts", "how-to", "solution-accelerator", "video", "blog"],
36-
37-
// Sample CTAs - documentation first, then doc sub-types, then samples
38-
"Sample": ["documentation", "tutorial", "concepts", "how-to", "solution-accelerator", "video", "workshop", "training", "blog"],
39-
"GitHub Repo": ["documentation", "tutorial", "concepts", "how-to", "solution-accelerator", "video", "workshop", "training", "blog"],
40-
41-
// Solution Accelerator CTAs - documentation first, then doc sub-types, then solution-accelerator
42-
"Solution Accelerator": ["documentation", "tutorial", "concepts", "how-to", "solution-accelerator", "video", "workshop", "training", "blog"],
43-
44-
// Default fallback for generic CTAs - workshop and training before documentation when present
45-
"More": ["workshop", "training", "documentation", "tutorial", "concepts", "how-to", "solution-accelerator", "video", "blog"]
46-
};
21+
export function sortResourceTypeTagsByCTA(
22+
resourceTypeTags: Array<{ tag: TagType; [key: string]: any }>,
23+
_buttonText?: string
24+
): Array<{ tag: TagType; [key: string]: any }> {
25+
return [...resourceTypeTags].sort((a, b) => {
26+
const aIdx = RESOURCE_TYPE_PRIORITY.indexOf(a.tag);
27+
const bIdx = RESOURCE_TYPE_PRIORITY.indexOf(b.tag);
4728

48-
/**
49-
* Default resource type priority order when no CTA match is found
50-
*/
51-
export const DEFAULT_RESOURCE_TYPE_PRIORITY: TagType[] = [
52-
"documentation",
53-
"tutorial",
54-
"concepts",
55-
"how-to",
56-
"solution-accelerator",
57-
"video",
58-
"workshop",
59-
"training",
60-
"blog"
61-
];
29+
// Both in priority list → sort by position
30+
if (aIdx !== -1 && bIdx !== -1) return aIdx - bIdx;
6231

63-
/**
64-
* Gets the resource type tag priority based on CTA button text
65-
* @param buttonText - The CTA button text from getButtonText()
66-
* @returns Array of resource type tags in priority order
67-
*/
68-
export function getResourceTypePriorityByCTA(buttonText: string): TagType[] {
69-
return CTA_TO_RESOURCE_TYPE_PRIORITY[buttonText] || DEFAULT_RESOURCE_TYPE_PRIORITY;
32+
// Only one is in the list → that one goes first
33+
if (aIdx !== -1) return -1;
34+
if (bIdx !== -1) return 1;
35+
36+
// Neither is in the list → preserve original order
37+
return 0;
38+
});
7039
}
7140

7241
/**
73-
* Sorts resource type tags based on CTA button text priority
74-
* @param resourceTypeTags - Array of resource type tag objects
75-
* @param buttonText - The CTA button text
76-
* @returns Sorted array with CTA-relevant tags first
42+
* Returns the resource type tag priority order.
43+
* Exported for consumers that need to inspect or test the order directly.
7744
*/
78-
export function sortResourceTypeTagsByCTA(
79-
resourceTypeTags: Array<{ tag: TagType; [key: string]: any }>,
80-
buttonText: string
81-
): Array<{ tag: TagType; [key: string]: any }> {
82-
const priorityOrder = getResourceTypePriorityByCTA(buttonText);
83-
84-
return resourceTypeTags.sort((firstTag, secondTag) => {
85-
const firstTagIndex = priorityOrder.indexOf(firstTag.tag);
86-
const secondTagIndex = priorityOrder.indexOf(secondTag.tag);
87-
88-
// If both tags are in priority list, sort by priority
89-
if (firstTagIndex !== -1 && secondTagIndex !== -1) {
90-
return firstTagIndex - secondTagIndex;
91-
}
92-
93-
// If only one tag is in priority list, prioritize it
94-
if (firstTagIndex !== -1) return -1;
95-
if (secondTagIndex !== -1) return 1;
96-
97-
// If neither tag is in priority list, maintain original order
98-
return 0;
99-
});
100-
}
45+
export { RESOURCE_TYPE_PRIORITY };

static/templates.json

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
[
2-
{
2+
{
33
"title": "Build AI Apps with Azure Database for PostgreSQL",
44
"description": "This learning path teaches you how to build Generative AI applications and agents with Azure Database for PostgreSQL, including vector search with pgvector, invoking LLMs using the azure_ai extension and semantic operators, and creating end-to-end RAG solutions. It also covers building intelligent AI agents that integrate seamlessly with your PostgreSQL data.",
55
"website": "https://learn.microsoft.com/training/paths/build-ai-apps-azure-database-postgresql/",
@@ -1696,6 +1696,7 @@
16961696
"date": "2025-10-03",
16971697
"tileNumber": 1,
16981698
"learningPathTitle": "Get Started for Free with an Azure Free Account",
1699-
"learningPathDescription": "Create and use an Azure Database for PostgreSQL flexible server instance for free using an Azure free account!"
1699+
"learningPathDescription": "Create and use an Azure Database for PostgreSQL flexible server instance for free using an Azure free account!",
1700+
"priority": "P0"
17001701
}
17011702
]

0 commit comments

Comments
 (0)