Compare commits

...

11 Commits

Author SHA1 Message Date
Arik Chakma
1c80f22a54 feat: not found topics 2025-08-22 10:38:23 +06:00
kamranahmedse
12ae7de3c5 chore: sync content to repo 2025-08-21 18:14:25 +01:00
Kamran Ahmed
9316d4027f Add BI analyst roadmap 2025-08-21 17:47:33 +01:00
Kamran Ahmed
5a63432412 Add BI analyst 2025-08-21 17:45:26 +01:00
kamranahmedse
ffecb5ae1a chore: sync content to repo 2025-08-21 16:21:29 +01:00
Kamran Ahmed
7a51c1af6c fix: broken syntax of workflow 2025-08-21 16:19:03 +01:00
Arik Chakma
6970cccc85 chore: add kamran 2025-08-21 16:04:23 +01:00
Arik Chakma
78940d44a9 fix: replace sync endpoint 2025-08-21 16:04:23 +01:00
Arik Chakma
6f11403a41 feat: migrate content to database 2025-08-21 16:04:23 +01:00
Arik Chakma
214799b0c2 chore: replace topic content 2025-08-21 16:04:23 +01:00
Arik Chakma
b5f564cba4 chore: add javi as reviewers 2025-08-21 16:04:23 +01:00
286 changed files with 8867 additions and 257 deletions

1
.astro/types.d.ts vendored
View File

@@ -1,2 +1 @@
/// <reference types="astro/client" />
/// <reference path="content.d.ts" />

View File

@@ -50,9 +50,8 @@ jobs:
branch: "chore/sync-content-to-repo-${{ inputs.roadmap_slug }}"
base: "master"
labels: |
dependencies
automated pr
reviewers: arikchakma
reviewers: jcanalesluna,kamranahmedse
commit-message: "chore: sync content to repo"
title: "chore: sync content to repository - ${{ inputs.roadmap_slug }}"
body: |
@@ -64,4 +63,4 @@ jobs:
> Commit: ${{ github.sha }}
> Workflow Path: ${{ github.workflow_ref }}
**Please Review the Changes and Merge the PR if everything is fine.**
**Please Review the Changes and Merge the PR if everything is fine.**

View File

