Skip to content
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
1 change: 1 addition & 0 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,7 @@ updates:
- /backend/python/pydevlake
- /backend/python/plugins/azuredevops
- /backend/python/plugins/redmine
- /backend/python/plugins/zephyr
- /backend/python/test/fakeplugin
- /grafana/scripts
schedule:
Expand Down
11 changes: 11 additions & 0 deletions backend/core/models/domainlayer/qa/qa_project.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,11 @@ package qa

import (
"github.com/apache/devlake/core/models/domainlayer"
plugin "github.com/apache/devlake/core/plugin"
)

var _ plugin.Scope = (*QaProject)(nil)

// QaProject represents a QA project in the domain layer
type QaProject struct {
domainlayer.DomainEntityExtended
Expand All @@ -30,3 +33,11 @@ type QaProject struct {
func (QaProject) TableName() string {
return "qa_projects"
}

func (r *QaProject) ScopeId() string {
return r.Id
}

func (r *QaProject) ScopeName() string {
return r.Name
}
50 changes: 50 additions & 0 deletions backend/python/plugins/zephyr/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
<!--
Licensed to the Apache Software Foundation (ASF) under one or more
contributor license agreements. See the NOTICE file distributed with
this work for additional information regarding copyright ownership.
The ASF licenses this file to You under the Apache License, Version 2.0
(the "License"); you may not use this file except in compliance with
the License. You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
# Zephyr

Apache DevLake plugin for ingesting data from [Zephyr Scale Cloud](https://smartbear.com/product/zephyr/) through the [REST API](https://support.smartbear.com/zephyr-scale-cloud/docs/en/rest-api/zephyr-scale-cloud-rest-api.html). Each DevLake scope is a Jira project.

Test cases and executions are normalized into DevLake's QA domain. Folders, cycles, labels, and issue links stay in the tool layer for the Grafana dashboard.

## Collected data

| Zephyr resource | Raw table | Tool table | Domain tables |
| --- | --- | --- | --- |
| Test cases | `_raw_zephyr_testcases` | `_tool_zephyr_testcases` | `qa_test_cases` |
| Test executions | `_raw_zephyr_testexecutions` | `_tool_zephyr_testexecutions` | `qa_test_case_executions` |
| Test cycles | `_raw_zephyr_testcycles` | `_tool_zephyr_testcycles` | — |
| Folders | `_raw_zephyr_folders` | `_tool_zephyr_folders` | — |
| Test case labels | `_raw_zephyr_testcaselabels` | `_tool_zephyr_testcaselabels` | — |
| Test case issue links | `_raw_zephyr_testcaseissuelinks` | `_tool_zephyr_testcaseissuelinks` | — |
| Test execution issue links | `_raw_zephyr_testexecutionissuelinks` | `_tool_zephyr_testexecutionissuelinks` | — |
| Projects (scope) | — | `_tool_zephyr_projects` | `qa_projects` |

Custom field values stay on `custom_fields_json`. The scope config names the custom field used as the test case type. The default name is `Test Type`.

A full sync applies the project sync policy `timeAfter` as a created-on filter for test cases, a start filter for cycles, and an actual-end filter for executions.

## Configuration

1. Create a Zephyr Scale API access token in Jira under **Profile → Zephyr Scale API Access Tokens**.
2. In Config UI, add a Zephyr connection with the API base URL and that token. The US default is `https://api.zephyrscale.smartbear.com/v2`. The EU base is `https://eu.api.zephyrscale.smartbear.com/v2`.
3. Select Jira projects. Set the test type custom field name when it is not `Test Type`.

Pass and Passed executions map to `SUCCESS`. Fail, Failed, and Blocked map to `FAILED`. In Progress maps to `IN_PROGRESS`. Any other status maps to `PENDING`.

## Dashboard

After a blueprint run, open the Zephyr dashboard in Grafana. MySQL and PostgreSQL definitions are in `grafana/dashboards/mysql/Zephyr.json` and `grafana/dashboards/postgresql/Zephyr.json`.
20 changes: 20 additions & 0 deletions backend/python/plugins/zephyr/build.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
#!/bin/sh
#
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
# The ASF licenses this file to You under the Apache License, Version 2.0
# (the "License"); you may not use this file except in compliance with
# the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#

cd "$(dirname "$0")"
sh ../../uv.sh sync .
23 changes: 23 additions & 0 deletions backend/python/plugins/zephyr/conftest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
# The ASF licenses this file to You under the Apache License, Version 2.0
# (the "License"); you may not use this file except in compliance with
# the License. You may obtain a copy of the License at

# http://www.apache.org/licenses/LICENSE-2.0

# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

import inspect


def pytest_pycollect_makeitem(collector, name, obj):
# Streams and models are named Test* and are imported by the real tests.
# Pytest would otherwise try to collect them and warn about their constructors.
if inspect.isclass(obj) and not str(getattr(obj, "__module__", "")).startswith("tests"):
return []
38 changes: 38 additions & 0 deletions backend/python/plugins/zephyr/pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
# The ASF licenses this file to You under the Apache License, Version 2.0
# (the "License"); you may not use this file except in compliance with
# the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

[tool.poetry]
name = "zephyr"
version = "0.1.0"
description = "DevLake plugin for Zephyr Cloud"
authors = ["Roger Abelenda <3179183+rabelenda@users.noreply.github.com>"]
readme = "README.md"

[tool.poetry.dependencies]
python = ">=3.11,<3.12"
pydevlake = { path = "../../pydevlake", develop = true }

[tool.poetry.group.dev.dependencies]
pytest = "^9.1.1"

[tool.pytest.ini_options]
testpaths = ["tests"]
filterwarnings = [
"ignore:This declarative base already contains:sqlalchemy.exc.SAWarning",
]

[build-system]
requires = ["poetry-core"]
build-backend = "poetry.core.masonry.api"
20 changes: 20 additions & 0 deletions backend/python/plugins/zephyr/run.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
#!/bin/sh
#
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
# The ASF licenses this file to You under the Apache License, Version 2.0
# (the "License"); you may not use this file except in compliance with
# the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#

cd "$(dirname "$0")"
sh ../../uv.sh python . zephyr/main.py "$@"
58 changes: 58 additions & 0 deletions backend/python/plugins/zephyr/tests/api_test.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
# The ASF licenses this file to You under the Apache License, Version 2.0
# (the "License"); you may not use this file except in compliance with
# the License. You may obtain a copy of the License at

# http://www.apache.org/licenses/LICENSE-2.0

# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

from unittest.mock import MagicMock, patch

from zephyr.api import ZephyrAPI
from zephyr.models import Connection


def _connection():
return Connection(
id=1,
name='test',
endpoint='https://api.zephyrscale.smartbear.com/v2',
token='token',
)


def test_iter_nextgen_paginates():
api = ZephyrAPI(_connection())
page1 = MagicMock()
page1.json = {
'values': [{'id': 1}],
'nextStartAtId': 10,
}
page2 = MagicMock()
page2.json = {
'values': [{'id': 2}],
'nextStartAtId': 10,
}
with patch.object(api, 'get', side_effect=[page1, page2]) as get_mock:
items = list(api._iter_nextgen('testcases/nextgen', projectKey='ABC'))
assert [item['id'] for item in items] == [1, 2]
assert get_mock.call_count == 2
second_call = get_mock.call_args_list[1]
assert second_call.kwargs['startAtId'] == 10


def test_project_lookups_index_by_id():
api = ZephyrAPI(_connection())
with patch.object(api, 'folders', return_value=[{'id': 5, 'name': 'Folder A'}]), patch.object(
api, 'priorities', return_value=[]
), patch.object(api, 'statuses', return_value=[]):
lookups = api.project_lookups('ABC')
assert lookups.folders[5]['name'] == 'Folder A'
assert lookups.components == {}
Loading
Loading