Skip to content

[Integration] Add ScaleQuality integration - #3597

Open
eamaral wants to merge 3 commits into
port-labs:mainfrom
eamaral:add-scalequality-integration
Open

[Integration] Add ScaleQuality integration#3597
eamaral wants to merge 3 commits into
port-labs:mainfrom
eamaral:add-scalequality-integration

Conversation

@eamaral

@eamaral eamaral commented Jul 22, 2026

Copy link
Copy Markdown

Description

Adds the ScaleQuality integration, an Ocean exporter that ingests ScaleQuality's measured quality signals into Port.

ScaleQuality exposes a read-only REST API. This integration calls a single bulk endpoint, GET /v1/entities, which returns every organization, business unit and team the API key can see (each already enriched with its signals), and maps them onto a scaleQualityEntity blueprint:

  • AI durability (%) — share of AI-written code that survives instead of turning into rework (measured)
  • Engineering maturity (level) — consolidated assessment level, 1 to 5 (measured)
  • Code maturity (/100) — zero-config code maturity verdict (measured)
  • Technologies — count on the tech radar (declared)
  • Durability statusok / warn / risk / unknown

Each entity carries a parent relation, so teams roll up into business units and business units into the organization, plus a deep link back into the ScaleQuality app.

Type of change

  • New integration

Notes

  • Poll-only (no webhooks); saas.enabled: false. Auth is a read-only Bearer API key.
  • make lint (black, ruff, mypy strict, yamllint, poetry check) and pytest pass locally.
  • Verified end to end against a live ScaleQuality organization: a resync returned 13 entities (org + business units + teams) with correct values and the parent hierarchy.

Note

Low Risk
Greenfield read-only exporter with no changes to shared Ocean core; risk is limited to correct mapping and handling of the external API key.

Overview
Adds a new ScaleQuality Ocean integration under integrations/scalequality that syncs org, business unit, and team quality signals into Port on a polling resync schedule (saas.enabled: false).

On resync it calls ScaleQuality GET /v1/entities via a thin async client (Bearer API key), returns the entities payload, and maps each row to the scaleQualityEntity blueprint (AI durability, engineering/code maturity, tech radar count, durability status, measuredAt, deep link, and parent hierarchy). Port resources include blueprints.json, port-app-config.yaml, and integration spec.yaml with scaleQualityApiUrl / sensitive scaleQualityApiKey.

Includes standard Ocean packaging (Poetry, shared Makefile), .env.example, README/changelog, and pytest coverage for the HTTP client (auth header, URL normalization, entity parsing).

Reviewed by Cursor Bugbot for commit a6d1da7. Configure here.

Adds an Ocean exporter that ingests ScaleQuality quality signals (AI code
durability, engineering maturity, code maturity, tech radar) for every org,
business unit and team as Port entities.

Signed-off-by: Erik Amaral <erik.fernandes87@gmail.com>
@eamaral
eamaral force-pushed the add-scalequality-integration branch from a6d1da7 to 7c728be Compare July 22, 2026 23:48
@qodo-code-review

Copy link
Copy Markdown
Contributor

PR Summary by Qodo

Add ScaleQuality Ocean exporter integration

✨ Enhancement ⚙️ Configuration changes 🧪 Tests 📝 Documentation 🕐 40+ Minutes

Grey Divider

AI Description

• Add a new Ocean exporter that ingests ScaleQuality quality signals into Port entities.
• Define the scaleQualityEntity blueprint and mappings, including parent rollups and deep links.
• Add local dev tooling and tests for the ScaleQuality API client.
Diagram

graph TD
  A["Ocean resync"] --> B["main.py resync"] --> C["ScaleQualityClient"] --> D{{"ScaleQuality API /entities"}} --> E["Port mapping (blueprint)"] --> F[("Port catalog")]
  subgraph Legend
    direction LR
    _p["Process"] ~~~ _e{{"External API"}} ~~~ _db[("Port")]
  end
Loading
High-Level Assessment

The following are alternative approaches to this PR:

1. Model org/BU/team as separate blueprints
  • ➕ Clearer semantics per scope type (different required fields/relations per level)
  • ➕ Easier to attach scope-specific automation/actions later
  • ➖ More blueprints/mappings to maintain
  • ➖ Requires cross-blueprint relations and potentially more complex querying in Port
