Skip to content

Commit 5f39caf

Browse files
committed
Use a TAG field for instance names
1 parent 786587e commit 5f39caf

File tree

5 files changed

+110
-10
lines changed

5 files changed

+110
-10
lines changed

redis_sre_agent/core/instances.py

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616

1717
from pydantic import BaseModel, Field, SecretStr, field_serializer, field_validator
1818
from redisvl.query import CountQuery, FilterQuery
19-
from redisvl.query.filter import Tag, Text
19+
from redisvl.query.filter import Tag
2020

2121
from .encryption import encrypt_secret, get_secret_value
2222
from .keys import RedisKeys
@@ -414,8 +414,8 @@ async def query_instances(
414414
filter_expr = user_filter if filter_expr is None else (filter_expr & user_filter)
415415

416416
if search:
417-
# Text search on name field (supports prefix matching)
418-
name_filter = Text("name") % f"*{search}*"
417+
# Tag filter on name field with wildcard for partial matching
418+
name_filter = Tag("name") == f"*{search}*"
419419
filter_expr = name_filter if filter_expr is None else (filter_expr & name_filter)
420420

421421
# Get total count with filter
@@ -787,13 +787,12 @@ async def get_instance_by_name(instance_name: str) -> Optional[RedisInstance]:
787787
await _ensure_instances_index_exists()
788788
index = await get_instances_index()
789789

790-
# Use exact text match on name field
791-
# Note: name is indexed as TEXT, so we search for the exact term
790+
# Use exact tag match on name field
792791
fq = FilterQuery(
793792
return_fields=["data"],
794793
num_results=1,
795794
)
796-
fq.set_filter(Text("name") % instance_name)
795+
fq.set_filter(Tag("name") == instance_name)
797796

798797
results = await index.query(fq)
799798

redis_sre_agent/core/redis.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -188,7 +188,7 @@
188188
"storage_type": "hash",
189189
},
190190
"fields": [
191-
{"name": "name", "type": "text"},
191+
{"name": "name", "type": "tag"},
192192
{"name": "environment", "type": "tag"},
193193
{"name": "usage", "type": "tag"},
194194
{"name": "instance_type", "type": "tag"},

tests/unit/api/test_instances_api.py

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,73 @@ def test_list_instances_with_filters(self, client, sample_instance):
108108
assert call_kwargs["limit"] == 50
109109
assert call_kwargs["offset"] == 10
110110

111+
def test_list_instances_with_search(self, client, sample_instance):
112+
"""Test instance listing with search parameter."""
113+
mock_result = InstanceQueryResult(
114+
instances=[sample_instance],
115+
total=1,
116+
limit=100,
117+
offset=0,
118+
)
119+
120+
with patch("redis_sre_agent.core.instances.query_instances") as mock_query:
121+
mock_query.return_value = mock_result
122+
123+
response = client.get(
124+
"/api/v1/instances",
125+
params={"search": "test-redis"},
126+
)
127+
128+
assert response.status_code == 200
129+
mock_query.assert_called_once()
130+
call_kwargs = mock_query.call_args[1]
131+
assert call_kwargs["search"] == "test-redis"
132+
133+
def test_list_instances_with_empty_search(self, client, sample_instance):
134+
"""Test instance listing with empty search string."""
135+
mock_result = InstanceQueryResult(
136+
instances=[sample_instance],
137+
total=1,
138+
limit=100,
139+
offset=0,
140+
)
141+
142+
with patch("redis_sre_agent.core.instances.query_instances") as mock_query:
143+
mock_query.return_value = mock_result
144+
145+
response = client.get(
146+
"/api/v1/instances",
147+
params={"search": ""},
148+
)
149+
150+
assert response.status_code == 200
151+
mock_query.assert_called_once()
152+
call_kwargs = mock_query.call_args[1]
153+
# Empty string should be passed as empty (falsy)
154+
assert call_kwargs["search"] == ""
155+
156+
def test_list_instances_with_instance_type_filter(self, client, sample_instance):
157+
"""Test instance listing with instance_type filter."""
158+
mock_result = InstanceQueryResult(
159+
instances=[sample_instance],
160+
total=1,
161+
limit=100,
162+
offset=0,
163+
)
164+
165+
with patch("redis_sre_agent.core.instances.query_instances") as mock_query:
166+
mock_query.return_value = mock_result
167+
168+
response = client.get(
169+
"/api/v1/instances",
170+
params={"instance_type": "redis_cloud"},
171+
)
172+
173+
assert response.status_code == 200
174+
mock_query.assert_called_once()
175+
call_kwargs = mock_query.call_args[1]
176+
assert call_kwargs["instance_type"] == "redis_cloud"
177+
111178
def test_get_instance_success(self, client, sample_instance):
112179
"""Test successful instance retrieval."""
113180
with patch("redis_sre_agent.core.instances.get_instances") as mock_get:

tests/unit/mcp_server/test_mcp_server.py

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -423,6 +423,43 @@ async def test_list_instances_with_filters(self):
423423
assert result["total"] == 1
424424
assert result["instances"][0]["environment"] == "production"
425425

426+
@pytest.mark.asyncio
427+
async def test_list_instances_with_search(self):
428+
"""Test instance listing with search parameter."""
429+
from redis_sre_agent.core.instances import InstanceQueryResult
430+
431+
mock_instance = MagicMock()
432+
mock_instance.id = "redis-prod-1"
433+
mock_instance.name = "Production Redis"
434+
mock_instance.environment = "production"
435+
mock_instance.usage = "cache"
436+
mock_instance.description = "Main cache"
437+
mock_instance.instance_type = "redis_cloud"
438+
mock_instance.repo_url = None
439+
440+
mock_result = InstanceQueryResult(
441+
instances=[mock_instance],
442+
total=1,
443+
limit=100,
444+
offset=0,
445+
)
446+
447+
with patch(
448+
"redis_sre_agent.core.instances.query_instances",
449+
new_callable=AsyncMock,
450+
) as mock_query:
451+
mock_query.return_value = mock_result
452+
453+
result = await redis_sre_list_instances(search="Production")
454+
455+
# Verify query_instances was called with the search parameter
456+
mock_query.assert_called_once()
457+
call_kwargs = mock_query.call_args[1]
458+
assert call_kwargs["search"] == "Production"
459+
460+
assert result["total"] == 1
461+
assert result["instances"][0]["name"] == "Production Redis"
462+
426463
@pytest.mark.asyncio
427464
async def test_list_instances_error(self):
428465
"""Test list instances error handling."""

ui/src/pages/Instances.tsx

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -812,7 +812,6 @@ const AddInstanceForm = ({
812812

813813
const Instances = () => {
814814
const [instances, setInstances] = useState<RedisInstance[]>([]);
815-
const [totalCount, setTotalCount] = useState(0);
816815
const [isLoading, setIsLoading] = useState(true);
817816
const [isRefreshing, setIsRefreshing] = useState(false);
818817
const [error, setError] = useState("");
@@ -878,12 +877,10 @@ const Instances = () => {
878877
response.instances.map(convertToUIInstance);
879878

880879
setInstances(uiInstances);
881-
setTotalCount(response.total);
882880
} catch (err) {
883881
setError("Failed to load Redis instances. Please try again.");
884882
console.error("Error loading instances:", err);
885883
setInstances([]);
886-
setTotalCount(0);
887884
} finally {
888885
setIsLoading(false);
889886
}

0 commit comments

Comments
 (0)