Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions website/modules/case-studies-page/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ module.exports = {
{ name: 'industry' },
{ name: 'stack' },
{ name: 'caseStudyType' },
{ name: 'partner' },
],
pieces: 'case-studies',
piecesFiltersUrl: '/case-studies',
Expand Down Expand Up @@ -59,6 +60,7 @@ module.exports = {
industry: {},
stack: {},
caseStudyType: {},
partner: {},
});
}
},
Expand Down
141 changes: 99 additions & 42 deletions website/modules/case-studies-page/services/NavigationService.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,24 +9,81 @@
*/
class NavigationService {
/**
* Converts tag slugs to IDs
* Converts slugs to document IDs for a given piece module
* @param {Object} apos - ApostropheCMS instance
* @param {Object} req - Request object
* @param {Array} slugs - Array of tag slugs
* @returns {Promise<Array>} Promise resolving to array of tag IDs
* @param {Array} slugs - Array of slugs
* @param {string} moduleKey - Key of the piece module in apos.modules (e.g. 'cases-tags', 'business-partner')
* @returns {Promise<Array>} Promise resolving to array of document IDs
*/
static async convertSlugsToIds(apos, req, slugs) {
const tagPromises = slugs.map(async (slug) => {
const results = await apos.modules['cases-tags']
static async convertSlugsToIdsForModule(apos, req, slugs, moduleKey) {
const docPromises = slugs.map(async (slug) => {
const results = await apos.modules[moduleKey]
.find(req, { slug })
Comment thread
IhorMasechko marked this conversation as resolved.
.toArray();
if (results.length > 0) {
return results[0];
}
return null;
});
const tags = await Promise.all(tagPromises);
return tags.filter((tag) => tag).map((tag) => tag.aposDocId);
const docs = await Promise.all(docPromises);
return docs.filter((doc) => doc).map((doc) => doc.aposDocId);
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.

/**
* Converts tag slugs to IDs (cases-tags module)
* @param {Object} apos - ApostropheCMS instance
* @param {Object} req - Request object
* @param {Array} slugs - Array of tag slugs
Comment thread
IhorMasechko marked this conversation as resolved.
* @returns {Promise<Array>} Promise resolving to array of tag IDs
*/
static convertSlugsToIds(apos, req, slugs) {
return NavigationService.convertSlugsToIdsForModule(
apos,
req,
slugs,
'cases-tags',
);
}

/**
* Converts business partner slugs to IDs
* @param {Object} apos - ApostropheCMS instance
* @param {Object} req - Request object
* @param {Array} slugs - Array of partner slugs
* @returns {Promise<Array>} Promise resolving to array of partner IDs
*/
static convertPartnerSlugsToIds(apos, req, slugs) {
return NavigationService.convertSlugsToIdsForModule(
apos,
req,
slugs,
'business-partner',
);
}

/**
* Applies a single slug-based filter to the query
* @param {Object} filteredQuery - Current query object
* @param {Object} req - Request object
* @param {Object} apos - ApostropheCMS instance
* @param {Object} options - Options object
* @param {string} options.paramName - Query parameter name
* @param {Function} options.convertFn - Async function to convert slugs to IDs
* @param {string} options.fieldName - Document field name for the filter
* @returns {Promise<Object>} Promise resolving to modified query object
*/
static async applySlugFilter(filteredQuery, req, apos, options) {
const { paramName, convertFn, fieldName } = options;
const values = req.query[paramName];
if (!values || values.length === 0) {
return filteredQuery;
}
const ids = await convertFn(apos, req, values);
if (ids.length === 0) {
return filteredQuery;
}
return filteredQuery.and({ [fieldName]: { $in: ids } });
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Outdated

/**
Expand All @@ -37,45 +94,45 @@ class NavigationService {
* @returns {Promise<Object>} Promise resolving to modified query object
*/
static async applyFiltersToQuery(query, req, apos) {
const filterConfigs = [
{
param: 'industry',
convert: NavigationService.convertSlugsToIds,
field: 'industryIds',
},
{
param: 'stack',
convert: NavigationService.convertSlugsToIds,
field: 'stackIds',
},
{
param: 'caseStudyType',
convert: NavigationService.convertSlugsToIds,
field: 'caseStudyTypeIds',
},
{
param: 'partner',
convert: NavigationService.convertPartnerSlugsToIds,
field: 'partnerIds',
},
];
const idArrays = await Promise.all(
filterConfigs.map((config) => {
if (req.query[config.param]?.length) {
return config.convert(apos, req, req.query[config.param]);
}
return Promise.resolve([]);
}),
);
let filteredQuery = query;

if (req.query.industry && req.query.industry.length > 0) {
const industryIds = await NavigationService.convertSlugsToIds(
apos,
req,
req.query.industry,
);
if (industryIds.length > 0) {
filteredQuery = filteredQuery.and({
industryIds: { $in: industryIds },
});
}
}

if (req.query.stack && req.query.stack.length > 0) {
const stackIds = await NavigationService.convertSlugsToIds(
apos,
req,
req.query.stack,
);
if (stackIds.length > 0) {
filteredQuery = filteredQuery.and({ stackIds: { $in: stackIds } });
}
}

if (req.query.caseStudyType && req.query.caseStudyType.length > 0) {
const caseStudyTypeIds = await NavigationService.convertSlugsToIds(
apos,
req,
req.query.caseStudyType,
);
if (caseStudyTypeIds.length > 0) {
for (let index = 0; index < filterConfigs.length; index += 1) {
const ids = idArrays[index];
if (ids.length > 0) {
filteredQuery = filteredQuery.and({
caseStudyTypeIds: { $in: caseStudyTypeIds },
[filterConfigs[index].field]: { $in: ids },
});
}
}

return filteredQuery;
}

Expand Down
49 changes: 33 additions & 16 deletions website/modules/case-studies-page/services/TagCountService.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,16 +9,16 @@
*/
class TagCountService {
/**
* Creates a mapping from tag IDs to tag slugs
* @param {Array} casesTags - Array of tag objects
* @returns {Object} Map of tag ID to slug
* Creates a mapping from document ID to slug for any array of docs with aposDocId and slug
* @param {Array} docs - Array of document objects with aposDocId and slug
* @returns {Object} Map of document ID to slug
*/
static createTagMap(casesTags) {
const tagMap = {};
casesTags.forEach((tag) => {
tagMap[tag.aposDocId] = tag.slug;
static createIdToSlugMap(docs) {
const map = {};
docs.forEach((doc) => {
map[doc.aposDocId] = doc.slug;
});
return tagMap;
return map;
}

/**
Expand Down Expand Up @@ -47,21 +47,25 @@ class TagCountService {
* @param {Object} req - ApostropheCMS request object
* @param {Object} aposModules - ApostropheCMS modules
* @param {Object} options - Module options
* @returns {Promise<Array>} Promise resolving to [caseStudies, casesTags] arrays
* @returns {Promise<Array>} Promise resolving to [caseStudies, casesTags, businessPartners] arrays
*/
static async fetchCaseStudiesAndTags(req, aposModules, options) {
const caseStudies = await aposModules[options.pieces].find(req).toArray();
const casesTags = await aposModules['cases-tags'].find(req).toArray();
return [caseStudies, casesTags];
const [caseStudies, casesTags, businessPartners] = await Promise.all([
aposModules[options.pieces].find(req).toArray(),
aposModules['cases-tags'].find(req).toArray(),
aposModules['business-partner'].find(req).toArray(),
]);
return [caseStudies, casesTags, businessPartners];
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.

/**
* Processes case studies to count tags by type
* @param {Array} caseStudies - Array of case study objects
* @param {Object} tagMap - Map of tag ID to slug
* @param {Object} partnerMap - Map of partner ID to slug
* @param {Object} tagCounts - Object to store all tag counts
*/
static processCaseStudies(caseStudies, tagMap, tagCounts) {
static processCaseStudies(caseStudies, tagMap, partnerMap, tagCounts) {
caseStudies.forEach((study) => {
TagCountService.countTagsOfType(
study.industryIds,
Expand All @@ -76,6 +80,12 @@ class TagCountService {
tagMap,
tagCounts.caseStudyType,
);

TagCountService.countTagsOfType(
study.partnerIds,
partnerMap,
tagCounts.partner,
);
});
}

Expand All @@ -91,14 +101,21 @@ class TagCountService {
industry: {},
stack: {},
caseStudyType: {},
partner: {},
};

const [caseStudies, casesTags] =
const [caseStudies, casesTags, businessPartners] =
await TagCountService.fetchCaseStudiesAndTags(req, aposModules, options);

const tagMap = TagCountService.createTagMap(casesTags);
const tagMap = TagCountService.createIdToSlugMap(casesTags);
const partnerMap = TagCountService.createIdToSlugMap(businessPartners);

TagCountService.processCaseStudies(caseStudies, tagMap, tagCounts);
TagCountService.processCaseStudies(
caseStudies,
tagMap,
partnerMap,
tagCounts,
);

return tagCounts;
}
Expand Down
8 changes: 8 additions & 0 deletions website/modules/case-studies-page/services/UrlService.js
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,14 @@ class UrlService {
});
}

// Add partner parameters
if (query.partner) {
const partners = UrlService.ensureArray(query.partner);
partners.forEach((partner, index) => {
params.append(`partner[${index}]`, partner);
});
}

// Add search parameter
if (query.search) {
params.append('search', query.search);
Expand Down
32 changes: 28 additions & 4 deletions website/modules/case-studies-page/views/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
<div class="cs_filter-info">
<div class="filters-cta">Filter Case Studies</div>
{% set hasActiveFilters = data.query.industry or data.query.stack or
data.query.caseStudyType %} {% if hasActiveFilters %}
data.query.caseStudyType or data.query.partner %} {% if hasActiveFilters %}
<p class="items-count">
{{ data.totalPieces }} Ite{% if data.totalPieces == 1 %}m{% else %}ms{%
endif %} Found
Expand All @@ -32,7 +32,7 @@
<div class="clear-all">
<a
class="clear-all-link"
href="{{ data.url | build({ industry: null, stack: null, caseStudyType: null }) }}#filter"
href="{{ data.url | build({ industry: null, stack: null, caseStudyType: null, partner: null }) }}#filter"
aria-label="Clear all filters"
>
<span class="clear-all-content">
Expand All @@ -46,7 +46,7 @@

<div class="selected-tags">
<ul class="selected-tags-list">
{% for filterType in ['industry', 'stack', 'caseStudyType'] %} {% if
{% for filterType in ['industry', 'stack', 'caseStudyType', 'partner'] %} {% if
data.query[filterType] %} {% for tag in data.piecesFilters[filterType]
%} {# Check if tag is selected (handle both array and string) #} {%
set isTagSelected = false %} {% if data.query[filterType].length is
Expand Down Expand Up @@ -79,6 +79,14 @@
>
<img src="/images/close.svg" alt="Close Icon" />
</a>
{% elif filterType == 'partner' %}
<a
class="remove-tag"
href="{{ data.url | build({}, { partner: { $pull: tag.value } }) }}#filter"
aria-label="Remove partner tag {{ tag.label }}"
>
<img src="/images/close.svg" alt="Close Icon" />
</a>
{% endif %}
</li>
{% endif %} {% endfor %} {% endif %} {% endfor %}
Expand All @@ -91,7 +99,7 @@
<div class="tags-filter">
{# Display all filter types #} {% for filterType, filterLabel in [
['industry', 'Industry'], ['caseStudyType', 'Case Type'], ['stack',
'Technology'] ] %} {% set tags = data.piecesFilters[filterType] %} {% if
'Technology'], ['partner', 'Partner'] ] %} {% set tags = data.piecesFilters[filterType] %} {% if
tags and tags.length %}
<div class="filter-section">
<!-- prettier-ignore -->
Expand Down Expand Up @@ -175,6 +183,14 @@
<span class="tag-label">{{ tag.label }}</span>
<span class="tag-count">[ {{ count }} ]</span>
</a>
{% elif filterType == 'partner' %}
<a
class="tag-link"
href="{{ data.url | build({}, { partner: { $pull: tag.value } }) }}#filter"
>
<span class="tag-label">{{ tag.label }}</span>
<span class="tag-count">[ {{ count }} ]</span>
</a>
{% endif %}
</li>
{% else %}
Expand Down Expand Up @@ -206,6 +222,14 @@
<span class="tag-label">{{ tag.label }}</span>
<span class="tag-count">[ {{ count }} ]</span>
</a>
{% elif filterType == 'partner' %}
<a
class="tag-link"
href="{{ data.url | build({}, { partner: { $addToSet: tag.value } }) }}#filter"
>
<span class="tag-label">{{ tag.label }}</span>
<span class="tag-count">[ {{ count }} ]</span>
</a>
{% endif %}
</li>
{% endif %} {% endfor %}
Expand Down
3 changes: 3 additions & 0 deletions website/modules/case-studies/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,9 @@ module.exports = {
_stack: {
label: 'Stack',
},
_partner: {
label: 'Partner',
},
},
},
helpers() {
Expand Down
Loading