@eventcatalog/core 0.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (62) hide show
  1. package/CHANGELOG.md +1 -0
  2. package/README.md +11 -0
  3. package/bin/eventcatalog.js +125 -0
  4. package/components/BreadCrumbs.tsx +50 -0
  5. package/components/ContentView.tsx +127 -0
  6. package/components/Footer.tsx +38 -0
  7. package/components/Grids/EventGrid.tsx +89 -0
  8. package/components/Grids/ServiceGrid.tsx +70 -0
  9. package/components/Header.tsx +59 -0
  10. package/components/Mdx/Admonition.tsx +33 -0
  11. package/components/Mdx/Examples.tsx +77 -0
  12. package/components/Mermaid/index.tsx +47 -0
  13. package/components/NotFound/index.tsx +44 -0
  14. package/components/Sidebars/EventSidebar.tsx +202 -0
  15. package/components/Sidebars/ServiceSidebar.tsx +198 -0
  16. package/components/SyntaxHighlighter.tsx +34 -0
  17. package/hooks/EventCatalog.tsx +35 -0
  18. package/lib/__tests__/assets/events/AddedItemToCart/index.md +19 -0
  19. package/lib/__tests__/assets/events/EmailSent/index.md +15 -0
  20. package/lib/__tests__/assets/events/EventWithSchemaAndExamples/examples/Basic.cs +31 -0
  21. package/lib/__tests__/assets/events/EventWithSchemaAndExamples/examples/Basic.js +1 -0
  22. package/lib/__tests__/assets/events/EventWithSchemaAndExamples/index.md +8 -0
  23. package/lib/__tests__/assets/events/EventWithSchemaAndExamples/schema.json +4 -0
  24. package/lib/__tests__/assets/events/EventWithVersions/index.md +10 -0
  25. package/lib/__tests__/assets/events/EventWithVersions/versioned/0.0.1/index.md +10 -0
  26. package/lib/__tests__/assets/services/Email Platform/index.md +17 -0
  27. package/lib/__tests__/events.spec.ts +294 -0
  28. package/lib/__tests__/file-reader.spec.ts +57 -0
  29. package/lib/__tests__/graphs.spec.ts +62 -0
  30. package/lib/__tests__/services.spec.ts +144 -0
  31. package/lib/events.ts +221 -0
  32. package/lib/file-reader.ts +52 -0
  33. package/lib/graphs.ts +33 -0
  34. package/lib/services.ts +72 -0
  35. package/next-env.d.ts +5 -0
  36. package/next.config.js +3 -0
  37. package/package.json +52 -0
  38. package/pages/_app.tsx +49 -0
  39. package/pages/api/event/[name]/download.js +25 -0
  40. package/pages/events/[name]/logs.tsx +170 -0
  41. package/pages/events/[name]/v/[version].tsx +19 -0
  42. package/pages/events/[name].tsx +139 -0
  43. package/pages/events.tsx +227 -0
  44. package/pages/index.tsx +47 -0
  45. package/pages/overview.tsx +80 -0
  46. package/pages/services/[name].tsx +102 -0
  47. package/pages/services.tsx +123 -0
  48. package/pages/users/[id].tsx +83 -0
  49. package/postcss.config.js +6 -0
  50. package/public/favicon.ico +0 -0
  51. package/public/logo-random.svg +114 -0
  52. package/public/logo.svg +44 -0
  53. package/public/opengraph.png +0 -0
  54. package/scripts/__tests__/assets/eventcatalog.config.js +33 -0
  55. package/scripts/__tests__/generate.spec.ts +39 -0
  56. package/scripts/generate.js +28 -0
  57. package/styles/Home.module.css +116 -0
  58. package/styles/globals.css +48 -0
  59. package/tailwind.config.js +16 -0
  60. package/tsconfig.json +38 -0
  61. package/types/index.ts +7 -0
  62. package/utils/random-bg.ts +13 -0
