When you create a new marketing campaign using the creation component, you will notice the Marketing Assets field. This field allows you to select various assets for your marketing campaign.
In this part of the series, I will show how to create an external component that displays the associated assets of marketing campaigns. Additionally, I have included a preview URL for viewing your assets.
However, in this case, I opted to directly fetch the assets using the Raw client. To know more, you can refer to the Sitecore documentation. I am taking the assets in batches and binding the data to the marketing campaigns and their associated assets.
import React, { useEffect, useState } from "react";
import { ContentHubClient } from "@sitecore/sc-contenthub-webclient-sdk";
interface Props {
client: ContentHubClient;
}
interface Campaign {
id: number;
properties: {
CampaignName: string;
StartDate: string;
EndDate: string;
Location: string;
CampaignStatusActive: boolean;
};
assets: AssetInfo[];
}
interface AssetInfo {
id: number;
fileName: string;
previewUrl: string;
}
const ListMarketingCampaigns: React.FC<Props> = ({ client }) => {
const [campaigns, setCampaigns] = useState<Campaign[]>([]);
const [error, setError] = useState<string | null>(null);
const [assetsMap, setAssetsMap] = useState<{ [key: number]:
AssetInfo[]}>({});
const [loading, setLoading] = useState<boolean>(true);
const renditionBaseUrl = `https://${location.hostname}/api/gateway`;
const take = 100;
// Function to extract the campaign ID from a URL
const extractIdFromUrl = (url: string): number => {
const parts = url.split('/');
return parseInt(parts[parts.length - 1], 10);
};
// Function to fetch campaign ID from MarketingAssets relation
const fetchCampaignIdFromAssetRelation = async (href: string):
Promise<number[] | null> => {
try {
const relationResponse = await client.raw.getAsync<any>(href);
const relatedCampaigns = relationResponse.content?.parents || [];
const campaignIds = relatedCampaigns.map((parent: any) =>
extractIdFromUrl(parent.href));
if (campaignIds.length > 0) {
return campaignIds;
} else {
console.log("No related campaigns found in MarketingAssets relation
response.");
return [];
}
} catch (error) {
console.error("Error fetching MarketingAssets relation:", error);
return [];
}
};
useEffect(() => {
const fetchCampaignsAndAssets = async () => {
try {
// Fetch Marketing Campaigns
const campaignsResponse = await client.raw.getAsync<any>(
`/api/entitydefinitions/MarketingCampaign/entities?skip=0&take=100
&sort=CreatedOn&order=desc&culture=en-US`
);
const campaignsData: Campaign[] = campaignsResponse.content?.items;
if (!Array.isArray(campaignsData)) {
setError("Unexpected response format for campaigns.");
console.error("Campaigns Data Error:", campaignsData);
return;
}
setCampaigns(campaignsData);
const campaignIds = campaignsData.map((campaign) => campaign.id);
const campaignAssetsMap: { [key: number]: AssetInfo[] } = {};
// Fetch assets in batches
let allAssetsFetched = false;
let skip = 0;
while (!allAssetsFetched) {
console.log(`Fetching assets with skip=${skip} and take=${take}...`);
const assetsResponse = await client.raw.getAsync<any>(
`/api/entitydefinitions/M.Asset/entities?skip=${skip}&take=${take}
&sort=CreatedOn&order=desc&culture=en-US`
);
if (!assetsResponse || !assetsResponse.content?.items ||
assetsResponse.content.items.length === 0) {
allAssetsFetched = true; // No more assets to fetch
console.log("No more assets to fetch.");
break;
}
// Process assets and check their MarketingAssets relation
for (const assetEntity of assetsResponse.content.items) {
const marketingAssetsHref = assetEntity.relations?.MarketingAssets?.href;
if (marketingAssetsHref) {
const relatedCampaignIds = await fetchCampaignIdFromAssetRelation
(marketingAssetsHref);
relatedCampaignIds!.forEach(campaignId => {
if (campaignIds.includes(campaignId)) {
const assetInfo: AssetInfo = {
id: assetEntity.id,
fileName: assetEntity.properties.FileName || "Unnamed Asset",
previewUrl: `${renditionBaseUrl}/${assetEntity.id}/preview`,
};
if (!campaignAssetsMap[campaignId]) {
campaignAssetsMap[campaignId] = [];
}
campaignAssetsMap[campaignId].push(assetInfo);
}
});
} else {
console.log("No MarketingAssets relation found for asset:",
assetEntity.id);
}
}
skip += take;
}
setAssetsMap(prevMap => ({ ...prevMap, ...campaignAssetsMap }));
setLoading(false);
} catch (err) {
setError(`Failed to fetch campaigns or assets. Error: ${err}`);
console.error("Error fetching campaigns or assets:", err);
setLoading(false);
}
};
fetchCampaignsAndAssets();
}, [client]);
return (
<div style={{ margin: '20px' }}>
<h1>Marketing Campaigns</h1>
{error && <p>{error}</p>}
{loading ? (
<p>Loading assets...</p>
) : (
campaigns.length > 0 ? (
<div
style={{
overflowX: 'auto',
boxShadow: '0 4px 8px rgba(0,0,0,0.1)',
borderRadius: '10px',
}}
>
<table style={{ width: '100%', borderCollapse: 'collapse',
borderRadius: '10px', overflow: 'hidden' }}>
<thead>
<tr style={{ backgroundColor: '#f8f8f8', color: '#333',
textAlign: 'left' }}>
<th style={{ padding: '12px 15px', fontWeight: '600',
borderBottom: '1px solid #ddd' }}>Name</th>
<th style={{ padding: '12px 15px', fontWeight: '600',
borderBottom: '1px solid #ddd' }}>Start Date</th>
<th style={{ padding: '12px 15px', fontWeight: '600',
borderBottom: '1px solid #ddd' }}>End Date</th>
<th style={{ padding: '12px 15px', fontWeight: '600',
borderBottom: '1px solid #ddd' }}>Location</th>
<th style={{ padding: '12px 15px', fontWeight: '600',
borderBottom: '1px solid #ddd' }}>Status</th>
<th style={{ padding: '12px 15px', fontWeight: '600',
borderBottom: '1px solid #ddd' }}>Marketing Assets</th>
</tr>
</thead>
<tbody>
{campaigns.map((campaign, index) => (
<tr key={campaign.id} style={{ backgroundColor:
index % 2 === 0 ? '#f9f9f9' : '#fff',
transition: 'background-color 0.3s ease' }}>
<td style={{ padding: '12px 15px',
borderBottom: '1px solid #ddd'
}}>{campaign.properties.CampaignName}</td>
<td style={{ padding: '12px 15px',
borderBottom: '1px solid #ddd'}}>
{new Date(campaign.properties.StartDate).
toLocaleDateString()}
</td>
<td style={{ padding: '12px 15px',
borderBottom: '1px solid #ddd' }}>
{new Date(campaign.properties.EndDate).
toLocaleDateString()}</td>
<td style={{ padding: '12px 15px',
borderBottom: '1px solid #ddd' }}>
{campaign.properties.Location}</td>
<td style={{ padding: '12px 15px',
borderBottom: '1px solid #ddd' }}>
{campaign.properties.CampaignStatusActive
? "Active" : "Inactive"}</td>
<td style={{ padding: '12px 15px',
borderBottom: '1px solid #ddd' }}>
{assetsMap[campaign.id] &&
assetsMap[campaign.id].length > 0 ? (
<ul>
{assetsMap[campaign.id].map((asset) => (
<li key={asset.id}>
<a href={asset.previewUrl} target="_blank"
rel="noopener noreferrer">{asset.fileName}</a>
</li>
))}
</ul>
) : (
<span>No assets</span>
)}
</td>
</tr>
))}
</tbody>
</table>
</div>
) : (
<p>No campaigns found.</p>
)
)}
</div>
);
};
export default ListMarketingCampaigns;
In this code, we’re making a raw API call to fetch the assets associated with a specific marketing campaign. The returned data can then be utilized within our component to render the assets along with their details.
The external component will display all entities created from the marketing campaign definition, including their associated assets. In the Marketing Assets column, you will see the filenames of the assets, which are clickable links that will open the asset previews. This setup provides a clear and convenient way for users to access and view the assets linked to their marketing campaigns.