Skip to content

Commit

Permalink
Merge branch 'master' into bami/marketing-report
Browse files Browse the repository at this point in the history
  • Loading branch information
iambami authored Dec 14, 2024
2 parents 4c30374 + f26816b commit dfbaf34
Show file tree
Hide file tree
Showing 16 changed files with 320 additions and 21 deletions.
138 changes: 138 additions & 0 deletions markdown/blog/2024-december-and-paris.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
---
title: "November and December Community Update And AsyncAPI Conf in Paris 2024"
date: 2024-12-16T06:00:00+01:00
type: Communication
tags:
- Project Status
cover: /img/posts/2024-blog-banner/blog-banner-december.webp
authors:
- name: Thulisile Sibanda
photo: /img/avatars/thulieblack.webp
link: https://www.linkedin.com/in/v-thulisile-sibanda/
byline: Community Builder and Open Source Fanatic!
excerpt: 'November and December Community Update And Paris Conference Summary'
featured: true
---
I can't believe we are in the final weeks of the year, and it has been an eventful one. As a community, we have experienced both proud and painful moments, celebrated our victories, and faced challenges and losses. However, in the end, we overcame those difficulties and emerged stronger and better.

Although this summary is a bit later than usual, I am excited to share the details of what happened in November and December and the highlights from the last conference of the year, which took place in Paris.

## AsyncAPI Community Building and Maintenance Goals Proposal 2025

