How Tracetest developed a code editor for adding selectors. Covers the business requirements and React implementation with Code Mirror editor & Lezer parser.
In this post, I'll showcase how the Tracetest team built a custom front-end non-simple code editor based on React. It's used for the advanced query language to target spans across an OpenTelemetry Trace.
## Introduction to the Advanced Selector Query Language
[Tracetest](https://tracetest.io/) allows users to create assertions targeting specific spans from a distributed trace simply and reliably.
A distributed trace can be composed by N number of spans that, can be nested, repeated, or split into multiple queue producer/consumer sections.
Having a simple way to target the spans that matter for your assertions is instrumental to have good and reliable tests that will help you sleep at night.
To achieve this, Tracetest uses a [custom span selector language](https://docs.tracetest.io/concepts/selectors/) to target spans. It inherits part of its syntax highlighting from CSS by using attribute matches, pseudo selectors, and child-parent operators.
This query language enables users to create the selectors the Tracetest system uses to apply the different assertions. Each section of the selector is composed of the following tokens:
* **Span Wrapper.** To start describing a query, a span wrapper is the first thing that needs to be added.
* Example: `span`.
* **Attribute Matchers.** Inside the span wrapper, a list of expressions can be added to narrow down the specific span that the user wants to match.
* Example: `name="get request" tracetest.span.type = "database"`.
* **Pseudo Selectors.** Sometimes you’ll need to be more specific around what span to choose from a collection and a pseudo selector similar to CSS can help with that.
* Example: `span:first`.
* Span Operators. Currently, the query language we’ve built supports the child-parent (ancestor) operator and Or operator. You can write and combine both to match multiple spans or select one that is a child of a specific one.
* Example: `span[name="get request"] span[tracetest.span.type = "database"], span[http.status_code contains "200"]`
> *Note: For more information about advanced selectors and the query language, visit the Tracetest docs.*
### We Needed a Code Editor in Our UI
There are two main ways to use Tracetest.
The second is by using the UI to create tests, execute requests and create assertions/checks. Tests created directly in YAML can be loaded into the UI and vice versa.
When using the YAML text files, creating advanced queries is pretty straightforward as you can define the different selectors as text to trigger the process.
The complexity comes when trying to implement the same level of functionality the language provides in the UI as there can be many branches, nested selectors, operators, etc.
The initial UI implementation only supported a single span wrapper with its attribute expressions and a separated input for pseudo selectors.
Another important issue is that if a user creates a selector that uses any of the language features that are not supported by the UI, then, when viewing it in the UI, the selector won't match the original text version. Trying to edit it will break the selector in most of the cases.
## Creating the Non-simple Code Editor in React
Users needed the ability to build complex queries using advanced query methods. With that in mind, we started thinking about the key features that we wanted to implement with the advanced editor. They are:
1. Syntax Highlighting
3. Lint and Error Prompt
In order to achieve this, we started researching potential solutions and found [Code Mirror](https://codemirror.net/).
And that’s just what we needed in order to accomplish the key features - to come up with our own parser. For that, we used [Lezer](https://lezer.codemirror.net/docs/), which is a tool to create custom grammar rules by specifying the different tokens and expressions.
As you learned at the beginning of this post, we defined the query language tokens and, based on that, we started to mix them to create our expressions.
Once we had the grammar ready, we could finalize the language setup using Typescript.
Next, we had to come up with a way to support Autocomplete and Linting.
The Code Mirror framework includes two separate packages to handle Autocomplete and Linting, where you can add the different rules that, combined with the parser, can enable these features.
We created two custom hooks to handle this functionality that can be found [here](https://github.com/kubeshop/tracetest/tree/main/web/src/components/AdvancedEditor/hooks).
When complete, the editor React component looked like this:
## Final Code Editor Look, Feel and Functionality
From the UI perspective, this was the final result:
The video showcases the final version of the advance editor. It includes a demo of the main features and how the React application switches the state of the Diagram when updating the code within the editor.
It also shows the invalid query state error message which validates if the input is valid before triggering the request to the back end.
At Tracetest, we always try to provide you the best user experience. No matter the platform (UI/CLI) we want to ensure you have the tools to interact with the system in an easy way. That's why we also released a managed version of Tracetest in the cloud for you to get started even faster!
Having said that, if you have any comments or suggestions feel free to join our [Slack Community](https://dub.sh/tracetest-community) - we are always looking for feedback that can help improve Tracetest in any way.
Using OpenTelemetry tracing (you should be!) and want to give Tracetest a try? Download [Tracetest Core](https://github.com/kubeshop/tracetest) from GitHub (MIT Licensed) and experience the latest in trace-based testing. Or, sign up to Tracetest for a quick start!
Learn how to create setup and teardown of trace-based tests with Test Suites, allowing for codeless setup to chain together several tests into one comprehensive flow!