2. Map to existing Port org/team constructs (if available)
  • ➕ Avoids creating a parallel hierarchy blueprint
  • ➕ Better alignment with other integrations using the same primitives
  • ➖ May not fit Port’s existing entity model cleanly (especially the BU layer)
  • ➖ Could force lossy mapping or awkward property placement
3. Add validation via Pydantic models for API payload
  • ➕ Earlier, clearer failures on unexpected payload changes
  • ➕ Typed access reduces mapping/key errors
  • ➖ More code and maintenance for a read-only bulk payload
  • ➖ May slow iteration if the API evolves frequently

Recommendation: The current approach (single bulk fetch + one hierarchical blueprint with a parent relation) is the simplest and lowest-risk fit for ScaleQuality’s API shape. Consider separate blueprints only if different lifecycle/automation needs emerge per scope type; otherwise, keep the unified blueprint and (optionally) introduce lightweight payload validation if API stability becomes a concern.

Files changed (21) +3268 / -0 · 1 not counted

Enhancement (3) +106 / -0
client.pyAdd async ScaleQuality API client for bulk entity fetch +42/-0

Add async ScaleQuality API client for bulk entity fetch

• Implements a thin async client that sets Bearer auth headers, calls 'GET /entities', logs results, and raises on HTTP status errors.

integrations/scalequality/client.py

integration.pyAdd Ocean integration config models and resource kind +37/-0

Add Ocean integration config models and resource kind

• Defines the resource kind ('scale-quality-entity') and Port app config schema used by Ocean to validate and load resource configuration.

integrations/scalequality/integration.py

main.pyImplement resync handler to ingest ScaleQuality entities +27/-0

Implement resync handler to ingest ScaleQuality entities

• Registers an Ocean resync handler that initializes the client from integration config and returns the fetched entities for mapping to Port.

integrations/scalequality/main.py

Tests (3) +92 / -0
__init__.pyAdd tests package marker not counted

Add tests package marker

• Introduces an (empty) 'tests' package initializer to support test discovery/import behavior.

integrations/scalequality/tests/init.py

conftest.pyAdd Ocean context fixture for tests +21/-0

Add Ocean context fixture for tests

• Initializes a minimal mocked Ocean context fixture so integration components can be constructed in unit tests.

integrations/scalequality/tests/conftest.py

test_client.pyAdd unit tests for ScaleQualityClient headers and entity parsing +71/-0

Add unit tests for ScaleQualityClient headers and entity parsing

• Tests Bearer auth header setup, base URL normalization, and that 'get_entities()' returns the 'entities' array (including empty responses) using 'pytest-httpx'.

integrations/scalequality/tests/test_client.py

Documentation (3) +71 / -0
CHANGELOG.mdAdd initial changelog entry for 0.1.0-beta +15/-0

Add initial changelog entry for 0.1.0-beta

• Introduces a Keep-a-Changelog formatted changelog with a first release note describing the new integration and ingested signals.

integrations/scalequality/CHANGELOG.md

CONTRIBUTING.mdAdd placeholder contributing guide +7/-0

Add placeholder contributing guide

• Adds a minimal contributing document stub for future local run and contributor guidance.

integrations/scalequality/CONTRIBUTING.md

README.mdDocument ScaleQuality integration behavior and configuration +49/-0

Document ScaleQuality integration behavior and configuration

• Documents the ingested signals, hierarchy behavior via 'parent', the single bulk API call strategy, required configuration, and local development steps.

integrations/scalequality/README.md

Other (12) +2999 / -0
.env.exampleAdd local env template for Port + ScaleQuality credentials +8/-0

Add local env template for Port + ScaleQuality credentials

• Introduces a sample '.env' with Ocean/Port settings and ScaleQuality API URL/key configuration for local runs.

integrations/scalequality/.env.example

.gitignoreAdd integration-specific Python/.env ignore rules +153/-0

Add integration-specific Python/.env ignore rules

• Adds a comprehensive Python-focused '.gitignore' including virtualenvs, caches, coverage artifacts, and '.env'.

integrations/scalequality/.gitignore

.gitignoreEnsure Port resource directory is tracked +1/-0

Ensure Port resource directory is tracked

• Adds a '.gitignore' rule to keep the resources directory in git while allowing generated files to be managed explicitly.

integrations/scalequality/.port/resources/.gitignore

blueprints.jsonDefine 'scaleQualityEntity' blueprint and parent relation +68/-0