The 2025 community building and maintenance goals proposal is currently open for discussion and will soon be put to a vote for TSC members. We would love your thoughts and suggestions, particularly when solving our community's challenges.
[Please take a moment to review the open PR related to the AsyncAPI Community Building Goals for 2025](https://github.com/asyncapi/community/pull/1575) to participate in the discussion and share your ideas and solutions.

## AsyncAPI Conf in Paris 2024

The AsyncAPI Conf was back again in Paris this December, thanks to [Mehdi Medjaoui](https://www.linkedin.com/in/mehdimedjaoui/) and the amazing team at [APIdays](https://www.apidays.global/) for hosting and sponsoring the venue. We participated in the three-day event, celebrating a year of the `API Standards` booth alongside friends from OpenAPI, JSON Schema, and GraphQL. Special thanks to all the members of the AsyncAPI community who could join the conference and help at the booth: [Hugo Guerrero](https://www.linkedin.com/in/hugoguerrero/), [Fran Méndez](https://www.linkedin.com/in/fmvilas/), [Richard Coppen](https://www.linkedin.com/in/richard-coppen/), [Hari Krishnan](https://www.linkedin.com/in/harikrishnan83/), and [Lukasz Gornicki](https://www.linkedin.com/in/lukasz-gornicki-a621914/).

The AsyncAPI Conf track took place on the 3rd day of the conference, featuring an impressive lineup of sessions that attracted a diverse audience. The event was consistently packed, with attendees engaged throughout the day.

<Figure
src="/img/posts/paris-conf-2024/full-room.webp"
caption="Attendees engaged through the sessions."
className="text-center"
/>

[Fran Méndez](https://www.linkedin.com/in/fmvilas/) and [Lukasz Gornicki](https://www.linkedin.com/in/lukasz-gornicki-a621914/) started the track with a welcome speech and mentioned that AsyncAPI recently celebrated its 8th anniversary in November.

<Figure
src="/img/posts/paris-conf-2024/lukasz-and-fran.webp"
caption="Fran and Lukasz during the welcome speech."
className="text-center"
/>

[Naresh Jain](https://www.linkedin.com/in/nareshjain/) and [Pierre Gauthier](https://www.linkedin.com/in/pierre-gauthier-46080916/) presented their keynote on `TMForum's AsyncAPI for a New Era of Event-Driven Architecture`. During the session, Pierre announced TMForum adopts AsyncAPI as a standard, with over 120 telco APIs already in production. TMForum has around 800 telco companies, and they will implement all APIs in an async manner, extensively utilizing the request-reply pattern.

<Figure
src="/img/posts/paris-conf-2024/naresh-and-pierre.webp"
caption="Naresh and Pierre presenting their keynote."
className="text-center"
/>

[Frank Kilcommins](https://www.linkedin.com/in/frank-kilcommins) demonstrated how treating API governance as an enabler unlocks the ability to deliver compelling developer experiences for producers and consumers in event-driven architecture (EDA).

<Figure
src="/img/posts/paris-conf-2024/frank.webp"
caption="Frank presenting on API Governance for EDA: Unlocking Developer Experience with AsyncAPI."
className="text-center"
/>

[Leonid Lukyanov](https://www.linkedin.com/in/leonid-lukyanov/) shared how EDAs introduce new data models, protocols, and APIs not found in the traditional REST/CRUD application stack. And how one can abstract these elements to make them feel familiar to application developers, allowing them to create streaming applications seamlessly.

<Figure
src="/img/posts/paris-conf-2024/leonid.webp"
caption="Leonid presenting on Reimagining Streaming Apps with AsyncAPI and Postgres."
className="text-center"
/>

[Hugo Guerrero](https://www.linkedin.com/in/hugoguerrero/) then shared how the AsyncAPI Initiative is not only in charge of the specification but has created open-source projects to make it easier for developers to work with the specification documents.

<Figure
src="/img/posts/paris-conf-2024/hugo.webp"
caption="Hugo presenting on Using the AsyncAPI Ecosystem for Your Event-Driven Architecture."
className="text-center"
/>

[Julien Testut](https://www.linkedin.com/in/julientestut/) and [Alessandro Cagnetti](https://www.linkedin.com/in/cagnetti/) shared how organizations can harness the full potential of event-driven integration by leveraging GoldenGate Data Streams, AsyncAPI, and Solace PubSub+ Event Mesh. They shared a great use case for AI and how it can be trained real-time and standardized with AsyncAPI.

<Figure
src="/img/posts/paris-conf-2024/julien-and-alessandro.webp"
caption="Julien and Alessandro presenting on Streaming Data Events Into An AI Cognitive Event Mesh Using AsyncAPI."
className="text-center"
/>

[Annegret Junker](https://www.linkedin.com/in/dr-annegret-junker-141a99a4/) explained how to design effective asynchronous APIs by using an API-first approach. The importance of defining Kafka topics and structuring your definitions.

<Figure
src="/img/posts/paris-conf-2024/annegret.webp"
caption="Annegret presenting on AsyncAPI for Kafka."
className="text-center"
/>


[Jonathan Michaux](https://www.linkedin.com/in/jmcx/) spoke on how leveraging AI agents with AsyncAPI can create conversational interfaces that dynamically interact with event streams and asynchronous messaging systems.

<Figure
src="/img/posts/paris-conf-2024/jonathan.webp"
caption="Jonathan presenting on AI Agents Meet AsyncAPI: Conversational Interfaces for Event Streams."
className="text-center"
/>

[Hari Krishnan](https://www.linkedin.com/in/harikrishnan83) and [Joel Rosario](https://www.linkedin.com/in/joel-c-rosario/) touched on leveraging the AsyncAPI specification as an executable contract and how to isolate and test each component within an EDA.

<Figure
src="/img/posts/paris-conf-2024/hari-and-joel.webp"
caption="Hari and Joel presenting on Contract-Driven Development for Event-Driven Architectures."
className="text-center"
/>

[Laurent Broudoux](https://www.linkedin.com/in/laurentbroudoux/) and [Hugo Guerrero](https://www.linkedin.com/in/hugoguerrero/) ended the day by explaining how to use Microcks to provide a solution for mocking and contract-testing your async APIs without extensive coding and empowering you to build extensive and reliable integration tests.

<Figure
src="/img/posts/paris-conf-2024/laurent-and-hugo.webp"
caption="Laurent and Hugo presenting From Nightmares to Sweet Dreams: Conquering AsyncAPI Testing!."
className="text-center"
/>

## Technical Steering Committee

Part of doing mentorships is witnessing the growth within the community, and we are excited to welcome [Ashmit Jagtap](https://www.linkedin.com/in/ashmit-jagtap) as our newest addition to the maintainers list and TSC member. We are proud of the work you have done so far.

<Profiles profiles={[
{
name: 'Ashmit Jagtap',
avatar: 'https://avatars.githubusercontent.com/u/69006513?v=4',
link: 'https://www.github.com/ashmit-coder'
}
]} />

## Final Remarks

It's been a privilege to write the AsyncAPI monthly summary blog consistently. As this is the final blog for the year, I am genuinely grateful for the opportunity to serve and continue supporting the community.

As we approach the holidays, I wish everyone happy holidays and a fantastic 2025.

I'll be back next year with an overall review summary of 2024.

**Until then, stay safe, and happy holidays!**
1 change: 0 additions & 1 deletion markdown/blog/2024-october-summary.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ authors:
link: https://www.linkedin.com/in/v-thulisile-sibanda/
byline: AsyncAPI Community Manager
excerpt: 'October Community Update And Online Conference Summary'
featured: true
---

October marked the third AsyncAPI Conference, this time an online edition. As someone fortunate enough to be extensively involved in organizing the conferences, I saw the challenges that came with in-person events.
Expand Down
Binary file not shown.
Binary file added public/img/posts/paris-conf-2024/annegret.webp
Binary file not shown.
Binary file added public/img/posts/paris-conf-2024/frank.webp
Binary file not shown.
Binary file added public/img/posts/paris-conf-2024/full-room.webp
Binary file not shown.
Binary file not shown.
Binary file added public/img/posts/paris-conf-2024/hugo.webp
Binary file not shown.
Binary file added public/img/posts/paris-conf-2024/jonathan.webp
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file added public/img/posts/paris-conf-2024/leonid.webp
Binary file not shown.
Binary file not shown.
Binary file not shown.
52 changes: 32 additions & 20 deletions scripts/markdown/check-markdown.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
const fs = require('fs');
const fs = require('fs').promises;
const matter = require('gray-matter');
const path = require('path');

Expand Down Expand Up @@ -98,14 +98,10 @@ function validateDocs(frontmatter) {
* @param {Function} validateFunction - The function used to validate the frontmatter.
* @param {string} [relativePath=''] - The relative path of the folder for logging purposes.
*/
function checkMarkdownFiles(folderPath, validateFunction, relativePath = '') {
fs.readdir(folderPath, (err, files) => {
if (err) {
console.error('Error reading directory:', err);
return;
}

files.forEach(file => {
async function checkMarkdownFiles(folderPath, validateFunction, relativePath = '') {
try {
const files = await fs.readdir(folderPath);
const filePromises = files.map(async (file) => {
const filePath = path.join(folderPath, file);
const relativeFilePath = path.join(relativePath, file);

Expand All @@ -114,17 +110,13 @@ function checkMarkdownFiles(folderPath, validateFunction, relativePath = '') {
return;
}

fs.stat(filePath, (err, stats) => {
if (err) {
console.error('Error reading file stats:', err);
return;
}
const stats = await fs.stat(filePath);

// Recurse if directory, otherwise validate markdown file
if (stats.isDirectory()) {
checkMarkdownFiles(filePath, validateFunction, relativeFilePath);
await checkMarkdownFiles(filePath, validateFunction, relativeFilePath);
} else if (path.extname(file) === '.md') {
const fileContent = fs.readFileSync(filePath, 'utf-8');
const fileContent = await fs.readFile(filePath, 'utf-8');
const { data: frontmatter } = matter(fileContent);

const errors = validateFunction(frontmatter);
Expand All @@ -134,13 +126,33 @@ function checkMarkdownFiles(folderPath, validateFunction, relativePath = '') {
process.exitCode = 1;
}
}
});
});
});

await Promise.all(filePromises);
} catch (err) {
console.error(`Error in directory ${folderPath}:`, err);
throw err;
}
}

const docsFolderPath = path.resolve(__dirname, '../../markdown/docs');
const blogsFolderPath = path.resolve(__dirname, '../../markdown/blog');

checkMarkdownFiles(docsFolderPath, validateDocs);
checkMarkdownFiles(blogsFolderPath, validateBlogs);
async function main() {
try {
await Promise.all([
checkMarkdownFiles(docsFolderPath, validateDocs),
checkMarkdownFiles(blogsFolderPath, validateBlogs)
]);
} catch (error) {
console.error('Failed to validate markdown files:', error);
process.exit(1);
}
}

/* istanbul ignore next */
if (require.main === module) {
main();
}

module.exports = { validateBlogs, validateDocs, checkMarkdownFiles, main, isValidURL };
150 changes: 150 additions & 0 deletions tests/markdown/check-markdown.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
const fs = require('fs').promises;
const path = require('path');
const os = require('os');
const {
isValidURL,
main,
validateBlogs,
validateDocs,
checkMarkdownFiles
} = require('../../scripts/markdown/check-markdown');

describe('Frontmatter Validator', () => {
let tempDir;
let mockConsoleError;
let mockProcessExit;

beforeEach(async () => {
mockConsoleError = jest.spyOn(console, 'error').mockImplementation();
mockProcessExit = jest.spyOn(process, 'exit').mockImplementation();
tempDir = await fs.mkdtemp(path.join(os.tmpdir(), 'test-config'));
});

afterEach(async () => {
mockConsoleError.mockRestore();
mockProcessExit.mockRestore();
await fs.rm(tempDir, { recursive: true, force: true });
});

it('validates authors array and returns specific errors', async () => {
const frontmatter = {
title: 'Test Blog',
date: '2024-01-01',
type: 'blog',
tags: ['test'],
cover: 'cover.jpg',
authors: [{ name: 'John' }, { photo: 'jane.jpg' }, { name: 'Bob', photo: 'bob.jpg', link: 'not-a-url' }]
};

const errors = validateBlogs(frontmatter);
expect(errors).toEqual(expect.arrayContaining([
'Author at index 0 is missing a photo',
'Author at index 1 is missing a name',
'Invalid URL for author at index 2: not-a-url'
]));
});

it('validates docs frontmatter for required fields', async () => {
const frontmatter = { title: 123, weight: 'not-a-number' };
const errors = validateDocs(frontmatter);
expect(errors).toEqual(expect.arrayContaining([
'Title is missing or not a string',
'Weight is missing or not a number'
]));
});

it('checks for errors in markdown files in a directory', async () => {
await fs.writeFile(path.join(tempDir, 'invalid.md'), `---\ntitle: Invalid Blog\n---`);
const mockConsoleLog = jest.spyOn(console, 'log').mockImplementation();

await checkMarkdownFiles(tempDir, validateBlogs);

expect(mockConsoleLog).toHaveBeenCalledWith(expect.stringContaining('Errors in file invalid.md:'));
mockConsoleLog.mockRestore();
});

it('returns multiple validation errors for invalid blog frontmatter', async () => {
const frontmatter = {
title: 123,
date: 'invalid-date',
type: 'blog',
tags: 'not-an-array',
cover: ['not-a-string'],
authors: { name: 'John Doe' }
};
const errors = validateBlogs(frontmatter);

expect(errors).toEqual([
'Invalid date format: invalid-date',
'Tags should be an array',
'Cover must be a string',
'Authors should be an array']);
});

it('logs error to console when an error occurs in checkMarkdownFiles', async () => {
const invalidFolderPath = path.join(tempDir, 'non-existent-folder');

await expect(checkMarkdownFiles(invalidFolderPath, validateBlogs))
.rejects.toThrow('ENOENT');

expect(mockConsoleError.mock.calls[0][0]).toContain('Error in directory');
});

it('skips the "reference/specification" folder during validation', async () => {
const referenceSpecDir = path.join(tempDir, 'reference', 'specification');
await fs.mkdir(referenceSpecDir, { recursive: true });
await fs.writeFile(path.join(referenceSpecDir, 'skipped.md'), `---\ntitle: Skipped File\n---`);

const mockConsoleLog = jest.spyOn(console, 'log').mockImplementation();

await checkMarkdownFiles(tempDir, validateDocs);

expect(mockConsoleLog).not.toHaveBeenCalledWith(expect.stringContaining('Errors in file reference/specification/skipped.md'));
mockConsoleLog.mockRestore();
});

it('logs and rejects when an exception occurs while processing a file', async () => {
const filePath = path.join(tempDir, 'invalid.md');
await fs.writeFile(filePath, `---\ntitle: Valid Title\n---`);

const mockReadFile = jest.spyOn(fs, 'readFile').mockRejectedValue(new Error('Test readFile error'));

await expect(checkMarkdownFiles(tempDir, validateBlogs)).rejects.toThrow('Test readFile error');
expect(mockConsoleError).toHaveBeenCalledWith(
expect.stringContaining(`Error in directory`),
expect.any(Error)
);

mockReadFile.mockRestore();
});

it('should handle main function errors and exit with status 1', async () => {
jest.spyOn(fs, 'readdir').mockRejectedValue(new Error('Test error'));

await main();

expect(mockProcessExit).toHaveBeenCalledWith(1);

expect(mockConsoleError).toHaveBeenCalledWith(
'Failed to validate markdown files:',
expect.any(Error)
);
});

it('should handle successful main function execution', async () => {

await main();

expect(mockConsoleError).not.toHaveBeenCalledWith();
});

it('should return true or false for URLs', () => {
expect(isValidURL('http://example.com')).toBe(true);
expect(isValidURL('https://www.example.com')).toBe(true);
expect(isValidURL('ftp://ftp.example.com')).toBe(true);
expect(isValidURL('invalid-url')).toBe(false);
expect(isValidURL('/path/to/file')).toBe(false);
expect(isValidURL('www.example.com')).toBe(false);
});

});

0 comments on commit dfbaf34

Please sign in to comment.