Compare commits

...

7 Commits

Author SHA1 Message Date
Prowler Bot 17c066e093 chore: changelog v5.25.3 (#11078)
Co-authored-by: Pepe Fagoaga <pepe@prowler.com>
2026-05-08 08:39:29 +02:00
Prowler Bot a70f0652b6 fix(ui): hide line numbers in CLI command remediation block (#11061)
Co-authored-by: Hugo Pereira Brito <101209179+HugoPBrito@users.noreply.github.com>
2026-05-06 15:06:00 +01:00
Prowler Bot fae4fbc0ae fix: PR number in changelog entry for #10529 (#11058)
Co-authored-by: Hugo Pereira Brito <101209179+HugoPBrito@users.noreply.github.com>
2026-05-06 11:56:21 +01:00
Prowler Bot bbe45ed708 fix(oci): scan identity in known valid region (#11056)
Co-authored-by: rchotacode <32524742+rchotacode@users.noreply.github.com>
Co-authored-by: Hugo Pereira Brito <101209179+HugoPBrito@users.noreply.github.com>
2026-05-06 11:44:26 +01:00
Prowler Bot 6b6d22bb31 chore(api): Bump version to v1.26.3 (#10996)
Co-authored-by: prowler-bot <179230569+prowler-bot@users.noreply.github.com>
2026-05-05 10:45:57 +02:00
Prowler Bot a3b4f94368 chore(sdk): Bump version to v5.25.3 (#10994)
Co-authored-by: prowler-bot <179230569+prowler-bot@users.noreply.github.com>
2026-05-05 10:45:35 +02:00
Prowler Bot 178cdb1b57 chore(ui): Bump version to v5.25.3 (#10995)
Co-authored-by: prowler-bot <179230569+prowler-bot@users.noreply.github.com>
2026-05-05 10:44:56 +02:00
16 changed files with 365 additions and 50 deletions
+1 -1
View File
@@ -145,7 +145,7 @@ SENTRY_RELEASE=local
NEXT_PUBLIC_SENTRY_ENVIRONMENT=${SENTRY_ENVIRONMENT}
#### Prowler release version ####
NEXT_PUBLIC_PROWLER_RELEASE_VERSION=v5.25.2
NEXT_PUBLIC_PROWLER_RELEASE_VERSION=v5.25.3
# Social login credentials
SOCIAL_GOOGLE_OAUTH_CALLBACK_URL="${AUTH_URL}/api/auth/callback/google"
+1 -1
View File
@@ -50,7 +50,7 @@ name = "prowler-api"
package-mode = false
# Needed for the SDK compatibility
requires-python = ">=3.11,<3.13"
version = "1.26.2"
version = "1.26.3"
[project.scripts]
celery = "src.backend.config.settings.celery"
+1 -1
View File
@@ -1,7 +1,7 @@
openapi: 3.0.3
info:
title: Prowler API
version: 1.26.2
version: 1.26.3
description: |-
Prowler API specification.
+1 -1
View File
@@ -422,7 +422,7 @@ class SchemaView(SpectacularAPIView):
def get(self, request, *args, **kwargs):
spectacular_settings.TITLE = "Prowler API"
spectacular_settings.VERSION = "1.26.2"
spectacular_settings.VERSION = "1.26.3"
spectacular_settings.DESCRIPTION = (
"Prowler API specification.\n\nThis file is auto-generated."
)
+8
View File
@@ -2,6 +2,14 @@
All notable changes to the **Prowler SDK** are documented in this file.
## [5.25.3] (Prowler v5.25.3)
### 🐞 Fixed
- Oracle Cloud identity scans known or supplied regions to better support non Ashburn tenancies [(#10529)](https://github.com/prowler-cloud/prowler/pull/10529)
---
## [5.25.2] (Prowler v5.25.2)
### 🐞 Fixed
+1 -1
View File
@@ -48,7 +48,7 @@ class _MutableTimestamp:
timestamp = _MutableTimestamp(datetime.today())
timestamp_utc = _MutableTimestamp(datetime.now(timezone.utc))
prowler_version = "5.25.2"
prowler_version = "5.25.3"
html_logo_url = "https://github.com/prowler-cloud/prowler/"
square_logo_img = "https://raw.githubusercontent.com/prowler-cloud/prowler/dc7d2d5aeb92fdf12e8604f42ef6472cd3e8e889/docs/img/prowler-logo-black.png"
aws_logo = "https://user-images.githubusercontent.com/38561120/235953920-3e3fba08-0795-41dc-b480-9bea57db9f2e.png"
@@ -66,6 +66,7 @@ class OraclecloudProvider(Provider):
_compartments: list = []
_mutelist: OCIMutelist
audit_metadata: Audit_Metadata
_home_region: str = "us-ashburn-1"
def __init__(
self,
@@ -160,6 +161,13 @@ class OraclecloudProvider(Provider):
# Get regions
self._regions = self.get_regions_to_audit(region)
self._home_region = None
if self._regions:
self._home_region = next(
(region.key for region in self._regions if region.is_home_region),
self._regions[0].key,
)
logger.info(f"Home region is: {self._home_region}")
# Get compartments
self._compartments = self.get_compartments_to_audit(
@@ -217,6 +225,10 @@ class OraclecloudProvider(Provider):
def regions(self):
return self._regions
@property
def home_region(self):
return self._home_region
@property
def compartments(self):
return self._compartments
@@ -1,6 +1,7 @@
"""OCI Identity Service Module."""
from datetime import datetime
from threading import Lock
from typing import Optional
import oci
@@ -26,6 +27,7 @@ class Identity(OCIService):
self.policies = []
self.dynamic_groups = []
self.domains = []
self._domains_lock = Lock()
self.password_policy = None
self.root_compartment_resources = []
self.active_non_root_compartments = []
@@ -61,8 +63,8 @@ class Identity(OCIService):
regional_client: Regional OCI client
"""
try:
# Identity is a global service, use home region
if regional_client.region not in self.provider.identity.region:
# Only use one region for global users
if regional_client.region != self.provider.home_region:
return
identity_client = self.__get_client__(regional_client.region)
@@ -312,7 +314,8 @@ class Identity(OCIService):
def __list_groups__(self, regional_client):
"""List all IAM groups."""
try:
if regional_client.region not in self.provider.identity.region:
# Only use one region for global groups
if regional_client.region != self.provider.home_region:
return
identity_client = self.__get_client__(regional_client.region)
@@ -355,7 +358,8 @@ class Identity(OCIService):
def __list_policies__(self, regional_client):
"""List all IAM policies."""
try:
if regional_client.region not in self.provider.identity.region:
# Only use one region for global policies
if regional_client.region != self.provider.home_region:
return
identity_client = self.__get_client__(regional_client.region)
@@ -399,8 +403,8 @@ class Identity(OCIService):
def __list_dynamic_groups__(self, regional_client):
"""List all dynamic groups in the tenancy."""
try:
# Dynamic groups are only in the home region
if regional_client.region not in self.provider.identity.region:
# Only use one region for global dynamic groups
if regional_client.region != self.provider.home_region:
return
identity_client = self.__get_client__(regional_client.region)
@@ -447,10 +451,6 @@ class Identity(OCIService):
def __list_domains__(self, regional_client):
"""List all identity domains."""
try:
# Domains are only in the home region
if regional_client.region not in self.provider.identity.region:
return
identity_client = self.__get_client__(regional_client.region)
logger.info("Identity - Listing Identity Domains...")
@@ -458,6 +458,7 @@ class Identity(OCIService):
try:
# List all domains in the tenancy
for compartment in self.audited_compartments:
domains = oci.pagination.list_call_get_all_results(
identity_client.list_domains,
compartment_id=compartment.id,
@@ -465,20 +466,38 @@ class Identity(OCIService):
).data
for domain in domains:
self.domains.append(
IdentityDomain(
id=domain.id,
display_name=domain.display_name,
description=domain.description or "",
url=domain.url,
home_region=domain.home_region,
compartment_id=compartment.id,
lifecycle_state=domain.lifecycle_state,
time_created=domain.time_created,
region=regional_client.region,
password_policies=[],
# Threads run __list_domains__ concurrently per
# region; serialize the dedupe-then-append so two
# regions returning the same domain cannot race
# past each other and produce duplicates or lose
# the home-region preference.
with self._domains_lock:
existing = next(
(d for d in self.domains if d.id == domain.id),
None,
)
if existing is not None:
# Prefer the entry from the domain's home region
if domain.home_region == regional_client.region:
self.domains.remove(existing)
else:
continue
self.domains.append(
IdentityDomain(
id=domain.id,
display_name=domain.display_name,
description=domain.description or "",
url=domain.url,
home_region=domain.home_region,
compartment_id=compartment.id,
lifecycle_state=domain.lifecycle_state,
time_created=domain.time_created,
region=regional_client.region,
password_policies=[],
)
)
)
except Exception as error:
logger.error(
@@ -493,8 +512,8 @@ class Identity(OCIService):
def __list_domain_password_policies__(self, regional_client):
"""List password policies for all identity domains."""
try:
# Password policies are only in the home region
if regional_client.region not in self.provider.identity.region:
# Only use one region for all domain scan
if regional_client.region != self.provider.home_region:
return
logger.info("Identity - Listing Domain Password Policies...")
@@ -551,7 +570,8 @@ class Identity(OCIService):
def __get_password_policy__(self, regional_client):
"""Get the password policy for the tenancy."""
try:
if regional_client.region not in self.provider.identity.region:
# Only use one region for global password policies
if regional_client.region != self.provider.home_region:
return
identity_client = self.__get_client__(regional_client.region)
@@ -578,8 +598,8 @@ class Identity(OCIService):
def __search_root_compartment_resources__(self, regional_client):
"""Search for resources in the root compartment using OCI Resource Search."""
try:
# Search is a global service, use home region
if regional_client.region not in self.provider.identity.region:
# Only use one region for global search
if regional_client.region != self.provider.home_region:
return
logger.info("Identity - Searching for resources in root compartment...")
@@ -626,10 +646,9 @@ class Identity(OCIService):
def __search_active_non_root_compartments__(self, regional_client):
"""Search for active non-root compartments using OCI Resource Search."""
try:
# Search is a global service, use home region
if regional_client.region not in self.provider.identity.region:
# Only use one region for global search
if regional_client.region != self.provider.home_region:
return
logger.info("Identity - Searching for active non-root compartments...")
# Create search client using the helper method for proper authentication
+1 -1
View File
@@ -95,7 +95,7 @@ maintainers = [{name = "Prowler Engineering", email = "engineering@prowler.com"}
name = "prowler"
readme = "README.md"
requires-python = ">=3.10,<3.13"
version = "5.25.2"
version = "5.25.3"
[project.scripts]
prowler = "prowler.__main__:prowler"
@@ -37,6 +37,7 @@ def set_mocked_oraclecloud_provider(
signer=MagicMock(),
profile="DEFAULT",
)
provider.home_region = region
# Mock identity
provider.identity = OCIIdentityInfo(
@@ -6,7 +6,7 @@ from prowler.providers.oraclecloud.exceptions.exceptions import (
OCIAuthenticationError,
OCIInvalidConfigError,
)
from prowler.providers.oraclecloud.models import OCISession
from prowler.providers.oraclecloud.models import OCIIdentityInfo, OCIRegion, OCISession
from prowler.providers.oraclecloud.oraclecloud_provider import OraclecloudProvider
@@ -199,3 +199,59 @@ MIIEpQIBAAKCAQEA0Z3VS5JJcds3xfn/ygWyF8n0sMcD/QHWCJ7yGSEtLN2T
)
assert connection.is_connected is True
class TestOraclecloudProviderInit:
"""Tests for OraclecloudProvider initialization"""
def test_init_with_region_set_populates_provider_state(self):
mock_session = OCISession(
config={"region": "us-ashburn-1"}, signer=None, profile="DEFAULT"
)
mock_identity = OCIIdentityInfo(
tenancy_id="ocid1.tenancy.oc1..aaaaaaaexample",
tenancy_name="test-tenancy",
user_id="ocid1.user.oc1..aaaaaaaexample",
region="us-ashburn-1",
profile="DEFAULT",
audited_regions=set(),
audited_compartments=[],
)
mock_regions = [
OCIRegion(key="us-phoenix-1", name="us-phoenix-1", is_home_region=False),
OCIRegion(key="us-ashburn-1", name="us-ashburn-1", is_home_region=True),
]
mock_compartments = ["ocid1.compartment.oc1..aaaaaaaexample"]
with (
patch(
"prowler.providers.oraclecloud.oraclecloud_provider.OraclecloudProvider.setup_session",
return_value=mock_session,
) as mock_setup_session,
patch(
"prowler.providers.oraclecloud.oraclecloud_provider.OraclecloudProvider.set_identity",
return_value=mock_identity,
),
patch(
"prowler.providers.oraclecloud.oraclecloud_provider.OraclecloudProvider.get_regions_to_audit",
return_value=mock_regions,
),
patch(
"prowler.providers.oraclecloud.oraclecloud_provider.OraclecloudProvider.get_compartments_to_audit",
return_value=mock_compartments,
),
patch(
"prowler.providers.common.provider.Provider.set_global_provider"
) as mock_set_global,
):
provider = OraclecloudProvider(
region={"us-ashburn-1"},
config_content={"dummy": True},
mutelist_content={"Accounts": {}},
)
assert mock_setup_session.call_args.kwargs["region"] == "us-ashburn-1"
assert provider.session == mock_session
assert provider.identity == mock_identity
assert provider.regions == mock_regions
assert provider.compartments == mock_compartments
assert provider.home_region == "us-ashburn-1"
mock_set_global.assert_called_once_with(provider)
@@ -1,4 +1,7 @@
from unittest.mock import patch
from concurrent.futures import ThreadPoolExecutor
from datetime import datetime
from threading import Lock
from unittest.mock import MagicMock, patch
from tests.providers.oraclecloud.oci_fixtures import set_mocked_oraclecloud_provider
@@ -28,3 +31,184 @@ class TestIdentityService:
# Verify service name
assert identity_client.service == "identity"
assert identity_client.provider == oraclecloud_provider
def test_list_domains_passwords_skipped_outside_home(self):
"""Domains should be skipped when not in home region."""
with patch(
"prowler.providers.oraclecloud.services.identity.identity_service.Identity.__init__",
return_value=None,
):
from prowler.providers.oraclecloud.services.identity.identity_service import (
Identity,
)
identity_client = Identity(None)
identity_client.service = "identity"
identity_client.provider = set_mocked_oraclecloud_provider()
identity_client.provider._home_region = "us-ashburn-1"
identity_client.audited_compartments = [
MagicMock(id="ocid1.compartment.oc1..aaaaaaaexample")
]
identity_client.domains = []
identity_client._domains_lock = Lock()
identity_client.session_signer = None
identity_client.session_config = None
regional_client_ash = MagicMock()
regional_client_ash.region = "us-ashburn-1"
regional_client_chi = MagicMock()
regional_client_chi.region = "us-chicago-1"
policy = MagicMock()
policy.id = "123"
policy.name = "Test Policy"
policy.description = "This is a test policy"
policy.min_length = 8
policy.password_expires_after = 90
policy.num_passwords_in_history = 5
policy.password_expire_warning = 7
policy.min_password_age = 1
domains = []
for region in ["us-phoenix-1", "us-ashburn-1", "us-chicago-1"]:
domain = MagicMock()
domain.id = (
"ocid1.domain.oc1.iad.aaaaaaaaexampleuniqueID"
if region == "us-chicago-1"
else "ocid1.domain.oc1.iad.aaaaaaaaexampleuniqueID2"
)
domain.display_name = "exampledomain"
domain.description = "example"
domain.url = "https://idcs-example.identity.oraclecloud.com"
domain.home_region = region
domain.region = "us-ashburn-1"
domain.lifecycle_state = "ACTIVE"
domain.time_created = datetime.now()
domains.append(domain)
with (
patch(
"prowler.providers.oraclecloud.services.identity.identity_service.Identity.__get_client__",
return_value=MagicMock(),
),
patch(
"prowler.providers.oraclecloud.services.identity.identity_service.oci.pagination.list_call_get_all_results",
return_value=MagicMock(data=domains),
),
patch(
"prowler.providers.oraclecloud.services.identity.identity_service.oci.identity_domains.IdentityDomainsClient",
return_value=MagicMock(
list_password_policies=lambda: MagicMock(
data=MagicMock(resources=[policy])
)
),
),
):
identity_client.__list_domains__(regional_client_ash)
identity_client.__list_domains__(regional_client_chi)
identity_client.__list_domain_password_policies__(regional_client_ash)
identity_client.__list_domain_password_policies__(regional_client_chi)
assert (
len(identity_client.domains) == 2
and any(
domain.home_region == "us-ashburn-1"
and domain.region == "us-ashburn-1"
for domain in identity_client.domains
)
and any(
domain.home_region == "us-chicago-1"
and domain.region == "us-chicago-1"
for domain in identity_client.domains
)
and all(len(d.password_policies) == 1 for d in identity_client.domains)
)
def test_list_domains_concurrent_dedupes_and_prefers_home_region(self):
"""__list_domains__ runs across regions in parallel; the dedupe
must stay correct under concurrent calls (no duplicates, home
region wins)."""
with patch(
"prowler.providers.oraclecloud.services.identity.identity_service.Identity.__init__",
return_value=None,
):
from prowler.providers.oraclecloud.services.identity.identity_service import (
Identity,
)
identity_client = Identity(None)
identity_client.service = "identity"
identity_client.provider = set_mocked_oraclecloud_provider()
identity_client.audited_compartments = [
MagicMock(id="ocid1.compartment.oc1..aaaaaaaexample")
]
identity_client.domains = []
identity_client._domains_lock = Lock()
identity_client.session_signer = None
identity_client.session_config = None
regions = [
"us-ashburn-1",
"us-chicago-1",
"us-phoenix-1",
"eu-frankfurt-1",
]
home_region_by_domain = {
"ocid1.domain.oc1..domainA": "us-ashburn-1",
"ocid1.domain.oc1..domainB": "us-chicago-1",
"ocid1.domain.oc1..domainC": "eu-frankfurt-1",
}
# Each region returns the same set of domains (every domain
# is visible from every region; only one of those regions is
# actually the domain's home region).
def make_domains_for_region(_region):
ds = []
for domain_id, home_region in home_region_by_domain.items():
d = MagicMock()
d.id = domain_id
d.display_name = f"name-{domain_id}"
d.description = ""
d.url = "https://example.identity.oraclecloud.com"
d.home_region = home_region
d.lifecycle_state = "ACTIVE"
d.time_created = datetime.now()
ds.append(d)
return MagicMock(data=ds)
regional_clients = []
for region in regions:
rc = MagicMock()
rc.region = region
regional_clients.append(rc)
with (
patch(
"prowler.providers.oraclecloud.services.identity.identity_service.Identity.__get_client__",
return_value=MagicMock(),
),
patch(
"prowler.providers.oraclecloud.services.identity.identity_service.oci.pagination.list_call_get_all_results",
side_effect=lambda _list_call, compartment_id, lifecycle_state: make_domains_for_region(
compartment_id
),
),
):
# Run several iterations to make any race more likely
# to surface; with the lock removed this loop fails
# frequently with duplicates.
for _ in range(20):
identity_client.domains = []
with ThreadPoolExecutor(
max_workers=len(regional_clients)
) as executor:
futures = [
executor.submit(identity_client.__list_domains__, rc)
for rc in regional_clients
]
for f in futures:
f.result()
assert len(identity_client.domains) == len(home_region_by_domain)
by_id = {d.id: d for d in identity_client.domains}
for domain_id, home_region in home_region_by_domain.items():
assert by_id[domain_id].region == home_region
assert by_id[domain_id].home_region == home_region
+8
View File
@@ -2,6 +2,14 @@
All notable changes to the **Prowler UI** are documented in this file.
## [1.25.3] (Prowler v5.25.3)
### 🐞 Fixed
- CLI command in the finding drawer no longer renders the line-number gutter, matching the original styled block while removing the leading `1` [(#11059)](https://github.com/prowler-cloud/prowler/pull/11059)
---
## [1.25.2] (Prowler v5.25.2)
### 🔄 Changed
@@ -219,21 +219,25 @@ vi.mock("@/components/shared/query-code-editor", () => ({
language,
value,
copyValue,
showLineNumbers = true,
}: {
ariaLabel: string;
language?: string;
value: string;
copyValue?: string;
showLineNumbers?: boolean;
}) => (
<div
data-testid="query-code-editor"
data-aria-label={ariaLabel}
data-language={language}
data-show-line-numbers={String(showLineNumbers)}
>
<span>{ariaLabel}</span>
<span>{value}</span>
<button
type="button"
aria-label={`Copy ${ariaLabel}`}
onClick={() => mockClipboardWriteText(copyValue ?? value)}
>
Copy editor code
@@ -255,7 +259,22 @@ vi.mock("@/components/icons/services/IconServices", () => ({
}));
vi.mock("@/components/ui/code-snippet/code-snippet", () => ({
CodeSnippet: ({ value }: { value: string }) => <span>{value}</span>,
CodeSnippet: ({
value,
formatter,
ariaLabel = "Copy to clipboard",
}: {
value: string;
formatter?: (value: string) => string;
ariaLabel?: string;
}) => (
<div data-testid="code-snippet">
<span>{formatter ? formatter(value) : value}</span>
<button type="button" onClick={() => mockClipboardWriteText(value)}>
{ariaLabel}
</button>
</div>
),
}));
vi.mock("@/components/ui/custom/custom-link", () => ({
@@ -592,7 +611,7 @@ describe("ResourceDetailDrawerContent — Fix 2: Remediation heading labels", ()
expect(allText).toContain("CLI Command");
});
it("should render remediation snippets with the shared code editor and copy CLI without the visual prompt", async () => {
it("should render CLI remediation in the code editor without line numbers and copy without the visual prompt", async () => {
// Given
const user = userEvent.setup();
render(
@@ -612,17 +631,19 @@ describe("ResourceDetailDrawerContent — Fix 2: Remediation heading labels", ()
// When
const editors = screen.getAllByTestId("query-code-editor");
await user.click(
within(editors[0]).getByRole("button", { name: "Copy editor code" }),
);
await user.click(screen.getByRole("button", { name: "Copy CLI Command" }));
// Then
expect(editors).toHaveLength(3);
expect(editors[0]).toHaveAttribute("data-aria-label", "CLI Command");
expect(editors[0]).toHaveAttribute("data-show-line-numbers", "false");
expect(editors[1]).toHaveAttribute("data-show-line-numbers", "true");
expect(editors[2]).toHaveAttribute("data-show-line-numbers", "true");
expect(mockClipboardWriteText).toHaveBeenCalledWith("aws s3 ...");
expect(screen.getByText("$ aws s3 ...")).toBeInTheDocument();
});
it("should pass syntax highlighting languages to each remediation editor", () => {
it("should pass syntax highlighting languages to all remediation editors", () => {
// Given
render(
<ResourceDetailDrawerContent
@@ -643,10 +664,12 @@ describe("ResourceDetailDrawerContent — Fix 2: Remediation heading labels", ()
const editors = screen.getAllByTestId("query-code-editor");
// Then
expect(editors).toHaveLength(3);
expect(editors[0]).toHaveAttribute("data-language", "shell");
expect(editors[1]).toHaveAttribute("data-language", "hcl");
expect(editors[2]).toHaveAttribute("data-language", "yaml");
expect(editors[0]).toHaveAttribute("data-aria-label", "CLI Command");
expect(editors[1]).toHaveAttribute("data-aria-label", "Terraform");
expect(editors[2]).toHaveAttribute("data-aria-label", "CloudFormation");
});
});
@@ -120,11 +120,13 @@ function renderRemediationCodeBlock({
value,
copyValue,
language = QUERY_EDITOR_LANGUAGE.PLAIN_TEXT,
showLineNumbers = true,
}: {
label: string;
value: string;
copyValue?: string;
language?: QueryEditorLanguage;
showLineNumbers?: boolean;
}) {
return (
<QueryCodeEditor
@@ -135,6 +137,7 @@ function renderRemediationCodeBlock({
editable={false}
minHeight={96}
showCopyButton
showLineNumbers={showLineNumbers}
onChange={() => {}}
/>
);
@@ -889,6 +892,7 @@ export function ResourceDetailDrawerContent({
copyValue: stripCodeFences(
checkMeta.remediation.code.cli,
),
showLineNumbers: false,
})}
</div>
)}
+5 -5
View File
@@ -10,8 +10,6 @@ import { EditorState } from "@codemirror/state";
import { tags } from "@lezer/highlight";
import CodeMirror, {
EditorView,
highlightActiveLineGutter,
lineNumbers,
placeholder as codeEditorPlaceholder,
} from "@uiw/react-codemirror";
import { Check, Copy } from "lucide-react";
@@ -1177,6 +1175,7 @@ interface QueryCodeEditorProps
editable?: boolean;
minHeight?: number;
showCopyButton?: boolean;
showLineNumbers?: boolean;
onChange: (value: string) => void;
onBlur?: () => void;
}
@@ -1195,6 +1194,7 @@ export const QueryCodeEditor = ({
editable = true,
minHeight = 320,
showCopyButton = false,
showLineNumbers = true,
onChange,
onBlur,
...props
@@ -1208,8 +1208,6 @@ export const QueryCodeEditor = ({
: lightHighlightStyle;
const extensions = [
lineNumbers(),
highlightActiveLineGutter(),
EditorView.lineWrapping,
codeEditorPlaceholder(placeholder ?? ""),
EditorView.contentAttributes.of({
@@ -1260,6 +1258,7 @@ export const QueryCodeEditor = ({
<div
data-testid="query-code-editor"
data-language={language}
data-show-line-numbers={String(showLineNumbers)}
className={cn(
"border-border-neutral-secondary bg-bg-neutral-primary overflow-hidden rounded-xl border",
invalid && "border-border-error-primary",
@@ -1307,8 +1306,9 @@ export const QueryCodeEditor = ({
basicSetup={{
foldGutter: false,
highlightActiveLine: false,
highlightActiveLineGutter: false,
highlightActiveLineGutter: showLineNumbers,
searchKeymap: false,
lineNumbers: showLineNumbers,
}}
editable={editable}
onChange={onChange}