package/lib/events.ts ADDED
@@ -0,0 +1,221 @@
1
+ import fs from 'fs';
2
+ import path from 'path';
3
+ import { serialize } from 'next-mdx-remote/serialize';
4
+ import type { Service, Event } from '@eventcatalog/types';
5
+ import compareVersions from 'compare-versions';
6
+ import * as Diff from 'diff';
7
+ import { MarkdownFile } from '@/types/index';
8
+
9
+ import { extentionToLanguageMap } from './file-reader';
10
+
11
+ import { getLastModifiedDateOfFile, getSchemaFromDir, readMarkdownFile } from '@/lib/file-reader';
12
+
13
+ const parseEventFrontMatterIntoEvent = (eventFrontMatter: any): Event => {
14
+ const { name, version, summary, producers = [], consumers = [], owners = [] } = eventFrontMatter;
15
+ return { name, version, summary, producers, consumers, owners };
16
+ };
17
+
18
+ const versionsForEvents = (pathToEvent) => {
19
+ const versionsDir = path.join(pathToEvent, 'versioned');
20
+
21
+ if (fs.existsSync(versionsDir)) {
22
+ const files = fs.readdirSync(versionsDir);
23
+ return files.sort(compareVersions).reverse();
24
+ }
25
+
26
+ return [];
27
+ };
28
+
29
+ export const getLogsForEvent = (eventName) => {
30
+ const eventsDir = path.join(process.env.PROJECT_DIR, 'events');
31
+ const historicVersions = versionsForEvents(path.join(eventsDir, eventName));
32
+
33
+ const allVersions = historicVersions.map((version) => ({
34
+ version,
35
+ pathToDir: path.join(eventsDir, eventName, 'versioned', version),
36
+ }));
37
+
38
+ // Get the latest version
39
+ const { data: { version: latestVersion } = {} } = readMarkdownFile(
40
+ path.join(eventsDir, eventName, 'index.md')
41
+ );
42
+
43
+ // Add the current version to the list
44
+ allVersions.unshift({ version: latestVersion, pathToDir: path.join(eventsDir, eventName) });
45
+
46
+ const versions = allVersions.reduce((diffs, versionData, index) => {
47
+ const hasVersionToCompareToo = !!allVersions[index + 1];
48
+ const previousVersionData = allVersions[index + 1];
49
+
50
+ // Check if both files have the schema to compare against...
51
+ if (hasVersionToCompareToo) {
52
+ const { version, pathToDir } = versionData;
53
+ const { version: previousVersion, pathToDir: previousVersionPathToDir } = previousVersionData;
54
+ const schema = getSchemaFromDir(pathToDir);
55
+ const previousSchema = getSchemaFromDir(previousVersionPathToDir);
56
+ let changelog = null;
57
+
58
+ try {
59
+ const { content } = readMarkdownFile(path.join(pathToDir, 'changelog.md'));
60
+ changelog = content;
61
+ } catch (error) {
62
+ // nothing found it's OK.
63
+ console.log('No changelog found');
64
+ }
65
+
66
+ const comparision = {
67
+ versions: [previousVersion, version],
68
+ changelog: {
69
+ content: changelog,
70
+ },
71
+ value:
72
+ schema && previousSchema
73
+ ? Diff.createTwoFilesPatch(
74
+ `schema.${schema.extension} (${previousVersion})`,
75
+ `schema.${previousSchema.extension} (${version})`,
76
+ previousSchema.snippet,
77
+ schema.snippet
78
+ )
79
+ : null,
80
+ };
81
+
82
+ diffs.push(comparision);
83
+ }
84
+
85
+ return diffs;
86
+ }, []);
87
+
88
+ const parseChangeLogs = versions.map(async (version) => {
89
+ if (version.changelog.content) {
90
+ const mdxSource = await serialize(version.changelog.content);
91
+ return {
92
+ ...version,
93
+ changelog: {
94
+ ...version.changelog,
95
+ source: mdxSource,
96
+ },
97
+ };
98
+ }
99
+ return version;
100
+ });
101
+
102
+ const values = Promise.all(parseChangeLogs);
103
+
104
+ return values;
105
+ };
106
+
107
+ const getEventExamplesFromDir = (pathToExamples) => {
108
+ let examples;
109
+
110
+ // Get examples for directory
111
+ try {
112
+ const files = fs.readdirSync(pathToExamples);
113
+ examples = files.map((filename) => {
114
+ const content = fs.readFileSync(path.join(pathToExamples, filename), {
115
+ encoding: 'utf-8',
116
+ });
117
+
118
+ const extension = filename.split('.').pop();
119
+
120
+ return {
121
+ name: filename,
122
+ snippet: content,
123
+ langugage: extentionToLanguageMap[extension],
124
+ };
125
+ });
126
+ } catch (error) {
127
+ examples = [];
128
+ }
129
+
130
+ return examples;
131
+ };
132
+
133
+ export const getAllEvents = (): Event[] => {
134
+ const eventsDir = path.join(process.env.PROJECT_DIR, 'events');
135
+ const folders = fs.readdirSync(eventsDir);
136
+ return folders.map((folder) => {
137
+ const { data } = readMarkdownFile(path.join(eventsDir, folder, 'index.md'));
138
+ const historicVersions = versionsForEvents(path.join(eventsDir, folder));
139
+ return {
140
+ ...parseEventFrontMatterIntoEvent(data),
141
+ historicVersions,
142
+ };
143
+ });
144
+ };
145
+
146
+ export const getEventByName = async (
147
+ eventName: string,
148
+ version?: string
149
+ ): Promise<{ event: Event; markdown: MarkdownFile }> => {
150
+ const eventsDir = path.join(process.env.PROJECT_DIR, 'events');
151
+ const eventDirectory = path.join(eventsDir, eventName);
152
+ let versionDirectory = null;
153
+
154
+ if (version) {
155
+ versionDirectory = path.join(eventsDir, eventName, 'versioned', version);
156
+ }
157
+
158
+ const directoryToLoadForEvent = version ? versionDirectory : eventDirectory;
159
+
160
+ try {
161
+ const { data, content } = readMarkdownFile(path.join(directoryToLoadForEvent, `index.md`));
162
+ const event = parseEventFrontMatterIntoEvent(data);
163
+
164
+ const mdxSource = await serialize(content);
165
+
166
+ return {
167
+ event: {
168
+ ...event,
169
+ historicVersions: versionsForEvents(eventDirectory),
170
+ schema: getSchemaFromDir(directoryToLoadForEvent),
171
+ examples: getEventExamplesFromDir(path.join(directoryToLoadForEvent, `examples`)),
172
+ },
173
+ markdown: {
174
+ content,
175
+ source: mdxSource,
176
+ lastModifiedDate: getLastModifiedDateOfFile(path.join(directoryToLoadForEvent, `index.md`)),
177
+ },
178
+ };
179
+ } catch (error) {
180
+ console.log(error);
181
+ console.log('Failed to get event by name', eventName);
182
+ return Promise.reject();
183
+ }
184
+ };
185
+
186
+ export const getUniqueServicesNamesFromEvents = (events: Event[]) => {
187
+ const allConsumersAndProducers = events.reduce((domains, event) => {
188
+ const { consumers = [], producers = [] } = event;
189
+ return domains.concat(consumers).concat(producers);
190
+ }, []);
191
+
192
+ const data = allConsumersAndProducers.map((service) => service);
193
+
194
+ // @ts-ignore
195
+ return [...new Set(data)];
196
+ };
197
+
198
+ export const getAllEventsByOwnerId = async (ownerId) => {
199
+ const events = await getAllEvents();
200
+ return events.filter(({ owners = [] }) => owners.some((id) => id === ownerId));
201
+ };
202
+
203
+ export const getAllEventsThatHaveRelationshipWithService = (
204
+ service: Service,
205
+ events: Event[]
206
+ ): { publishes: Event[]; subscribes: Event[] } => {
207
+ const relationshipsBetweenEvents = events.reduce(
208
+ (data, event) => {
209
+ const serviceSubscribesToEvent = event.consumers.some((id) => id === service.id);
210
+ const servicePublishesEvent = event.producers.some((id) => id === service.id);
211
+
212
+ return {
213
+ publishes: servicePublishesEvent ? data.publishes.concat(event) : data.publishes,
214
+ subscribes: serviceSubscribesToEvent ? data.subscribes.concat(event) : data.subscribes,
215
+ };
216
+ },
217
+ { publishes: [], subscribes: [] }
218
+ );
219
+
220
+ return relationshipsBetweenEvents;
221
+ };
@@ -0,0 +1,52 @@
1
+ import fs from 'fs';
2
+ import path from 'path';
3
+ import type { Schema } from '@eventcatalog/types';
4
+ import matter from 'gray-matter';
5
+
6
+ // https://github.com/react-syntax-highlighter/react-syntax-highlighter/blob/master/AVAILABLE_LANGUAGES_HLJS.MD
7
+ export const extentionToLanguageMap = {
8
+ cs: 'csharp',
9
+ js: 'javascript',
10
+ json: 'json',
11
+ yml: 'yml',
12
+ java: 'java',
13
+ pb: 'protobuf',
14
+ proto: 'protobuf',
15
+ thrift: 'thrift',
16
+ };
17
+
18
+ export const readMarkdownFile = (pathToFile: string) => {
19
+ const file = fs.readFileSync(pathToFile, {
20
+ encoding: 'utf-8',
21
+ });
22
+ return matter(file);
23
+ };
24
+
25
+ export const getSchemaFromDir = (pathToSchemaDir: string): Schema => {
26
+ try {
27
+ const files = fs.readdirSync(pathToSchemaDir);
28
+
29
+ // See if any schemas are in there, ignoring extension
30
+ const schemaFileName = files.find((fileName) => fileName.includes('schema'));
31
+ if (!schemaFileName) throw new Error('No schema found');
32
+
33
+ const schemaFile = fs.readFileSync(path.join(pathToSchemaDir, schemaFileName), 'utf-8');
34
+ const extension = schemaFileName.split('.').pop();
35
+
36
+ return {
37
+ snippet: `${schemaFile}`,
38
+ language: extentionToLanguageMap[extension] || extension,
39
+ extension,
40
+ };
41
+ } catch (error) {
42
+ return null;
43
+ }
44
+ };
45
+
46
+ export const getLastModifiedDateOfFile = (filePath) => {
47
+ const stats = fs.statSync(filePath);
48
+ const lastModifiedDate = new Date(stats.mtime);
49
+ return `${lastModifiedDate.getFullYear()}/${
50
+ lastModifiedDate.getMonth() + 1
51
+ }/${lastModifiedDate.getDate()}`;
52
+ };
package/lib/graphs.ts ADDED
@@ -0,0 +1,33 @@
1
+ import type { Event, Service } from '@eventcatalog/types';
2
+
3
+ const buildMermaid = (centerNode, leftNodes, rightNodes, rootNodeColor) => {
4
+ // mermaid does not work with spaces in nodes
5
+ const removeSpacesInNames = (nodes) => nodes.map((node) => node.replace(/ /g, '_'));
6
+ const lNodes = removeSpacesInNames(leftNodes);
7
+ const rNodes = removeSpacesInNames(rightNodes);
8
+ const nodeValue = centerNode.replace(/ /g, '_');
9
+
10
+ return `flowchart LR
11
+ ${lNodes.map((node) => `${node}:::producer-->${nodeValue}:::event\n`).join('')}
12
+ classDef event stroke:${rootNodeColor},stroke-width: 4px;
13
+ classDef producer stroke:#75d7b6,stroke-width: 2px;
14
+ classDef consumer stroke:#818cf8,stroke-width: 2px;
15
+ ${rNodes.map((node) => `${nodeValue}:::event-->${node}:::consumer\n`).join('')}
16
+ `;
17
+ };
18
+
19
+ export const buildMermaidFlowChartForEvent = (
20
+ { name: eventName, producers, consumers }: Event,
21
+ rootNodeColor = '#2563eb'
22
+ ) => buildMermaid(eventName, producers, consumers, rootNodeColor);
23
+
24
+ export const buildMermaidFlowChartForService = (
25
+ { publishes, subscribes, name: serviceName }: Service,
26
+ rootNodeColor = '#2563eb'
27
+ ) =>
28
+ buildMermaid(
29
+ serviceName,
30
+ subscribes.map((s) => s.name),
31
+ publishes.map((p) => p.name),
32
+ rootNodeColor
33
+ );
@@ -0,0 +1,72 @@
1
+ import fs from 'fs';
2
+ import path from 'path';
3
+ import { serialize } from 'next-mdx-remote/serialize';
4
+ import { Service } from '@eventcatalog/types';
5
+ import { readMarkdownFile, getLastModifiedDateOfFile } from '@/lib/file-reader';
6
+ import { MarkdownFile } from '../types/index';
7
+
8
+ import { getAllEvents, getAllEventsThatHaveRelationshipWithService } from '@/lib/events';
9
+
10
+ const buildService = (eventFrontMatter: any): Service => {
11
+ const { id, name, summary, owners = [], repository = {}, tags = [] } = eventFrontMatter;
12
+ return { id, name, summary, owners, repository, tags };
13
+ };
14
+
15
+ export const getAllServices = (): Service[] => {
16
+ const servicesDir = path.join(process.env.PROJECT_DIR, 'services');
17
+
18
+ const folders = fs.readdirSync(servicesDir);
19
+ const services = folders.map((folder) =>
20
+ readMarkdownFile(path.join(servicesDir, folder, 'index.md'))
21
+ );
22
+ const events = getAllEvents();
23
+
24
+ const parsedServices = services.map((frontMatter) => buildService(frontMatter.data));
25
+
26
+ // @ts-ignore
27
+ return parsedServices.map((service) => ({
28
+ ...service,
29
+ ...getAllEventsThatHaveRelationshipWithService(service, events),
30
+ }));
31
+ };
32
+
33
+ export const getAllServicesByOwnerId = async (ownerId): Promise<Service[]> => {
34
+ const services = await getAllServices();
35
+ const servicesOwnedByUser = services.filter((service) =>
36
+ service.owners.some((id) => id === ownerId)
37
+ );
38
+ return servicesOwnedByUser.map((service) => ({
39
+ ...service,
40
+ }));
41
+ };
42
+
43
+ export const getServiceByName = async (
44
+ serviceName
45
+ ): Promise<{ service: Service; markdown: MarkdownFile }> => {
46
+ try {
47
+ const servicesDir = path.join(process.env.PROJECT_DIR, 'services');
48
+ const serviceDirectory = path.join(servicesDir, serviceName);
49
+ const { data, content } = readMarkdownFile(path.join(serviceDirectory, `index.md`));
50
+ const service = buildService(data);
51
+
52
+ const events = getAllEvents();
53
+
54
+ const mdxSource = await serialize(content);
55
+
56
+ return {
57
+ // @ts-ignore
58
+ service: {
59
+ ...service,
60
+ ...getAllEventsThatHaveRelationshipWithService(service, events),
61
+ },
62
+ markdown: {
63
+ content,
64
+ lastModifiedDate: getLastModifiedDateOfFile(path.join(serviceDirectory, `index.md`)),
65
+ source: mdxSource,
66
+ },
67
+ };
68
+ } catch (error) {
69
+ console.log('Failed to get service by name', serviceName);
70
+ return Promise.reject();
71
+ }
72
+ };
package/next-env.d.ts ADDED
@@ -0,0 +1,5 @@
1
+ /// <reference types="next" />
2
+ /// <reference types="next/image-types/global" />
3
+
4
+ // NOTE: This file should not be edited
5
+ // see https://nextjs.org/docs/basic-features/typescript for more information.
package/next.config.js ADDED
@@ -0,0 +1,3 @@
1
+ module.exports = {
2
+ reactStrictMode: true,
3
+ };
package/package.json ADDED
@@ -0,0 +1,52 @@
1
+ {
2
+ "name": "@eventcatalog/core",
3
+ "version": "0.0.0",
4
+ "publishConfig": {
5
+ "access": "public"
6
+ },
7
+ "bin": {
8
+ "eventcatalog": "bin/eventcatalog.js"
9
+ },
10
+ "scripts": {
11
+ "dev": "next dev",
12
+ "generate": "node scripts/generate.js",
13
+ "build": "next build",
14
+ "start": "next start",
15
+ "lint": "next lint"
16
+ },
17
+ "dependencies": {
18
+ "@headlessui/react": "^1.4.2",
19
+ "@heroicons/react": "^1.0.5",
20
+ "@mdx-js/react": "^1.6.22",
21
+ "@tailwindcss/forms": "^0.3.4",
22
+ "@tailwindcss/line-clamp": "^0.2.2",
23
+ "@tailwindcss/typography": "^0.4.1",
24
+ "compare-versions": "^4.1.2",
25
+ "copy-text-to-clipboard": "^3.0.1",
26
+ "diff": "^5.0.0",
27
+ "diff2html": "^3.4.13",
28
+ "fs-extra": "^10.0.0",
29
+ "gray-matter": "^4.0.3",
30
+ "js-file-download": "^0.4.12",
31
+ "mermaid": "^8.13.4",
32
+ "next": "^12.0.5",
33
+ "next-mdx-remote": "^3.0.8",
34
+ "react": "17.0.2",
35
+ "react-dom": "17.0.2",
36
+ "react-force-graph-3d": "^1.21.10",
37
+ "react-syntax-highlighter": "^15.4.5"
38
+ },
39
+ "devDependencies": {
40
+ "@eventcatalog/types": "0.0.0",
41
+ "@types/react": "^17.0.36",
42
+ "autoprefixer": "^10.4.0",
43
+ "commander": "^8.3.0",
44
+ "eslint": "7.32.0",
45
+ "eslint-config-next": "12.0.4",
46
+ "postcss": "^8.3.11",
47
+ "tailwindcss": "^2.2.19",
48
+ "trim": "0.0.3",
49
+ "typescript": "^4.4.4"
50
+ },
51
+ "gitHead": "5f82ce7f267e9b3ec771bbd3cc81359ed0a8fd18"
52
+ }
package/pages/_app.tsx ADDED
@@ -0,0 +1,49 @@
1
+ import '../styles/globals.css';
2
+ import { AppProps } from 'next/app';
3
+ import Head from 'next/head';
4
+ import Header from '@/components/Header';
5
+ import Footer from '@/components/Footer';
6
+ import { EventCatalogContextProvider } from '@/hooks/EventCatalog';
7
+
8
+ export default ({ Component, pageProps }: AppProps) => (
9
+ <EventCatalogContextProvider>
10
+ <Head>
11
+ <title>EventCatalog</title>
12
+ <script src="//unpkg.com/three" />
13
+ <script src="//unpkg.com/three/examples/js/renderers/CSS2DRenderer.js" />
14
+
15
+ <meta
16
+ name="description"
17
+ content="An open source project to Discover, Explore and Document your Event Driven Architectures."
18
+ />
19
+ <meta property="og:url" content="https://demo.eventcatalog.dev/" />
20
+ <meta property="og:type" content="website" />
21
+ <meta
22
+ property="og:title"
23
+ content="EventCatalog | Discover, Explore and Document your Event Driven Architectures."
24
+ />
25
+ <meta
26
+ property="og:description"
27
+ content="An open source tool powered by markdown to document your Event Driven Architecture."
28
+ />
29
+ <meta property="og:image" content="https://demo.eventcatalog.dev/opengraph.png" />
30
+ <meta
31
+ property="og:image:alt"
32
+ content="EventCatalog | Discover, Explore and Document your Event Driven Architectures."
33
+ />
34
+ <meta property="og:image:width" content="1200" />
35
+ <meta property="og:image:height" content="600" />
36
+ <meta property="og:locale" content="en-GB" />
37
+ <meta name="author" content="David Boyne" />
38
+
39
+ {/* Need to load this before any of the Html2Diff Code */}
40
+ <link
41
+ rel="stylesheet"
42
+ href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/9.13.1/styles/atom-one-light.min.css"
43
+ />
44
+ </Head>
45
+ <Header />
46
+ <Component {...pageProps} />
47
+ <Footer />
48
+ </EventCatalogContextProvider>
49
+ );
@@ -0,0 +1,25 @@
1
+ import fs from 'fs';
2
+ import path from 'path';
3
+
4
+ // eslint-disable-next-line func-names
5
+ export default function (req, res) {
6
+ const { name: eventName } = req.query;
7
+
8
+ const eventDir = path.join(process.env.PROJECT_DIR, 'events', eventName);
9
+
10
+ try {
11
+ // Find the schema file
12
+ const filesInEventDir = fs.readdirSync(eventDir);
13
+ const schemaFileName = filesInEventDir.find((fileName) => fileName.indexOf('schema.') > -1);
14
+
15
+ if (schemaFileName) {
16
+ const schemaFile = fs.readFileSync(path.join(eventDir, schemaFileName));
17
+ res.send(schemaFile).end();
18
+ } else {
19
+ res.status(404).end();
20
+ }
21
+ } catch (error) {
22
+ console.log(error);
23
+ res.status(404).end();
24
+ }
25
+ }
@@ -0,0 +1,170 @@
1
+ import { MDXRemote } from 'next-mdx-remote';
2
+ import React, { useEffect } from 'react';
3
+ import Link from 'next/link';
4
+
5
+ import * as Diff2Html from 'diff2html/lib/ui/js/diff2html-ui-slim';
6
+ import 'diff2html/bundles/css/diff2html.min.css';
7
+
8
+ import { CodeIcon } from '@heroicons/react/solid';
9
+ import BreadCrumbs from '@/components/BreadCrumbs';
10
+
11
+ import { getLogsForEvent, getEventByName } from '@/lib/events';
12
+
13
+ function classNames(...classes) {
14
+ return classes.filter(Boolean).join(' ');
15
+ }
16
+
17
+ interface LogsProps {
18
+ name: string;
19
+ currentVersion: string;
20
+ changes: any;
21
+ }
22
+
23
+ function Logs({ changes, name: eventName, currentVersion }: LogsProps) {
24
+ const pages = [
25
+ { name: 'Events', href: '/events', current: false },
26
+ { name: eventName, href: `/events/${eventName}`, current: false },
27
+ { name: 'Logs', href: `/events/${eventName}/history`, current: true },
28
+ ];
29
+
30
+ useEffect(() => {
31
+ const configuration = {
32
+ drawFileList: false,
33
+ matching: 'lines',
34
+ highlight: true,
35
+ fileListToggle: false,
36
+ outputFormat: 'side-by-side',
37
+ };
38
+
39
+ changes.forEach((diff, index) => {
40
+ if (diff.value) {
41
+ const targetElement = document.getElementById(`code-diff-${index}`);
42
+
43
+ // @ts-ignore
44
+ const diff2htmlUi = new Diff2Html.Diff2HtmlUI(targetElement, diff.value, configuration);
45
+ diff2htmlUi.draw();
46
+ diff2htmlUi.highlightCode();
47
+ }
48
+ });
49
+ }, [changes]);
50
+
51
+ return (
52
+ <div className="flex relative min-h-screen">
53
+ <div className=" flex flex-col w-0 flex-1 ">
54
+ <main className="flex-1 ">
55
+ <div className="py-8 xl:py-10">
56
+ <div className="max-w-5xl mx-auto px-4 sm:px-6 lg:px-8 xl:max-w-7xl xl:grid xl:grid-cols-4">
57
+ <div className="xl:col-span-4 flex-col justify-between flex ">
58
+ <div className="mb-5 border-b border-gray-100 pb-4">
59
+ <BreadCrumbs pages={pages} />
60
+ </div>
61
+ <div>
62
+ <div>
63
+ <div className="border-b pb-4 flex justify-between mb-4">
64
+ <div className="space-y-2 w-full">
65
+ <h1 className="text-3xl font-bold text-gray-900 relative">EmailSent</h1>
66
+ </div>
67
+ </div>
68
+ </div>
69
+
70
+ {changes.length === 0 && (
71
+ <div className="text-gray-400 text-xl">No versions for Event found.</div>
72
+ )}
73
+
74
+ <div className="flow-root mb-20">
75
+ <ul className="">
76
+ {changes.map((event, eventIdx) => (
77
+ <li key={eventIdx} className="">
78
+ <div className="relative pb-8">
79
+ {eventIdx !== changes.length - 1 ? (
80
+ <span
81
+ className="absolute top-4 left-4 -ml-px h-full w-0.5 bg-gray-100"
82
+ aria-hidden="true"
83
+ />
84
+ ) : null}
85
+ <div className="relative flex space-x-3">
86
+ <div>
87
+ <span
88
+ className={classNames(
89
+ 'h-8 text-white text-xs w-8 rounded-full flex items-center justify-center ring-8 ring-white bg-blue-500'
90
+ )}
91
+ >
92
+ <CodeIcon className="h-4 w-4" />
93
+ {/* {event.versions[0]} */}
94
+ </span>
95
+ </div>
96
+ <div>
97
+ <div>
98
+ <p className="font-bold text-gray-800 text-xl">
99
+ Schema version update
100
+ {event.versions.map((version, index) => {
101
+ const linkHref =
102
+ version === currentVersion
103
+ ? `/events/${eventName}`
104
+ : `/events/${eventName}/v/${version}`;
105
+ return (
106
+ <Link key={version} href={linkHref}>
107
+ <a className="font-medium">
108
+ {index === 0 && ` from`}
109
+ <span className="text-blue-500 underline px-1">
110
+ {version}
111
+ {version === currentVersion ? '(latest)' : ''}
112
+ </span>
113
+ {index === 0 && `to`}
114
+ </a>
115
+ </Link>
116
+ );
117
+ })}
118
+ </p>
119
+ {event.changelog.source && (
120
+ <>
121
+ <h2 className="text-xl text-blue-500 font-bold mt-4 border-b border-gray-100 pb-2">
122
+ Changelog
123
+ </h2>
124
+ <div className="prose max-w-none mt-2">
125
+ <MDXRemote {...event.changelog.source} />
126
+ </div>
127
+ </>
128
+ )}
129
+ {!event.changelog.source && (
130
+ <h2 className="text-base text-gray-300 font-bold mt-4">
131
+ No changelog file found.
132
+ </h2>
133
+ )}
134
+ </div>
135
+ <div className="text-right text-sm text-gray-500 py-4">
136
+ <div id={`code-diff-${eventIdx}`} />
137
+ </div>
138
+ </div>
139
+ </div>
140
+ </div>
141
+ </li>
142
+ ))}
143
+ </ul>
144
+ </div>
145
+ </div>
146
+ </div>
147
+ </div>
148
+ </div>
149
+ </main>
150
+ </div>
151
+ </div>
152
+ );
153
+ }
154
+
155
+ export const getServerSideProps = async ({ query }) => {
156
+ const { name: eventName } = query;
157
+
158
+ const history = await getLogsForEvent(eventName);
159
+ const { event: { version } = {} } = await getEventByName(eventName);
160
+
161
+ return {
162
+ props: {
163
+ changes: history,
164
+ name: eventName,
165
+ currentVersion: version,
166
+ },
167
+ };
168
+ };
169
+
170
+ export default Logs;