This is a complete, working example showing all files and code needed to add a new "Datasets" category to the Nexus search.
File: /content/datasets/datasets.csv
dataset_name,dataset_url,dataset_description,dataset_type,dataset_source,dataset_keywords
SARS-CoV-2 Reference Genomes,https://www.ncbi.nlm.nih.gov/datasets/coronavirus/genomes/,Complete SARS-CoV-2 reference genome sequences from NCBI,Reference Genomes,NCBI,"COVID-19, SARS-CoV-2, reference, genomes, viral"
Bacterial Isolate Genome Database,https://www.ncbi.nlm.nih.gov/pathogens/isolates,Curated bacterial pathogen isolate genomes with AMR data,Pathogen Genomes,NCBI Pathogen Detection,"bacteria, pathogens, AMR, isolates, surveillance"
"Public Health England Surveillance Data",https://www.gov.uk/phe,Epidemiological surveillance datasets for public health,Surveillance Data,Public Health England,"epidemiology, surveillance, public health, outbreak"
WHO Disease Outbreak News,https://www.who.int/emergencies/disease-outbreak-news,Global disease outbreak reports and data,Outbreak Reports,World Health Organization,"outbreaks, surveillance, WHO, global health"Key Points:
- Header row with consistent naming:
dataset_* - URLs properly formatted
- Keywords as comma-separated strings
- Use quotes for fields containing commas
File: /app/pages/nexus.vue
Location: Around line 72-76
Before:
const searchCategories = [
{ label: 'Pipelines', value: 'pipelines' },
{ label: 'Trainings', value: 'trainings' },
{ label: 'Resources', value: 'resources' }
]After:
const searchCategories = [
{ label: 'Pipelines', value: 'pipelines' },
{ label: 'Trainings', value: 'trainings' },
{ label: 'Resources', value: 'resources' },
{ label: 'Datasets', value: 'datasets' } // ← ADD THIS LINE
]File: /app/pages/nexus.vue
Location: After PipelineRawData interface (around line 61-68)
Add:
interface DatasetRawData {
dataset_name: string
dataset_url: string
dataset_description: string
dataset_type: string
dataset_source: string
dataset_keywords: string | string[]
}Complete Interfaces Section Should Look Like:
// ============================================================================
// Types
// ============================================================================
interface SearchResultItem {
name: string
url: string
description: string
category: string
language?: string
ownership?: string
keywords?: string[]
}
interface PipelineRawData {
pipeline_name: string
pipeline_url: string
pipeline_description: string
pipeline_language: string
pipeline_ownership: string
pipeline_keywords: string | string[]
}
interface DatasetRawData {
dataset_name: string
dataset_url: string
dataset_description: string
dataset_type: string
dataset_source: string
dataset_keywords: string | string[]
}File: /app/pages/nexus.vue
Location: After pipelines data loading (around line 77-80)
Add:
// Load datasets data
const { data: datasetsData } = await useAsyncData('datasets', async () => {
return await queryCollection('datasets').all()
})Complete Data Loading Section:
// ============================================================================
// Data Loading
// ============================================================================
const { data: pipelinesData } = await useAsyncData('pipelines', async () => {
return await queryCollection('pipelines').all()
})
const { data: datasetsData } = await useAsyncData('datasets', async () => {
return await queryCollection('datasets').all()
})File: /app/pages/nexus.vue
Location: After transformPipelineData function (around line 97-104)
Add:
/**
* Transforms raw dataset data into SearchResultItem format
*/
const transformDatasetData = (rawData: DatasetRawData): SearchResultItem => ({
name: rawData.dataset_name || '',
url: rawData.dataset_url || '',
description: rawData.dataset_description || '',
category: 'Datasets',
language: rawData.dataset_type || '', // Maps 'type' to 'language' field
ownership: rawData.dataset_source || '', // Maps 'source' to 'ownership' field
keywords: parseKeywords(rawData.dataset_keywords)
})Complete Transformation Section:
// ============================================================================
// Data Transformation
// ============================================================================
/**
* Transforms pipeline keywords from string or array format to array of trimmed strings
*/
const parseKeywords = (keywords: string | string[]): string[] => {
if (typeof keywords === 'string') {
return keywords.split(',').map(k => k.trim())
}
return Array.isArray(keywords) ? keywords : []
}
/**
* Transforms raw pipeline data into SearchResultItem format
*/
const transformPipelineData = (rawData: PipelineRawData): SearchResultItem => ({
name: rawData.pipeline_name || '',
url: rawData.pipeline_url || '',
description: rawData.pipeline_description || '',
category: 'Pipelines',
language: rawData.pipeline_language || '',
ownership: rawData.pipeline_ownership || '',
keywords: parseKeywords(rawData.pipeline_keywords)
})
/**
* Transforms raw dataset data into SearchResultItem format
*/
const transformDatasetData = (rawData: DatasetRawData): SearchResultItem => ({
name: rawData.dataset_name || '',
url: rawData.dataset_url || '',
description: rawData.dataset_description || '',
category: 'Datasets',
language: rawData.dataset_type || '',
ownership: rawData.dataset_source || '',
keywords: parseKeywords(rawData.dataset_keywords)
})File: /app/pages/nexus.vue
Location: Around line 106-110
Before:
// Load and transform all pipeline data
const allResults = ref<SearchResultItem[]>([])
if (pipelinesData.value?.[0]?.meta?.body) {
const csvData = pipelinesData.value[0].meta.body as PipelineRawData[]
allResults.value = csvData.map(transformPipelineData)
}After:
// Load and transform all data
const allResults = ref<SearchResultItem[]>([])
// Load pipelines
if (pipelinesData.value?.[0]?.meta?.body) {
const csvData = pipelinesData.value[0].meta.body as PipelineRawData[]
allResults.value.push(...csvData.map(transformPipelineData))
}
// Load datasets
if (datasetsData.value?.[0]?.meta?.body) {
const csvData = datasetsData.value[0].meta.body as DatasetRawData[]
allResults.value.push(...csvData.map(transformDatasetData))
}cd /home/floreknx/Documents/staphb-rework
pnpm run devOpen: http://localhost:3000/nexus
- Click the category dropdown
- Verify "Datasets" appears in the list
- Select "Datasets"
- Select "Datasets" category
- Type "COVID" in search box
- Should see "SARS-CoV-2 Reference Genomes" result
- Click on a result card link
- Verify it opens the correct URL in new tab
- Check all metadata displays (type, source, keywords)
- Search for "surveillance"
- Should return multiple dataset results
- Try other keywords from your CSV
┌─────────────────┐
│ Pipelines │
│ Trainings │
│ Resources │
│ Datasets ✓ │ ← Your new category
└─────────────────┘
┌──────────────────────────────────────────────────────┐
│ SARS-CoV-2 Reference Genomes [Datasets] │
│ ─────────────────────────────────────────────────── │
│ Complete SARS-CoV-2 reference genome sequences │
│ from NCBI │
│ │
│ 📝 Reference Genomes 👥 NCBI │
│ │
│ [COVID-19] [SARS-CoV-2] [reference] [genomes]... │
└──────────────────────────────────────────────────────┘
Check:
// Verify this line was added:
{ label: 'Datasets', value: 'datasets' }Solution: Make sure spelling is exact and comma is present
Check:
- CSV file is in correct location:
/content/datasets/datasets.csv - CSV has proper headers with no typos
- Data loading code is present
- Transform function is called
Debug:
// Add temporary logging in nexus.vue:
console.log('Datasets loaded:', datasetsData.value)
console.log('All results:', allResults.value)Check:
// Interface must match CSV headers:
interface DatasetRawData {
dataset_name: string // ← Matches "dataset_name" in CSV
dataset_url: string // ← Matches "dataset_url" in CSV
dataset_description: string // ← etc.
...
}Solution: Ensure interface field names exactly match CSV headers
Check:
// Verify parseKeywords is called:
keywords: parseKeywords(rawData.dataset_keywords)CSV Format:
# Good - comma-separated in quotes:
"keyword1, keyword2, keyword3"
# Bad - no quotes with commas:
keyword1, keyword2, keyword3 ← Will break CSV parsingstaphb-rework/
├── content/
│ ├── pipelines/
│ │ └── piplines.csv
│ └── datasets/ ← NEW FOLDER
│ └── datasets.csv ← NEW FILE
│
└── app/
└── pages/
└── nexus.vue ← MODIFIED (6 sections updated)
feat: Add Datasets category to Nexus search
- Created datasets.csv with 4 initial datasets
- Added DatasetRawData interface
- Implemented transformDatasetData function
- Added datasets to search categories dropdown
- Tested search functionality
Datasets now searchable by name, description, type, source, and keywords.
After implementing this example:
- Add more datasets to
/content/datasets/datasets.csv - Customize field labels if needed (e.g., "Type" instead of "Language")
- Add more categories using the same pattern
- Implement advanced features from the main guide
You've successfully added a new category by:
✅ Creating a data file (CSV)
✅ Adding category to dropdown
✅ Defining TypeScript interface
✅ Loading data with useAsyncData
✅ Creating transform function
✅ Merging into search results
This same pattern works for any category: Tools, Documentation, Publications, etc.
Example Last Updated: January 22, 2026
Tested With: Nuxt 3, Vue 3, TypeScript