Define 'scaleQualityEntity' blueprint and parent relation

• Adds the Port blueprint for ScaleQuality scopes (org/BU/team) with quality-signal properties and a self-referential 'parent' relation for hierarchy rollups.

integrations/scalequality/.port/resources/blueprints.json

port-app-config.yamlMap ScaleQuality entity payload to Port entities and relations +23/-0

Map ScaleQuality entity payload to Port entities and relations

• Configures a single resource kind ('scale-quality-entity') and maps API fields to blueprint properties and the 'parent' relation, enabling related-entity creation and dependent deletion.

integrations/scalequality/.port/resources/port-app-config.yaml

spec.yamlRegister integration metadata and required configuration +22/-0

Register integration metadata and required configuration

• Adds the integration spec (exporter feature, docs section, SaaS disabled) and declares required config keys for API URL and read-only API key.

integrations/scalequality/.port/spec.yaml

MakefileWire integration Makefile to shared infra targets +1/-0

Wire integration Makefile to shared infra targets

• Adds a Makefile delegating to the repository’s shared integration build/lint/test infrastructure.

integrations/scalequality/Makefile

.gitignoreIgnore generated towncrier fragments by default +2/-0

Ignore generated towncrier fragments by default

• Ignores all files in the changelog fragments directory except the '.gitignore' itself.

integrations/scalequality/changelog/.gitignore

debug.pyAdd debug entrypoint to run Ocean locally +4/-0

Add debug entrypoint to run Ocean locally

• Provides a simple '__main__' entrypoint that invokes 'port_ocean.run()' for local debugging.

integrations/scalequality/debug.py

poetry.lockLock Python dependencies for the integration +2599/-0

Lock Python dependencies for the integration

• Adds the Poetry lockfile to pin runtime and dev dependencies for reproducible installs.

integrations/scalequality/poetry.lock

poetry.tomlConfigure Poetry to create in-project virtualenvs +3/-0

Configure Poetry to create in-project virtualenvs

• Sets Poetry virtualenv behavior to create '.venv' within the project directory.

integrations/scalequality/poetry.toml

pyproject.tomlDefine integration package, dev tooling, and strict type/test configs +115/-0

Define integration package, dev tooling, and strict type/test configs

• Adds Poetry project metadata, Port Ocean dependency, dev toolchain (ruff/black/mypy/pytest/towncrier), and strict mypy/pytest settings.

integrations/scalequality/pyproject.toml

@aws-amplify-eu-west-1

Copy link
Copy Markdown

This pull request is automatically being deployed by Amplify Hosting (learn more).

Access this pull request here: https://pr-3597.d1ftd8v2gowp8w.amplifyapp.com

@qodo-code-review

qodo-code-review Bot commented Jul 22, 2026

Copy link
Copy Markdown
Contributor

Code Review by Qodo

🐞 Bugs (0) 📘 Rule violations (1) 📜 Skill insights (0)

Context used
✅ Compliance rules (platform): 23 rules

Grey Divider


Remediation recommended

1. get_entities() returns full entity list 📘 Rule violation ➹ Performance
Description
ScaleQualityClient.get_entities() fetches all entities in one call without pagination and returns
the entire list, which can be large and memory-heavy for big organizations. This violates the
integration guidelines requiring pagination and generator-based processing for large collections.
Code

integrations/scalequality/client.py[R26-42]

+    async def get_entities(self) -> list[dict[Any, Any]]:
+        """Fetch every org, business unit and team with its quality signals."""
+        url = f"{self.base_url}/entities"
+        try:
+            response = await self.http_client.get(url)
+            response.raise_for_status()
+        except httpx.HTTPStatusError as error:
+            logger.error(
+                f"ScaleQuality API returned {error.response.status_code} for "
+                f"{url}: {error.response.text}"
+            )
+            raise
+
+        payload: dict[Any, Any] = response.json()
+        entities: list[dict[Any, Any]] = payload.get("entities", [])
+        logger.info(f"Fetched {len(entities)} entities from ScaleQuality")
+        return entities
Evidence
PR Compliance ID 569304 requires pagination for third-party API collection retrieval, but
get_entities() calls GET {base_url}/entities with no pagination parameters. PR Compliance ID
569293 requires generator patterns for large dataset processing, but get_entities() returns a full
list (return entities) instead of yielding items/pages.