@@ -31,6 +31,7 @@
"migrate:editor-roadmaps": "tsx ./scripts/migrate-editor-roadmap.ts",
"sync:content-to-repo": "tsx ./scripts/sync-content-to-repo.ts",
"sync:repo-to-database": "tsx ./scripts/sync-repo-to-database.ts",
"migrate:content-repo-to-database": "tsx ./scripts/migrate-content-repo-to-database.ts",
"test:e2e": "playwright test"
},
"dependencies": {

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 633 KiB

View File

@@ -47,6 +47,7 @@ Here is the list of available roadmaps with more being actively worked upon.
- [Linux Roadmap](https://roadmap.sh/linux)
- [Terraform Roadmap](https://roadmap.sh/terraform)
- [Data Analyst Roadmap](https://roadmap.sh/data-analyst)
- [BI Analyst Roadmap](https://roadmap.sh/bi-analyst)
- [Data Engineer Roadmap](https://roadmap.sh/data-engineer)
- [Machine Learning Roadmap](https://roadmap.sh/machine-learning)
- [MLOps Roadmap](https://roadmap.sh/mlops)

View File

@@ -0,0 +1,255 @@
import fs from 'node:fs/promises';
import path from 'node:path';
import { fileURLToPath } from 'node:url';
import type { OfficialRoadmapDocument } from '../src/queries/official-roadmap';
import { parse } from 'node-html-parser';
import { markdownToHtml } from '../src/lib/markdown';
import { htmlToMarkdown } from '../src/lib/html';
import matter from 'gray-matter';
import type { RoadmapFrontmatter } from '../src/lib/roadmap';
import {
allowedOfficialRoadmapTopicResourceType,
type AllowedOfficialRoadmapTopicResourceType,
type SyncToDatabaseTopicContent,
} from '../src/queries/official-roadmap-topic';
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
const args = process.argv.slice(2);
const secret = args
.find((arg) => arg.startsWith('--secret='))
?.replace('--secret=', '');
if (!secret) {
throw new Error('Secret is required');
}
let roadmapJsonCache: Map<string, OfficialRoadmapDocument> = new Map();
export async function fetchRoadmapJson(
roadmapId: string,
): Promise<OfficialRoadmapDocument> {
if (roadmapJsonCache.has(roadmapId)) {
return roadmapJsonCache.get(roadmapId)!;
}
const response = await fetch(
`https://roadmap.sh/api/v1-official-roadmap/${roadmapId}`,
);
if (!response.ok) {
throw new Error(
`Failed to fetch roadmap json: ${response.statusText} for ${roadmapId}`,
);
}
const data = await response.json();
if (data.error) {
throw new Error(
`Failed to fetch roadmap json: ${data.error} for ${roadmapId}`,
);
}
roadmapJsonCache.set(roadmapId, data);
return data;
}
export async function syncContentToDatabase(
topics: SyncToDatabaseTopicContent[],
) {
const response = await fetch(
`https://roadmap.sh/api/v1-sync-official-roadmap-topics`,
{
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
topics,
secret,
}),
},
);
if (!response.ok) {
const error = await response.json();
throw new Error(
`Failed to sync content to database: ${response.statusText} ${JSON.stringify(error, null, 2)}`,
);
}
return response.json();
}
// Directory containing the roadmaps
const ROADMAP_CONTENT_DIR = path.join(__dirname, '../src/data/roadmaps');
const allRoadmaps = await fs.readdir(ROADMAP_CONTENT_DIR);
const editorRoadmapIds = new Set<string>();
for (const roadmapId of allRoadmaps) {
const roadmapFrontmatterDir = path.join(
ROADMAP_CONTENT_DIR,
roadmapId,
`${roadmapId}.md`,
);
const roadmapFrontmatterRaw = await fs.readFile(
roadmapFrontmatterDir,
'utf-8',
);
const { data } = matter(roadmapFrontmatterRaw);
const roadmapFrontmatter = data as RoadmapFrontmatter;
if (roadmapFrontmatter.renderer === 'editor') {
editorRoadmapIds.add(roadmapId);
}
}
for (const roadmapId of editorRoadmapIds) {
try {
const roadmap = await fetchRoadmapJson(roadmapId);
const files = await fs.readdir(
path.join(ROADMAP_CONTENT_DIR, roadmapId, 'content'),
);
console.log(`🚀 Starting ${files.length} files for ${roadmapId}`);
const topics: SyncToDatabaseTopicContent[] = [];
for (const file of files) {
const isContentFile = file.endsWith('.md');
if (!isContentFile) {
console.log(`🚨 Skipping ${file} because it is not a content file`);
continue;
}
const nodeSlug = file.replace('.md', '');
if (!nodeSlug) {
console.error(`🚨 Node id is required: ${file}`);
continue;
}
const nodeId = nodeSlug.split('@')?.[1];
if (!nodeId) {
console.error(`🚨 Node id is required: ${file}`);
continue;
}
const node = roadmap.nodes.find((node) => node.id === nodeId);
if (!node) {
console.error(`🚨 Node not found: ${file}`);
continue;
}
const filePath = path.join(
ROADMAP_CONTENT_DIR,
roadmapId,
'content',
`${nodeSlug}.md`,
);
const fileExists = await fs
.stat(filePath)
.then(() => true)
.catch(() => false);
if (!fileExists) {
console.log(`🚨 File not found: ${filePath}`);
continue;
}
const content = await fs.readFile(filePath, 'utf8');
const html = markdownToHtml(content, false);
const rootHtml = parse(html);
let ulWithLinks: HTMLElement | undefined;
rootHtml.querySelectorAll('ul').forEach((ul) => {
const listWithJustLinks = Array.from(ul.querySelectorAll('li')).filter(
(li) => {
const link = li.querySelector('a');
return link && link.textContent?.trim() === li.textContent?.trim();
},
);
if (listWithJustLinks.length > 0) {
// @ts-expect-error - TODO: fix this
ulWithLinks = ul;
}
});
const listLinks: SyncToDatabaseTopicContent['resources'] =
ulWithLinks !== undefined
? Array.from(ulWithLinks.querySelectorAll('li > a'))
.map((link) => {
const typePattern = /@([a-z.]+)@/;
let linkText = link.textContent || '';
const linkHref = link.getAttribute('href') || '';
let linkType = linkText.match(typePattern)?.[1] || 'article';
linkType = allowedOfficialRoadmapTopicResourceType.includes(
linkType as any,
)
? linkType
: 'article';
linkText = linkText.replace(typePattern, '');
if (!linkText || !linkHref) {
return null;
}
return {
title: linkText,
url: linkHref,
type: linkType as AllowedOfficialRoadmapTopicResourceType,
};
})
.filter((link) => link !== null)
.sort((a, b) => {
const order = [
'official',
'opensource',
'article',
'video',
'feed',
];
return order.indexOf(a!.type) - order.indexOf(b!.type);
})
: [];
const title = rootHtml.querySelector('h1');
ulWithLinks?.remove();
title?.remove();
const allParagraphs = rootHtml.querySelectorAll('p');
if (listLinks.length > 0 && allParagraphs.length > 0) {
// to remove the view more see more from the description
const lastParagraph = allParagraphs[allParagraphs.length - 1];
lastParagraph?.remove();
}
const htmlStringWithoutLinks = rootHtml.toString();
const description = htmlToMarkdown(htmlStringWithoutLinks);
const updatedDescription =
`# ${title?.textContent}\n\n${description}`.trim();
const label = node?.data?.label as string;
if (!label) {
console.error(`🚨 Label is required: ${file}`);
continue;
}
topics.push({
roadmapSlug: roadmapId,
nodeId,
description: updatedDescription,
resources: listLinks,
});
}
await syncContentToDatabase(topics);
console.log(
`✅ Synced ${topics.length} topics to database for ${roadmapId}`,
);
} catch (error) {
console.error(error);
process.exit(1);
}
}

View File

@@ -3,6 +3,7 @@ import path from 'node:path';
import { fileURLToPath } from 'node:url';
import { slugify } from '../src/lib/slugger';
import type { OfficialRoadmapDocument } from '../src/queries/official-roadmap';
import type { OfficialRoadmapTopicContentDocument } from '../src/queries/official-roadmap-topic';
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
@@ -19,36 +20,6 @@ if (!roadmapSlug || roadmapSlug === '__default__') {
}
console.log(`🚀 Starting ${roadmapSlug}`);
export const allowedOfficialRoadmapTopicResourceType = [
'roadmap',
'official',
'opensource',
'article',
'course',
'podcast',
'video',
'book',
'feed',
] as const;
export type AllowedOfficialRoadmapTopicResourceType =
(typeof allowedOfficialRoadmapTopicResourceType)[number];
export type OfficialRoadmapTopicResource = {
_id?: string;
type: AllowedOfficialRoadmapTopicResourceType;
title: string;
url: string;
};
export interface OfficialRoadmapTopicContentDocument {
_id?: string;
roadmapSlug: string;
nodeId: string;
description: string;
resources: OfficialRoadmapTopicResource[];
createdAt: Date;
updatedAt: Date;
}
export async function roadmapTopics(
roadmapId: string,

View File

@@ -5,37 +5,11 @@ import type { OfficialRoadmapDocument } from '../src/queries/official-roadmap';
import { parse } from 'node-html-parser';
import { markdownToHtml } from '../src/lib/markdown';
import { htmlToMarkdown } from '../src/lib/html';
export const allowedOfficialRoadmapTopicResourceType = [
'roadmap',
'official',
'opensource',
'article',
'course',
'podcast',
'video',
'book',
'feed',
] as const;
export type AllowedOfficialRoadmapTopicResourceType =
(typeof allowedOfficialRoadmapTopicResourceType)[number];
export type OfficialRoadmapTopicResource = {
_id?: string;
type: AllowedOfficialRoadmapTopicResourceType;
title: string;
url: string;
};
export interface OfficialRoadmapTopicContentDocument {
_id?: string;
roadmapSlug: string;
nodeId: string;
description: string;
resources: OfficialRoadmapTopicResource[];
createdAt: Date;
updatedAt: Date;
}
import {
allowedOfficialRoadmapTopicResourceType,
type AllowedOfficialRoadmapTopicResourceType,
type SyncToDatabaseTopicContent,
} from '../src/queries/official-roadmap-topic';
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
@@ -82,10 +56,7 @@ export async function fetchRoadmapJson(
}
export async function syncContentToDatabase(
topics: Omit<
OfficialRoadmapTopicContentDocument,
'createdAt' | 'updatedAt' | '_id'
>[],
topics: SyncToDatabaseTopicContent[],
) {
const response = await fetch(
`https://roadmap.sh/api/v1-sync-official-roadmap-topics`,
@@ -125,10 +96,7 @@ console.log(`🚀 Starting ${files.length} files`);
const ROADMAP_CONTENT_DIR = path.join(__dirname, '../src/data/roadmaps');
try {
const topics: Omit<
OfficialRoadmapTopicContentDocument,
'createdAt' | 'updatedAt' | '_id'
>[] = [];
const topics: SyncToDatabaseTopicContent[] = [];
for (const file of files) {
const isContentFile = file.endsWith('.md') && file.includes('content/');
@@ -198,7 +166,7 @@ try {
}
});
const listLinks: Omit<OfficialRoadmapTopicResource, '_id'>[] =
const listLinks: SyncToDatabaseTopicContent['resources'] =
ulWithLinks !== undefined
? Array.from(ulWithLinks.querySelectorAll('li > a'))
.map((link) => {

View File

@@ -408,6 +408,11 @@ const groups: GroupType[] = [
link: '/data-analyst',
type: 'role',
},
{
title: 'BI Analyst',
link: '/bi-analyst',
type: 'role',
},
{
title: 'Data Engineer',
link: '/data-engineer',

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,49 @@
---
pdfUrl: '/pdfs/roadmaps/bi-analyst.pdf'
jsonUrl: '/jsons/roadmaps/bi-analyst.json'
order: 21
title: 'BI Analyst'
description: 'Learn to become a Business Intelligence Analyst in 2025'
briefTitle: 'BI Analyst'
briefDescription: 'Learn to become a Business Intelligence Analyst in 2025'
hasTopics: true
isHidden: false
isUpcoming: false
isNew: true
dimensions:
width: 968
height: 5120
schema:
headline: 'BI Analyst'
description: 'Learn what business intelligence analysis is, what BI analysts do and how to become one using our community-driven roadmap.'
datePublished: '2025-08-21'
dateModified: '2025-08-21'
imageUrl: 'https://roadmap.sh/roadmaps/bi-analyst.png'
seo:
title: 'BI Analyst'
description: 'Learn what business intelligence analysis is, what BI analysts do and how to become one using our community-driven roadmap.'
keywords:
- 'bi analyst'
- 'bi analyst roadmap'
- 'bi analyst roadmap 2025'
- 'Business Intelligence'
- 'become a BI Analyst'
- 'data analytics'
- 'business intelligence analyst'
- 'analytical skills'
- 'data visualization'
- 'career roadmap'
relatedRoadmaps:
- 'data-analyst'
- 'sql'
- 'python'
- 'ai-data-scientist'
sitemap:
priority: 1
changefreq: monthly
tags:
- 'roadmap'
- 'main-sitemap'
- 'role-roadmap'
renderer: editor
---

View File

@@ -0,0 +1 @@
# A/B Testing

View File

@@ -0,0 +1 @@
# Accesibility

View File

@@ -0,0 +1 @@
# Accessibility

View File

@@ -0,0 +1 @@
# Accuracy

View File

@@ -0,0 +1 @@
# Advanced Queries

View File

@@ -0,0 +1 @@
# Airflow

View File

@@ -0,0 +1 @@
# Algorithmic Bias

View File

@@ -0,0 +1 @@
# Analog vs Digital Data

View File

@@ -0,0 +1 @@
# APIs

View File

@@ -0,0 +1 @@
# Barplot

View File

@@ -0,0 +1 @@
# Basic Machine Learning

View File

@@ -0,0 +1 @@
# Basic Queries

View File

@@ -0,0 +1 @@
# Beyond Linear Regression

View File

@@ -0,0 +1,3 @@
# BI Analyst vs. Other Roles
A BI Analyst focuses on analyzing data to provide insights and recommendations for business improvements. This role differs from other data-related roles like Data Scientists, who build predictive models, or Data Engineers, who focus on building and maintaining data infrastructure. While a BI Analyst uses data to understand past and current performance, other roles might focus on predicting future outcomes or ensuring the data is readily available for analysis.

View File

@@ -0,0 +1 @@
# BI Communities

View File

@@ -0,0 +1 @@
# BI Competitions

View File

@@ -0,0 +1 @@
# BI Platforms

View File

@@ -0,0 +1 @@
# Bias Recognition

View File

@@ -0,0 +1 @@
# Building Your Portfolio

View File

@@ -0,0 +1 @@
# Business Acumen

View File

@@ -0,0 +1 @@
# Calculated Fields & Measures

View File

@@ -0,0 +1 @@
# Categorical vs Numerical

View File

@@ -0,0 +1 @@
# CCPA

View File

@@ -0,0 +1 @@
# Central Tendency

View File

@@ -0,0 +1 @@
# Certifications

View File

@@ -0,0 +1 @@
# Change Management

View File

@@ -0,0 +1 @@
# Chart Categories

View File

@@ -0,0 +1 @@
# Cloud BI Ecosystem

View File

@@ -0,0 +1 @@
# Cloud Computing Basics

View File

@@ -0,0 +1 @@
# Cloud data warehouses

View File

@@ -0,0 +1 @@
# Cloud

View File

@@ -0,0 +1 @@
# CLV

View File

@@ -0,0 +1 @@
# Coherence

View File

@@ -0,0 +1 @@
# Cohort Analysis

View File

@@ -0,0 +1 @@
# Color theory

View File

@@ -0,0 +1 @@
# Communication & Storytelling

View File

@@ -0,0 +1 @@
# Compliance Reporting

View File

@@ -0,0 +1 @@
# Compliance Reporting

View File

@@ -0,0 +1 @@
# Conferences & Webinars

View File

@@ -0,0 +1 @@
# Confidence Intervals

View File

@@ -0,0 +1 @@
# Correlation Analysis

View File

@@ -0,0 +1 @@
# Correlation vs Causation

View File

@@ -0,0 +1 @@
# Critical Thinking

View File

@@ -0,0 +1 @@
# CSV

View File

@@ -0,0 +1 @@
# Dashboard Design

View File

@@ -0,0 +1 @@
# Dashboard Design

View File

@@ -0,0 +1 @@
# Data Architectures

View File

@@ -0,0 +1 @@
# Data Cleaning

View File

@@ -0,0 +1 @@
# Data Formats

View File

@@ -0,0 +1 @@
# Data Lake

View File

@@ -0,0 +1 @@
# Data Lineage

View File

@@ -0,0 +1 @@
# Data Mart

View File

@@ -0,0 +1 @@
# Data Modeling for BI

View File

@@ -0,0 +1 @@
# Data Pipeline Design

View File

@@ -0,0 +1 @@
# Data Quality

View File

@@ -0,0 +1 @@
# Data Sources

View File

@@ -0,0 +1 @@
# Data Transformation Techniques

View File

@@ -0,0 +1 @@
# Data Warehouse

View File

@@ -0,0 +1 @@
# Databases

View File

@@ -0,0 +1 @@
# dbt

View File

@@ -0,0 +1 @@
# Descriptive Analysis

View File

@@ -0,0 +1 @@
# Descriptive Statistics

View File

@@ -0,0 +1 @@
# Design principles

View File

@@ -0,0 +1 @@
# Diagnostic Analysis

View File

@@ -0,0 +1 @@
# Discrete vs Continuous

View File

@@ -0,0 +1 @@
# Dispersion

View File

@@ -0,0 +1 @@
# Distribution

View File

@@ -0,0 +1 @@
# dplyr

View File

@@ -0,0 +1 @@
# Duplicates

View File

@@ -0,0 +1 @@
# End-to-end Analytics Project

View File

@@ -0,0 +1 @@
# Ethical Data Use

View File

@@ -0,0 +1 @@
# ETL basics

View File

@@ -0,0 +1 @@
# ETL Tools

View File

@@ -0,0 +1 @@
# Excel

View File

@@ -0,0 +1 @@
# Excel

View File

@@ -0,0 +1 @@
# Excel

View File

@@ -0,0 +1 @@
# Exploratory Data Analysis (EDA)

View File

@@ -0,0 +1 @@
# Fact vs Dimension Tables

View File

@@ -0,0 +1 @@
# Finance

View File

@@ -0,0 +1 @@
# Finance

View File

@@ -0,0 +1 @@
# Financial Performance

View File

@@ -0,0 +1 @@
# Forecasting

View File

@@ -0,0 +1 @@
# Fraud Detection

View File

@@ -0,0 +1 @@
# GDPR

View File

@@ -0,0 +1 @@
# Healthcare

View File

@@ -0,0 +1 @@
# Heatmap

View File

@@ -0,0 +1 @@
# Histogram

View File

@@ -0,0 +1 @@
# Hospital Efficiency

Some files were not shown because too many files have changed in this diff Show More