Skip to content

Feature yaml editor #76

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 9 commits into from
Aug 16, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
18 changes: 0 additions & 18 deletions docs/configure-coderabbit.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,24 +39,6 @@ You can add a `.coderabbit.yaml` configuration file to the root of your
repositories. Below is a sample YAML file that can be used as a starting point
and changed as needed:

```yaml
# yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json
language: "en-US"
early_access: false
reviews:
profile: "chill"
request_changes_workflow: false
high_level_summary: true
poem: true
review_status: true
collapse_walkthrough: false
auto_review:
enabled: true
drafts: false
chat:
auto_reply: true
```
Write your configuration file in the below editor to validate:

```mdx-code-block
Expand Down
103 changes: 0 additions & 103 deletions src/components/YamlEditor/YamlEditor.jsx

This file was deleted.

160 changes: 160 additions & 0 deletions src/components/YamlEditor/YamlEditor.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
import React, { useState, useEffect } from "react";

import AceEditor from "react-ace";
import "ace-builds/src-noconflict/theme-github";
import "ace-builds/src-noconflict/ext-language_tools";

import "ace-builds/webpack-resolver";
import "ace-builds/src-noconflict/mode-yaml";

import jsYaml from "js-yaml";

import Ajv from "ajv";
const ajv = new Ajv({ allErrors: true });

import Schema from "../../../static/schema/schema.v2.json";

const validate = ajv.compile(Schema.definitions.schema);
const initialValue = `# yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json
language: "en-US"
early_access: false
reviews:
profile: "chill"
request_changes_workflow: false
high_level_summary: true
poem: true
review_status: true
collapse_walkthrough: false
auto_review:
enabled: true
drafts: false
chat:
auto_reply: true

`;
export default function YamlEditor() {
const [value, setValue] = useState(initialValue);
const [annotations, setAnnotations] = useState([]);

useEffect(() => {
setValue(initialValue);
validateAndSetAnnotations(initialValue);
}, []);

function validateAndSetAnnotations(yaml) {
try {
const doc = jsYaml.load(yaml, { strict: true });
const isValid = validate(doc);

if (!isValid && validate.errors) {
setAnnotations(
validate.errors.map((err) => {
const instancePathArr = err?.instancePath?.split("/");
const key =
instancePathArr && instancePathArr[instancePathArr.length - 1];
return {
row: err.instancePath ? getLineNumber(yaml, err.instancePath) : 0,
column: 0,
text: `${key}: ${err.message} ${
err?.params?.allowedValues
? `Allowed values: ${err.params.allowedValues.join(", ")}`
: ""
}`,
type: "error",
};
})
);
} else {
setAnnotations([]);
}
} catch (err) {
const instancePathArr = err?.instancePath?.split("/");
const key =
instancePathArr && instancePathArr[instancePathArr.length - 1];

setAnnotations([
{
row: err.instancePath ? getLineNumber(yaml, err.instancePath) : 0,
column: 0,
text:
`${key}: ${err.message} ${
err?.params?.allowedValues
? `Allowed values: ${err.params.allowedValues.join(", ")}`
: ""
}` || "YAML parsing error",
type: "error",
},
]);
}
}

function getLineNumber(yaml, instancePath) {
const lines = yaml.split("\n");
const pathParts = instancePath.split("/").filter(Boolean);
let currentObj = jsYaml.load(yaml);
let lineNumber = 0;

const lastPathPart = pathParts[pathParts.length - 1];
const lastPathPartIndex = pathParts.length - 1;

for (let i = 0; i < lines.length; i++) {
if (lines[i].trim().startsWith(pathParts[0] + ":")) {
// Found the top-level field
lineNumber = i;
currentObj = currentObj[pathParts[0]];

for (let j = 1; j < lastPathPartIndex; j++) {
// Go through the nested fields
for (let k = lineNumber + 1; k < lines.length; k++) {
if (lines[k].trim().startsWith(pathParts[j] + ":")) {
lineNumber = k;
currentObj = currentObj[pathParts[j]];
break;
}
}
}

// look for the last path part with array syntax as well as object syntax
for (let l = lineNumber + 1; l < lines.length; l++) {
if (lines[l].trim().startsWith(`- ${lastPathPart}:`)) {
lineNumber = l;
break;
} else if (lines[l].trim().startsWith(lastPathPart + ":")) {
lineNumber = l;
break;
}
}
break;
}
}

return lineNumber;
}

function onChange(newValue) {
setValue(newValue);
validateAndSetAnnotations(newValue);
}

return (
<div className="m4">
<AceEditor
mode="yaml"
theme="github"
onChange={onChange}
value={value}
name="yaml-editor"
editorProps={{ $blockScrolling: true }}
setOptions={{
useWorker: false,
showPrintMargin: false,
showGutter: true,
}}
annotations={annotations}
width="100%"
height="400px"
/>
<br />
</div>
);
}