Rule 569304: Implement pagination for large dataset retrieval in integrations
Rule 569293: Use generators for large dataset processing in integrations
integrations/scalequality/client.py[26-42]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The integration fetches a potentially large collection from ScaleQuality using a single request and returns it as a full in-memory list, without pagination and without a generator-based processing pattern.

## Issue Context
`ScaleQualityClient.get_entities()` calls `GET {base_url}/entities` with no paging params and returns `list[dict[Any, Any]]`. For large tenants this can increase memory usage and sync time.

## Fix Focus Areas
- integrations/scalequality/client.py[26-42]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


2. Network errors lack context ✓ Resolved 🐞 Bug ◔ Observability
Description
ScaleQualityClient.get_entities only logs HTTP status failures; connection/timeouts and JSON
decoding errors will raise without any ScaleQuality-specific log context (operation/URL),
complicating resync troubleshooting.
Code

integrations/scalequality/client.py[R29-41]

+        try:
+            response = await self.http_client.get(url)
+            response.raise_for_status()
+        except httpx.HTTPStatusError as error:
+            logger.error(
+                f"ScaleQuality API returned {error.response.status_code} for "
+                f"{url}: {error.response.text}"
+            )
+            raise
+
+        payload: dict[Any, Any] = response.json()
+        entities: list[dict[Any, Any]] = payload.get("entities", [])
+        logger.info(f"Fetched {len(entities)} entities from ScaleQuality")
Evidence
The ScaleQuality client only catches HTTPStatusError and immediately parses JSON outside the
try/except, so transport failures and JSON decoding errors won’t get ScaleQuality-specific logs.
Jira’s client in this repo demonstrates the expected pattern of explicitly logging RequestError with
request context.

integrations/scalequality/client.py[26-41]
integrations/jira/jira/client.py[250-265]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`ScaleQualityClient.get_entities` only catches `httpx.HTTPStatusError`. Transport-level failures (`httpx.RequestError`, e.g. timeouts/connection errors) and JSON parsing failures will propagate without a ScaleQuality-specific log message (URL/operation), making failures harder to debug.

## Issue Context
Other integrations in this repo log `RequestError` separately to preserve operation context while still re-raising.

## Fix Focus Areas
- integrations/scalequality/client.py[26-41]
- integrations/jira/jira/client.py[250-265]

## Suggested fix
- Wrap the request + parsing in a broader try/except:
 - `except httpx.RequestError as e:` log method + url + error string, then re-raise.
 - `except ValueError as e:` (or `json.JSONDecodeError`) log method + url + status code + a short body snippet (if safe), then re-raise.
- Keep raising the original exception to preserve failure behavior.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Informational

3. Unbounded error body logging ✓ Resolved 🐞 Bug ⛨ Security
Description
On HTTP status failures, the client logs the full upstream response body via error.response.text;
this can create very large log entries and may inadvertently persist upstream-provided details in
logs.
Code

integrations/scalequality/client.py[R33-36]

+            logger.error(
+                f"ScaleQuality API returned {error.response.status_code} for "
+                f"{url}: {error.response.text}"
+            )
Evidence
The error handler interpolates error.response.text directly into logs for any non-2xx response,
which is the full upstream body as a string.

integrations/scalequality/client.py[32-36]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The HTTPStatusError handler logs `error.response.text` in full. This can bloat logs and may capture upstream-provided details that shouldn’t be persisted verbatim.

## Issue Context
The integration is read-only, but error responses can still be unexpectedly large (HTML, stack traces, verbose JSON).

## Fix Focus Areas
- integrations/scalequality/client.py[32-36]

## Suggested fix
- Log structured metadata (status code, URL/path, maybe a request ID header) and either:
 - omit the body entirely, or
 - log only a bounded snippet (e.g., first N characters) and/or only for specific status codes.
- Keep the exception re-raise unchanged.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

Qodo Logo

Comment thread integrations/scalequality/client.py Outdated
Comment thread integrations/scalequality/client.py
Comment thread integrations/scalequality/client.py
eamaral added 2 commits July 22, 2026 21:36
- get_entities is now an async generator, aligning with Ocean's streaming
  resync contract instead of returning one large in-memory list
- catch httpx.RequestError and JSON decode errors with URL/operation context
- bound the logged upstream error body so a large response cannot bloat logs

Signed-off-by: Erik Amaral <erik.fernandes87@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant