Skip to content

(Partners) Introduce partners section #27190

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 52 commits into from
May 12, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
52 commits
Select commit Hold shift + click to select a range
5304530
(Partners) Introduce partners section
hestonhoffman Jan 17, 2025
4dd6d3c
Edits
hestonhoffman Jan 17, 2025
c959a3c
Fix
hestonhoffman Jan 21, 2025
d024cb0
Menu updates
hestonhoffman Jan 21, 2025
f4f3a9b
oops
hestonhoffman Jan 21, 2025
b1fd2a9
load full Partner page
StefonSimmons Jan 27, 2025
988613d
adjust title style
StefonSimmons Jan 27, 2025
48d0240
open sidenav-partners menu sublists
StefonSimmons Jan 27, 2025
095ff35
update sources mod
StefonSimmons Jan 27, 2025
d7a364d
Merge branch 'master' into heston/web-5551
StefonSimmons Jan 27, 2025
6c50a14
fix id on menu item
StefonSimmons Jan 28, 2025
b22c799
fix side nav for non main menu items
StefonSimmons Jan 28, 2025
e6b7740
include branch name
StefonSimmons Jan 29, 2025
3e3a870
rm unused classes
StefonSimmons Jan 29, 2025
b5cc997
Merge branch 'master' into heston/WEB-5551
hestonhoffman Jan 31, 2025
57483cd
Add partners to home page
hestonhoffman Jan 31, 2025
4c0004f
initial search config
bgdeutsch Feb 19, 2025
e26ed74
merging master
bgdeutsch Feb 20, 2025
f29137c
should be public not dist
bgdeutsch Feb 20, 2025
b140b9a
use cascade so we dont have to repeat type partners
bgdeutsch Feb 21, 2025
e51bbda
defines a new preset in the config file
bgdeutsch Feb 21, 2025
a986e41
partners search
StefonSimmons Feb 24, 2025
d3c5e48
Merge branch 'master' into heston/WEB-5551
StefonSimmons Feb 24, 2025
8ed2ca6
updates typesense config
bgdeutsch Feb 24, 2025
806f9b4
Merge branch 'master' into heston/WEB-5551
DavidWeid Mar 3, 2025
7591424
Load results from partners index (only on partners pages)
DavidWeid Mar 3, 2025
8bb5982
Style cleanup. Show docs hits on partners pages
DavidWeid Mar 4, 2025
3503448
Merge branch 'master' into heston/WEB-5551
DavidWeid Mar 4, 2025
fe25694
Fix up `handleNRender` to handle multi-collection hits
DavidWeid Mar 4, 2025
1182112
enhance Partner header on search
StefonSimmons Mar 11, 2025
bcfea38
Merge branch 'heston/WEB-5551' of github.com:DataDog/documentation in…
StefonSimmons Mar 11, 2025
1c9a9a1
fix typo
StefonSimmons Mar 11, 2025
e64379c
intercept user click for partners
StefonSimmons Mar 11, 2025
acaf1eb
Merge branch 'master' into heston/WEB-5551
StefonSimmons Mar 11, 2025
ffc4fc4
add Partners to main docs results
StefonSimmons Apr 1, 2025
17e2a3e
add partners results to search results page
StefonSimmons Apr 1, 2025
7cbc8fd
change title name
StefonSimmons Apr 1, 2025
4643819
add subcategory
StefonSimmons Apr 8, 2025
553d926
change partners collection name
StefonSimmons Apr 8, 2025
cb72ada
change index to _v1
StefonSimmons Apr 11, 2025
ab2bf5c
change back to alias and add subcategory to getting_started path
StefonSimmons Apr 11, 2025
6145c66
updates partners search index
bgdeutsch Apr 14, 2025
5f67ff2
cmnt
StefonSimmons Apr 14, 2025
25e5cc2
Merge branch 'master' into heston/WEB-5551
StefonSimmons Apr 14, 2025
915a845
allow sub category for Getting Started section
StefonSimmons Apr 16, 2025
d724383
fix typo
StefonSimmons Apr 16, 2025
76f41d9
adjust search bar styles for new Partners section
StefonSimmons Apr 16, 2025
decad83
import partner page styles
StefonSimmons Apr 16, 2025
bdd2b9d
add new partners banner to list, single and section Partner templates
StefonSimmons Apr 16, 2025
c586b47
Quick edit
hestonhoffman May 2, 2025
87948c5
Merge branch 'master' into heston/WEB-5551
hestonhoffman May 2, 2025
d18c3c0
Merge branch 'master' into heston/WEB-5551
StefonSimmons May 12, 2025
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
3 changes: 3 additions & 0 deletions assets/scripts/components/async-loading.js
Original file line number Diff line number Diff line change
Expand Up @@ -85,10 +85,13 @@ function loadPage(newUrl) {
hitsContainer.classList.add("hits-container", "d-none");
const hits = document.createElement("div");
hits.setAttribute("id", "hits");
const hitsPartners = document.createElement("div");
hitsPartners.setAttribute("id", "hits-partners");

searchBoxContainer.append(searchBox);
searchBoxContainer.append(hitsContainer);
hitsContainer.append(hits);
hitsContainer.append(hitsPartners);

if(sidenavSearchbarMount) {
sidenavSearchbarMount.append(searchBoxContainer);
Expand Down
68 changes: 52 additions & 16 deletions assets/scripts/components/instantsearch.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { getConfig } from '../helpers/getConfig';
import instantsearch from 'instantsearch.js';
import TypesenseInstantSearchAdapter from 'typesense-instantsearch-adapter';
import { configure, searchBox } from 'instantsearch.js/es/widgets';
import { configure, searchBox, index } from 'instantsearch.js/es/widgets';
import { searchbarHits } from './instantsearch/searchbarHits';
import { searchpageHits } from './instantsearch/searchpageHits';
import { customPagination } from './instantsearch/customPagination';
Expand All @@ -10,6 +10,8 @@ import { debounce } from '../utils/debounce';
const { env } = document.documentElement.dataset;
const pageLanguage = getPageLanguage();
const typesenseConfig = getConfig(env).typesense;
const docsIndex = typesenseConfig.docsIndex;
const partnersIndex = typesenseConfig.partnersIndex;

const adapterOptions = {
server: {
Expand Down Expand Up @@ -38,14 +40,21 @@ const adapterOptions = {
],
cacheSearchResultsForSeconds: 2 * 60
},
additionalSearchParameters: {
preset: 'docs_alias_view'
collectionSpecificSearchParameters: {
[docsIndex]: {
preset: 'docs_alias_view',
collection: docsIndex
},
[partnersIndex]: {
preset: 'docs_partners_view',
collection: partnersIndex
}
}
};

const typesenseInstantSearchAdapter = new TypesenseInstantSearchAdapter(adapterOptions);

const searchClient = typesenseInstantSearchAdapter.searchClient;
let indexName = typesenseConfig.index;

function getPageLanguage() {
const pageLanguage = document.documentElement.lang;
Expand Down Expand Up @@ -97,11 +106,13 @@ function loadInstantSearch(currentPageWasAsyncLoaded) {
const searchBoxContainer = document.querySelector('#searchbox');
const hitsContainerContainer = document.querySelector('.hits-container');
const hitsContainer = document.querySelector('#hits');
const partnersHitsContainer = document.querySelector('#hits-partners');
const paginationContainer = document.querySelector('#pagination');
const pageTitleScrollTo = document.querySelector('#pagetitle');
const filtersDocs = `language: ${pageLanguage}`;
const homepage = document.querySelector('.kind-home');
const apiPage = document.querySelector('body.api');
const partnersPage = document.querySelector('body.partners');
let searchResultsPage = document.querySelector('.search_results_page');
let basePathName = '/';
let numHits = 5;
Expand All @@ -124,8 +135,15 @@ function loadInstantSearch(currentPageWasAsyncLoaded) {
typesenseInstantSearchAdapter.updateConfiguration({
...adapterOptions,
...{
additionalSearchParameters: {
preset: 'docs_alias_api_view'
collectionSpecificSearchParameters: {
[docsIndex]: {
preset: 'docs_alias_api_view',
collection: docsIndex
},
[partnersIndex]: {
preset: 'docs_partners_view',
collection: partnersIndex
}
}
}
});
Expand All @@ -139,20 +157,20 @@ function loadInstantSearch(currentPageWasAsyncLoaded) {
// No searchBoxContainer means no instantSearch
if (searchBoxContainer) {
const search = instantsearch({
indexName,
indexName: docsIndex,
searchClient,
// routing handles search state URL sync
routing: {
stateMapping: {
stateToRoute(uiState) {
const indexUiState = uiState[indexName];
const indexUiState = uiState[docsIndex];
return {
s: indexUiState.query
};
},
routeToState(routeState) {
return {
[indexName]: {
[docsIndex]: {
query: routeState.s
}
};
Expand Down Expand Up @@ -197,14 +215,14 @@ function loadInstantSearch(currentPageWasAsyncLoaded) {
showSubmit: true,
templates: {
submit({ cssClasses }, { html }) {
return html`<span id="submit-text" class="${cssClasses.submit}">search</span>

<i id="submit-icon" class="${cssClasses.submit} icon-search"></i>`;
return html`<span id="submit-text" class="${cssClasses.submit}">search</span
><i id="submit-icon" class="${cssClasses.submit} icon-search"></i>`;
}
}
}),

hitComponent({
partnersPage: partnersPage,
container: hitsContainer,
basePathName: basePathName
}),
Expand All @@ -214,7 +232,23 @@ function loadInstantSearch(currentPageWasAsyncLoaded) {
container: paginationContainer,
scrollTo: pageTitleScrollTo,
padding: 5
})
}),

// Add second index: Partners
index({
indexName: partnersIndex
}).addWidgets([
configure({
hitsPerPage: numHits,
distinct: 1
}),

hitComponent({
partnersPage: partnersPage,
container: partnersHitsContainer,
basePathName: basePathName
})
])
]);

// Start up frontend search
Expand Down Expand Up @@ -244,10 +278,12 @@ function loadInstantSearch(currentPageWasAsyncLoaded) {
window.location.pathname = searchPathname;
}
};

const handleOutsideSearchbarClick = (e) => {
// Intercept user clicks within instantSearch dropdown to send custom RUM event before redirect.
if (hitsContainer.contains(e.target)) {
const targetIsDescendant = [hitsContainer, partnersHitsContainer].some((c) => c.contains(e.target));

if (targetIsDescendant) {
e.preventDefault();
}

Expand All @@ -256,7 +292,7 @@ function loadInstantSearch(currentPageWasAsyncLoaded) {
do {
if (target === searchBoxContainerContainer) return;

if (target && target.href && hitsContainer.contains(e.target)) {
if (target && target.href && targetIsDescendant) {
sendSearchRumAction(search.helper.state.query, target.href);
window.history.pushState({}, '', target.href);
window.location.reload();
Expand Down
38 changes: 19 additions & 19 deletions assets/scripts/components/instantsearch/getHitData.js
Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@
import { replaceSpecialCharacters } from "../../helpers/string";
import { truncateContent, truncateContentAtHighlight } from "../../helpers/truncateContent";
import { replaceSpecialCharacters } from '../../helpers/string';
import { truncateContent, truncateContentAtHighlight } from '../../helpers/truncateContent';

export function getHitData(hit, searchQuery = '') {
const title = hit.title ? hit.title : hit.type;
const cleanRelPermalink =
hit.language == 'en' ? hit.relpermalink : hit.relpermalink.replace(`/${hit.language}/`, '');

const matchingWordsArray = getFilteredMatchingWords(searchQuery).map(word => replaceSpecialCharacters(word))
const matchingWordsArray = getFilteredMatchingWords(searchQuery).map((word) => replaceSpecialCharacters(word));
const joinedMatchingWordsFromSearch = matchingWordsArray.join('|');
const regexQry = new RegExp(`(${joinedMatchingWordsFromSearch})`, 'gi');
const highlightTitle = (hit._highlightResult.title.value || title);
const highlightContent = (hit._highlightResult.content.value || '');
const highlightSectionHeader = (hit._highlightResult.section_header.value || '');
const highlightTitle = hit._highlightResult.title.value || title;
const highlightContent = hit._highlightResult.content.value || '';
const highlightSectionHeader = hit._highlightResult.section_header?.value || '';

return {
relpermalink: cleanRelPermalink,
Expand All @@ -28,22 +28,22 @@ export function getHitData(hit, searchQuery = '') {
filtering out short/common terms that may cause inaccurate highlighting
*/
const getFilteredMatchingWords = (searchQuery) => {
const stopWords = ['the', 'and', 'for']
return searchQuery.split(' ').filter(word => word.length > 2 && !stopWords.includes(word))
}
const stopWords = ['the', 'and', 'for'];
return searchQuery.split(' ').filter((word) => word.length > 2 && !stopWords.includes(word));
};

/*
Manually add <mark> element when applicable as relevant content isn't always highlighted in title/content.
*/
const handleHighlightingSearchResultContent = (string, regex) => {
if (string.includes('<mark>')) return string
if (string.includes('<mark>')) return string;

if (string.search(regex) > 0) {
return string.replace(regex, '<mark>$1</mark>')
return string.replace(regex, '<mark>$1</mark>');
}

return string
}
return string;
};

/*
Produces a snippet to be displayed in the search results.
Expand All @@ -52,16 +52,16 @@ const handleHighlightingSearchResultContent = (string, regex) => {
or a similar section which should have relevant information for the end user.
*/
export const getSnippetForDisplay = (hit, isSearchPage) => {
const characterLimit = isSearchPage ? 300 : 180
let snippet = truncateContentAtHighlight(hit.highlighted_content, characterLimit)
const characterLimit = isSearchPage ? 300 : 180;
let snippet = truncateContentAtHighlight(hit.highlighted_content, characterLimit);

if (!hit.section_header) {
snippet = truncateContent(hit.highlighted_content, characterLimit)
snippet = truncateContent(hit.highlighted_content, characterLimit);

if (!snippet.includes('<mark>')) {
snippet = truncateContentAtHighlight(hit.highlighted_content, characterLimit)
snippet = truncateContentAtHighlight(hit.highlighted_content, characterLimit);
}
}

return snippet
}
return snippet;
};
52 changes: 31 additions & 21 deletions assets/scripts/components/instantsearch/searchbarHits.js
Original file line number Diff line number Diff line change
Expand Up @@ -56,15 +56,18 @@ const renderHits = (renderOptions, isFirstRender) => {

const handleNRender = (containerDiv, allJoinedListItemsArray, numHits) => {
// On non-first renders, add organized hits to applicable divs
const addHitsToEmptyElements = () => {
const addHitsToEmptyElements = (container) => {
allJoinedListItemsArray.forEach((joinedList, index) => {
containerDiv.querySelector(`#ais-Hits-category-${index} .ais-Hits-list`).innerHTML = joinedList;
if (joinedList) {
const target = container.querySelector(`#ais-Hits-category-${index} .ais-Hits-list`);
if (target) target.innerHTML = joinedList;
}
});
};

const hideOrShowElements = () => {
const finalHitsLists = document.querySelectorAll('.ais-Hits-list');

const hideOrShowElements = (container) => {
const finalHitsLists = container.querySelectorAll('.ais-Hits-list');
finalHitsLists.forEach((list) => {
if (list.childElementCount) {
list.classList.remove('no-hits');
Expand All @@ -73,13 +76,11 @@ const renderHits = (renderOptions, isFirstRender) => {
}
});

numHits === 0
? document.querySelector('#hits').classList.add('no-hits')
: document.querySelector('#hits').classList.remove('no-hits');
numHits === 0 ? container.classList.add('no-hits') : container.classList.remove('no-hits');
};

addHitsToEmptyElements();
hideOrShowElements();
addHitsToEmptyElements(containerDiv);
hideOrShowElements(containerDiv);
};

// Returns a bunch of <li>s
Expand All @@ -89,7 +90,10 @@ const renderHits = (renderOptions, isFirstRender) => {
const hit = getHitData(item, renderOptions.results.query);
const displayContent = getSnippetForDisplay(hit, false);
const cleanRelpermalink = `${basePathName}${hit.relpermalink}`.replace('//', '/');
const sectHeader = (hit.section_header !== "") ? `<span class="breadcrumb2"> &raquo; <strong>${hit.section_header}</strong></span>` : "";
const sectHeader =
hit.section_header !== ''
? `<span class="breadcrumb2"> &raquo; <strong>${hit.section_header}</strong></span>`
: '';

return `
<li class="ais-Hits-item">
Expand All @@ -107,10 +111,10 @@ const renderHits = (renderOptions, isFirstRender) => {
`;
})
.join('');

const enhanceApiCategoryHeader = category.toLowerCase() === 'api' && bodyClassContains('api');
const enhanceCategoryHeader = ['api', 'partners'].includes(category.toLowerCase()) && bodyClassContains(category.toLowerCase());
const categoryLiClassList = 'ais-Hits-item ais-Hits-category';
const categoryParagraphClassList = enhanceApiCategoryHeader ? 'fw-bold text-primary' : '';
const categoryParagraphClassList = enhanceCategoryHeader ? 'fw-bold text-primary' : '';

return hitsArray.length
? [
Expand All @@ -123,19 +127,25 @@ const renderHits = (renderOptions, isFirstRender) => {
const { widgetParams, hits } = renderOptions;
const { container, basePathName } = widgetParams;

const gettingStartedHitsArray = hits.filter((hit) => hit.category.toLowerCase() === 'getting started');
const docsHitsArray = hits.filter((hit) => hit.category.toLowerCase() === 'documentation' || hit.category === null);
const integrationsHitsArray = hits.filter((hit) => hit.category.toLowerCase() === 'integrations');
const guidesHitsArray = hits.filter((hit) => hit.category.toLowerCase() === 'guide');
const apiHitsArray = hits.filter((hit) => hit.category.toLowerCase() === 'api');
const partnersHitsArray = hits.filter((hit) => hit.type === 'partners');

const gettingStartedHitsArray = hits.filter((hit) => hit.category?.toLowerCase() === 'getting started');
const docsHitsArray = hits.filter(
(hit) => hit.category?.toLowerCase() === 'documentation' || hit.category === null
);
const integrationsHitsArray = hits.filter((hit) => hit.category?.toLowerCase() === 'integrations');
const guidesHitsArray = hits.filter((hit) => hit.category?.toLowerCase() === 'guide');
const apiHitsArray = hits.filter((hit) => hit.category?.toLowerCase() === 'api');

// Remove null from array
const allJoinedListItemsHTML = [
generateJoinedHits(gettingStartedHitsArray, 'Getting Started'),
generateJoinedHits(docsHitsArray, 'Documentation'),
generateJoinedHits(integrationsHitsArray, 'Integrations'),
generateJoinedHits(guidesHitsArray, 'Guides'),
generateJoinedHits(partnersHitsArray, 'Partners'),
generateJoinedHits(integrationsHitsArray, 'Integrations'),
generateJoinedHits(apiHitsArray, 'API')
];
].filter((item) => item !== null);

// Ensure API results render first if user performs a search from an API page.
if (bodyClassContains('api')) {
Expand Down
5 changes: 3 additions & 2 deletions assets/scripts/components/instantsearch/searchpageHits.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,13 @@ const renderHits = (renderOptions, isFirstRender) => {
};

const appendEmptyElements = () => {
container.appendChild(aisHits);
container?.appendChild(aisHits);
aisHits.appendChild(aisHitsList);
};

const addHitsToEmptyElements = () => {
container.querySelector('.ais-Hits-list').innerHTML = joinedListItemsHTML;
const target = container?.querySelector('.ais-Hits-list');
if (target) target.innerHTML = joinedListItemsHTML;
};

const getTitleHierarchyHTML = (hit) => {
Expand Down
Loading
Loading