feat: add Zabbix 7.4 documentation samples and importHostGroups MCP tool

This commit introduces a comprehensive set of GraphQL query and mutation samples based on the official Zabbix 7.4 API documentation, along with testing and automation improvements.

Changes:

- Documentation:

  - Added 21 GraphQL sample files in docs/queries/from_zabbix_docs/ covering various Zabbix API operations.

  - Updated docs/howtos/cookbook.md with a new recipe for executing these documentation samples.

- AI & MCP:

  - Added mcp/operations/importHostGroups.graphql to enable host group import via MCP tools.

- Testing:

  - Added src/test/zabbix_docs_samples.test.ts to automatically validate all documentation samples against the GraphQL schema.
This commit is contained in:
Andreas Hilbig 2026-01-31 10:52:56 +01:00
parent 9a79fc8e4c
commit b56255ffaa
24 changed files with 626 additions and 0 deletions

View file

@ -12,6 +12,63 @@ To generate a test case from a recipe:
--- ---
## 🍳 Recipe: Executing Zabbix 7.4 Documentation Samples
This recipe shows how to execute standard Zabbix 7.4 API examples using their GraphQL equivalents. These samples are directly derived from the official Zabbix documentation and adapted for use with this GraphQL API.
### 📋 Prerequisites
- Zabbix GraphQL API is running.
- You have a valid Zabbix user account and are logged in (or have an auth token).
### 🛠️ Step 1: Browse Available Samples
All samples derived from the Zabbix 7.4 documentation are stored in the following directory:
- `docs/queries/from_zabbix_docs/`
Each `.graphql` file in this directory contains a reference link to the original Zabbix documentation source.
### ⚙️ Step 2: Extract Query and Variables
Each sample file in `docs/queries/from_zabbix_docs/` is structured to include both the GraphQL operation and a corresponding set of sample variables.
1. **Open the Sample**: Open the desired `.graphql` file (e.g. `createHost.graphql`).
2. **Copy the Query**: Copy the GraphQL code block found under the `### Query` header.
3. **Copy the Variables**: Copy the JSON code block found under the `### Variables` header.
### 🚀 Step 3: Execution/Action
Choose a sample and execute it against the GraphQL endpoint using your preferred client (like Apollo Studio, GraphiQL, or Postman).
- **Configure the Request**:
- Paste the **Query** into the operation window.
- Paste the **Variables** JSON into the variables window.
- **Run the Operation**: Click "Execute" or "Play".
#### Example: Creating a Host
Using the content from `docs/queries/from_zabbix_docs/createHost.graphql`:
**Query**:
```graphql
mutation CreateHost($host: String!, $hostgroupids: [Int!]!, $templateids: [Int!]!) {
createHost(host: $host, hostgroupids: $hostgroupids, templateids: $templateids) {
hostids
}
}
```
**Variables**:
```json
{
"host": "Linux server",
"hostgroupids": [50],
"templateids": [20045]
}
```
### ✅ Step 4: Verification
Compare the GraphQL response with the expected output described in the Zabbix documentation. Note that while the field names in GraphQL match the Zabbix API field names (e.g. `hostid`, `host`), the structure is simplified and nested objects (like `hostgroups`) can be queried directly without separate `selectXXX` parameters.
- *Reference*: [Zabbix 7.4 API Documentation](https://www.zabbix.com/documentation/7.4/en/manual/api)
---
## 🍳 Recipe: Extending Schema with a New Device Type ## 🍳 Recipe: Extending Schema with a New Device Type
This recipe shows how to add support for a new specialized device type without modifying the core API code. We will use the `DistanceTrackerDevice` as an example. This recipe shows how to add support for a new specialized device type without modifying the core API code. We will use the `DistanceTrackerDevice` as an example.

View file

@ -0,0 +1,27 @@
# Source: https://www.zabbix.com/documentation/7.4/en/manual/api/reference/host/get
### Query
```graphql
query GetAllDevices($name_pattern: String) {
allDevices(name_pattern: $name_pattern) {
hostid
host
name
deviceType
state {
operational {
temperature
voltage
signalstrength
}
}
}
}
```
### Variables
```json
{
"name_pattern": "Device%"
}
```

View file

@ -0,0 +1,18 @@
# Source: https://www.zabbix.com/documentation/7.4/en/manual/api/reference/hostgroup/get
### Query
```graphql
query GetAllHostGroups($search_name: String) {
allHostGroups(search_name: $search_name) {
groupid
name
}
}
```
### Variables
```json
{
"search_name": "Zabbix%"
}
```

View file

@ -0,0 +1,34 @@
# Source: https://www.zabbix.com/documentation/7.4/en/manual/api/reference/host/get
### Query
```graphql
query GetAllHosts($name_pattern: String, $groupids: [Int!]) {
allHosts(name_pattern: $name_pattern, groupids: $groupids) {
hostid
host
name
deviceType
hostgroups {
groupid
name
}
... on ZabbixHost {
tags
items {
itemid
name
key_
lastvalue
}
}
}
}
```
### Variables
```json
{
"name_pattern": "Linux%",
"groupids": [2]
}
```

View file

@ -0,0 +1,18 @@
# Source: https://www.zabbix.com/documentation/7.4/en/manual/api/reference/templategroup/get
### Query
```graphql
query GetAllTemplateGroups($name_pattern: String) {
allTemplateGroups(name_pattern: $name_pattern) {
groupid
name
}
}
```
### Variables
```json
{
"name_pattern": "Templates%"
}
```

View file

@ -0,0 +1,23 @@
# Source: https://www.zabbix.com/documentation/7.4/en/manual/api/reference/host/create
### Query
```graphql
mutation CreateHost($host: String!, $hostgroupids: [Int!]!, $templateids: [Int!]!) {
createHost(
host: $host,
hostgroupids: $hostgroupids,
templateids: $templateids
) {
hostids
}
}
```
### Variables
```json
{
"host": "Linux server",
"hostgroupids": [50],
"templateids": [20045]
}
```

View file

@ -0,0 +1,18 @@
# Source: https://www.zabbix.com/documentation/7.4/en/manual/api/reference/templategroup/delete
### Query
```graphql
mutation DeleteTemplateGroups($groupids: [Int!]) {
deleteTemplateGroups(groupids: $groupids) {
id
message
}
}
```
### Variables
```json
{
"groupids": [10, 11]
}
```

View file

@ -0,0 +1,18 @@
# Source: https://www.zabbix.com/documentation/7.4/en/manual/api/reference/template/delete
### Query
```graphql
mutation DeleteTemplates($templateids: [Int!]) {
deleteTemplates(templateids: $templateids) {
id
message
}
}
```
### Variables
```json
{
"templateids": [10001, 10002]
}
```

View file

@ -0,0 +1,24 @@
# Source: https://www.zabbix.com/documentation/7.4/en/manual/api/reference/history/get
### Query
```graphql
query ExportHostValueHistory($host_filter: [String!], $itemKey_filter: [String!], $limit: Int) {
exportHostValueHistory(
host_filter: $host_filter,
itemKey_filter: $itemKey_filter,
limit: $limit,
type: FLOAT
) {
result
}
}
```
### Variables
```json
{
"host_filter": ["Linux server"],
"itemKey_filter": ["system.cpu.load[all,avg1]"],
"limit": 10
}
```

View file

@ -0,0 +1,28 @@
# Source: https://www.zabbix.com/documentation/7.4/en/manual/api/reference/usergroup/get
### Query
```graphql
query ExportUserRights($name_pattern: String) {
exportUserRights(name_pattern: $name_pattern) {
userGroups {
usrgrpid
name
hostgroup_rights {
name
permission
}
}
userRoles {
roleid
name
}
}
}
```
### Variables
```json
{
"name_pattern": "Zabbix%"
}
```

View file

@ -0,0 +1,20 @@
# Source: https://www.zabbix.com/documentation/7.4/en/manual/api/reference/usergroup/get
### Query
```graphql
query HasPermissions($permissions: [PermissionRequest!]!) {
hasPermissions(permissions: $permissions)
}
```
### Variables
```json
{
"permissions": [
{
"objectName": "Read-Only-Access",
"permission": "READ"
}
]
}
```

View file

@ -0,0 +1,26 @@
# Source: https://www.zabbix.com/documentation/7.4/en/manual/api/reference/hostgroup/create
### Query
```graphql
mutation ImportHostGroups($hostGroups: [CreateHostGroup!]!) {
importHostGroups(hostGroups: $hostGroups) {
groupName
groupid
message
}
}
```
### Variables
```json
{
"hostGroups": [
{
"groupName": "New Host Group 1"
},
{
"groupName": "New Host Group 2"
}
]
}
```

View file

@ -0,0 +1,30 @@
# Source: https://www.zabbix.com/documentation/7.4/en/manual/api/reference/host/create
### Query
```graphql
mutation ImportHosts($hosts: [CreateHost!]!) {
importHosts(hosts: $hosts) {
hostid
deviceKey
message
}
}
```
### Variables
```json
{
"hosts": [
{
"deviceKey": "Host 1",
"deviceType": "GenericDevice",
"groupNames": ["Zabbix servers"]
},
{
"deviceKey": "Host 2",
"deviceType": "GenericDevice",
"groupNames": ["Zabbix servers"]
}
]
}
```

View file

@ -0,0 +1,23 @@
# Source: https://www.zabbix.com/documentation/7.4/en/manual/api/reference/templategroup/create
### Query
```graphql
mutation ImportTemplateGroups($templateGroups: [CreateTemplateGroup!]!) {
importTemplateGroups(templateGroups: $templateGroups) {
groupName
groupid
message
}
}
```
### Variables
```json
{
"templateGroups": [
{
"groupName": "New Template Group 1"
}
]
}
```

View file

@ -0,0 +1,32 @@
# Source: https://www.zabbix.com/documentation/7.4/en/manual/api/reference/template/create
### Query
```graphql
mutation ImportTemplates($templates: [CreateTemplate!]!) {
importTemplates(templates: $templates) {
templateid
host
message
}
}
```
### Variables
```json
{
"templates": [
{
"host": "New Template 1",
"groupNames": ["Templates/Operating systems"],
"items": [
{
"name": "Custom item",
"key": "custom.item",
"value_type": 3,
"history": "90d"
}
]
}
]
}
```

View file

@ -0,0 +1,45 @@
# Source: https://www.zabbix.com/documentation/7.4/en/manual/api/reference/usergroup/create
### Query
```graphql
mutation ImportUserRights($input: UserRightsInput!, $dryRun: Boolean!) {
importUserRights(input: $input, dryRun: $dryRun) {
userGroups {
id
name
message
}
userRoles {
id
name
message
}
}
}
```
### Variables
```json
{
"dryRun": true,
"input": {
"userGroups": [
{
"name": "New User Group",
"hostgroup_rights": [
{
"name": "Zabbix servers",
"permission": "READ"
}
]
}
],
"userRoles": [
{
"name": "New Role",
"type": 1
}
]
}
}
```

View file

@ -0,0 +1,19 @@
# Source: https://www.zabbix.com/documentation/7.4/en/manual/api/reference/host/get
### Query
```graphql
query GetLocations($name_pattern: String) {
locations(name_pattern: $name_pattern) {
name
latitude
longitude
}
}
```
### Variables
```json
{
"name_pattern": "Riga%"
}
```

View file

@ -0,0 +1,16 @@
# Source: https://www.zabbix.com/documentation/7.4/en/manual/api/reference/user/login
### Query
```graphql
query Login($username: String!, $password: String!) {
login(username: $username, password: $password)
}
```
### Variables
```json
{
"username": "Admin",
"password": "zabbix_password"
}
```

View file

@ -0,0 +1,13 @@
# Source: https://www.zabbix.com/documentation/7.4/en/manual/api/reference/user/logout
### Query
```graphql
query Logout {
logout
}
```
### Variables
```json
{}
```

View file

@ -0,0 +1,18 @@
# Source: https://www.zabbix.com/documentation/7.4/en/manual/api/reference/template/get
### Query
```graphql
query GetTemplates($name_pattern: String) {
templates(name_pattern: $name_pattern) {
templateid
name
}
}
```
### Variables
```json
{
"name_pattern": "Template OS%"
}
```

View file

@ -0,0 +1,18 @@
# Source: https://www.zabbix.com/documentation/7.4/en/manual/api/reference/user/get
### Query
```graphql
query GetUserPermissions($objectNames: [String!]) {
userPermissions(objectNames: $objectNames) {
objectName
permission
}
}
```
### Variables
```json
{
"objectNames": ["Read-Only-Access"]
}
```

View file

@ -0,0 +1,13 @@
# Source: https://www.zabbix.com/documentation/7.4/en/manual/api/reference/apiinfo/version
### Query
```graphql
query GetZabbixVersion {
zabbixVersion
}
```
### Variables
```json
{}
```

View file

@ -0,0 +1,7 @@
mutation ImportHostGroups($hostGroups: [CreateHostGroup!]!) {
importHostGroups(hostGroups: $hostGroups) {
groupName
groupid
message
}
}

View file

@ -0,0 +1,81 @@
import {ApolloServer} from '@apollo/server';
import {schema_loader} from '../api/schema.js';
import {readdirSync, readFileSync} from 'fs';
import {join} from 'path';
import {zabbixAPI} from '../datasources/zabbix-api.js';
// Mocking ZabbixAPI.post
jest.mock("../datasources/zabbix-api.js", () => ({
zabbixAPI: {
post: jest.fn(),
executeRequest: jest.fn(),
baseURL: 'http://localhost/zabbix',
getLocations: jest.fn(),
requestByPath: jest.fn()
}
}));
describe("Zabbix Docs Samples Integration Tests", () => {
let server: ApolloServer;
beforeAll(async () => {
const schema = await schema_loader();
server = new ApolloServer({
schema,
});
});
const samplesDir = join(process.cwd(), 'docs', 'queries', 'from_zabbix_docs');
const files = readdirSync(samplesDir).filter(f => f.endsWith('.graphql'));
test.each(files)("Sample %s should execute successfully", async (file) => {
const filePath = join(samplesDir, file);
const content = readFileSync(filePath, 'utf-8').replace(/\r\n/g, '\n');
const queryMatch = content.match(/```graphql\n([\s\S]*?)\n```/);
const variablesMatch = content.match(/```json\n([\s\S]*?)\n```/);
if (!queryMatch) {
throw new Error(`No graphql block found in ${file}`);
}
const query = queryMatch[1];
const variables = variablesMatch ? JSON.parse(variablesMatch[1]) : {};
// Setup a generic mock response based on the operation
(zabbixAPI.post as jest.Mock).mockImplementation((method: string) => {
if (method.includes('login')) return Promise.resolve("test-token");
if (method.includes('logout')) return Promise.resolve(true);
if (method.includes('get')) return Promise.resolve([]);
if (method.includes('create')) {
if (method.includes('host')) return Promise.resolve({hostids: ["10001"]});
if (method.includes('group')) return Promise.resolve({groupids: ["50"]});
if (method.includes('template')) return Promise.resolve({templateids: ["20001"]});
return Promise.resolve({ids: ["1"]});
}
if (method.includes('delete')) return Promise.resolve({ids: ["1"]});
if (method.includes('version')) return Promise.resolve("7.4.0");
return Promise.resolve({});
});
// Some operations use requestByPath or other methods
(zabbixAPI.requestByPath as jest.Mock).mockResolvedValue({ hostids: ["10001"], groupid: "50", templateid: "20001" });
const response = await server.executeOperation({
query: query,
variables: variables,
}, {
contextValue: { zabbixAuthToken: 'test-token', dataSources: { zabbixAPI: zabbixAPI } }
});
if (response.body.kind === 'single') {
const result = response.body.singleResult;
if (result.errors) {
console.error(`Errors in ${file}:`, JSON.stringify(result.errors, null, 2));
}
expect(result.errors).toBeUndefined();
} else {
throw new Error(`Unexpected response kind: ${response.body.kind}`);
}
});
});