Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
49 commits
Select commit Hold shift + click to select a range
54d67a2
Add Tag entity with JPA annotations for custom_tags table
chiar17 Apr 6, 2026
8355c86
Add ManyToMany relationship for custom tags in TestCase entity
chiar17 Apr 6, 2026
483bce5
Add TagRepository interface for managing Tag entities
chiar17 Apr 6, 2026
eef0a16
Add TagResponse DTO for representing Tag data
chiar17 Apr 6, 2026
a6a31f5
Add TagRequest DTO for representing Tag creation data
chiar17 Apr 6, 2026
c193162
Implement TagService for managing tags, including creation, deletion,…
chiar17 Apr 6, 2026
7cc2a8e
Refactor TagService and add custom exception classes for tag operations
chiar17 Apr 6, 2026
9ee80c2
Add custom tag management endpoints to WorkspaceMetadataController an…
chiar17 Apr 6, 2026
b235fc0
Add buildCustomTagBadges function and integrate custom tags into grid…
chiar17 Apr 6, 2026
b3512b0
Add click and keydown event handlers for custom tags in grid rows
chiar17 Apr 6, 2026
4cacf55
Add workspace tags component with tag management functionality
chiar17 Apr 6, 2026
de82a11
Add custom tag filter to workspace header component
chiar17 Apr 6, 2026
10d6a6b
Add import for workspace tags component and update custom tag filter …
chiar17 Apr 6, 2026
8497a53
Add filterCustomTag to uiState input hydration
chiar17 Apr 6, 2026
b9459a1
Add tagsController to manage tag updates and integrate with dataContr…
chiar17 Apr 6, 2026
f9cc201
Add filterCustomTag event listener to apply filters on change
chiar17 Apr 6, 2026
dc6722c
Add populateCustomTagSelect function and updateTestCaseTags to manage…
chiar17 Apr 6, 2026
2954407
Add fetchCustomTags function to retrieve custom tags and integrate wi…
chiar17 Apr 6, 2026
e6f81f0
Add pending value handling for custom tags in workspace UI state
chiar17 Apr 6, 2026
5eb3991
Add customTagId handling in workspace UI state for filter queries
chiar17 Apr 6, 2026
2eed4c2
Fix tag name input focus restoration in workspace tags component
chiar17 Apr 6, 2026
3e0982d
Add onTagListChanged parameter to createWorkspaceTags function
chiar17 Apr 6, 2026
a78ab12
Add onTagListChanged callback invocation in createWorkspaceTags function
chiar17 Apr 6, 2026
b236505
Invoke onTagListChanged callback after updating tags in createWorkspa…
chiar17 Apr 6, 2026
9ed3e65
Invoke onTagListChanged callback to load filter options in createWork…
chiar17 Apr 6, 2026
b1fc479
Rename "Tags" to "Type" in various components for improved clarity
chiar17 Apr 6, 2026
e1032bd
Enhance Tag functionality: add usage count to TagResponse, implement …
chiar17 Apr 6, 2026
6a40e20
Implement custom tag update functionality, enhance tag editing UI, an…
chiar17 Apr 6, 2026
7a9515d
Add custom tag display in inline preview row and update UI for clarity
chiar17 Apr 6, 2026
cf0581e
Add custom tags to test case details model for improved display
chiar17 Apr 6, 2026
a750c3a
Add tags section to test case details for improved organization and d…
chiar17 Apr 6, 2026
6f1c0e2
Import createWorkspaceTags component for tag management integration
chiar17 Apr 6, 2026
e1bd04e
Implement tag management functions to handle assigned tags and update UI
chiar17 Apr 6, 2026
b5c9ef7
Add method to find test cases by team key and work keys for enhanced …
chiar17 Apr 6, 2026
16cfd68
Add bulk custom tags endpoint to WorkspaceMetadataController for enha…
chiar17 Apr 6, 2026
cb7fe7a
Add BulkTagRequest class for handling bulk tag operations
chiar17 Apr 6, 2026
d48986b
Add bulkSetTag method to TagService for managing tags across multiple…
chiar17 Apr 6, 2026
52fa0e4
Add workspace bulk tag assignment functionality with popover UI
chiar17 Apr 6, 2026
eac2c85
Add bulk tag assignment functionality to workspace page
chiar17 Apr 6, 2026
5c210ce
Add Tag button to bulk action bar for enhanced tag management
chiar17 Apr 6, 2026
9246473
Add color swatches and create tag functionality to bulk tag assignmen…
chiar17 Apr 6, 2026
8dcf97f
Add query method to retrieve TestCase with custom tags by team and wo…
chiar17 Apr 6, 2026
4477371
Add integration tests for custom tag API endpoints
chiar17 Apr 6, 2026
e27e492
Refactor test assertions to use updated repository method for retriev…
chiar17 Apr 6, 2026
e9eddc7
Merge branch 'main' into 20-tags
chiar17 Apr 7, 2026
9d4ba0b
Update src/main/java/com/formswim/teststream/workspace/services/TagSe…
kendymann Apr 7, 2026
f28c8e9
Update src/main/java/com/formswim/teststream/shared/domain/TagReposit…
kendymann Apr 7, 2026
3d42445
Fixed merge conflicts
Bazinator Apr 7, 2026
7ab4634
Changed to export All, and square button
Bazinator Apr 7, 2026
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
67 changes: 67 additions & 0 deletions src/main/java/com/formswim/teststream/shared/domain/Tag.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
package com.formswim.teststream.shared.domain;

import com.fasterxml.jackson.annotation.JsonIgnore;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.Table;
import jakarta.persistence.UniqueConstraint;

@Entity
@Table(
name = "custom_tags",
uniqueConstraints = {
@UniqueConstraint(name = "uk_custom_tags_team_name", columnNames = { "team_key", "name" })
}
)
public class Tag {

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;

@Column(nullable = false, length = 50)
private String name;

@Column(nullable = false, length = 7)
private String color;

@Column(name = "team_key", nullable = false, length = 100)
private String teamKey;

protected Tag() {
}

public Tag(String name, String color, String teamKey) {
this.name = name;
this.color = color;
this.teamKey = teamKey;
}

public Long getId() {
return id;
}

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

public String getColor() {
return color;
}

public void setColor(String color) {
this.color = color;
}

@JsonIgnore
public String getTeamKey() {
return teamKey;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package com.formswim.teststream.shared.domain;

import java.util.List;
import java.util.Optional;

import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Modifying;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import org.springframework.stereotype.Repository;

@Repository
public interface TagRepository extends JpaRepository<Tag, Long> {

List<Tag> findByTeamKeyOrderByNameAsc(String teamKey);

Optional<Tag> findByTeamKeyAndId(String teamKey, Long id);

boolean existsByTeamKeyAndName(String teamKey, String name);

@Query(value = "SELECT tag_id, COUNT(*) FROM test_case_custom_tags WHERE tag_id IN (:tagIds) GROUP BY tag_id", nativeQuery = true)
List<Object[]> countUsageByTagIds(@Param("tagIds") List<Long> tagIds);

@Modifying
@Query(value = "DELETE FROM test_case_custom_tags WHERE tag_id = :tagId", nativeQuery = true)
void deleteFromJoinTableByTagId(@Param("tagId") Long tagId);
}
23 changes: 23 additions & 0 deletions src/main/java/com/formswim/teststream/shared/domain/TestCase.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,20 @@
import jakarta.persistence.CascadeType;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.FetchType;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.JoinColumn;
import jakarta.persistence.JoinTable;
import jakarta.persistence.ManyToMany;
import jakarta.persistence.OneToMany;
import jakarta.persistence.OrderBy;
import jakarta.persistence.Table;
import jakarta.persistence.UniqueConstraint;

import org.hibernate.annotations.BatchSize;

import java.util.ArrayList;
import java.util.List;

Expand Down Expand Up @@ -104,6 +110,15 @@ public class TestCase {
@OrderBy("stepNumber ASC")
private List<TestStep> steps = new ArrayList<>();

@ManyToMany(fetch = FetchType.LAZY)
@BatchSize(size = 50)
@JoinTable(
name = "test_case_custom_tags",
joinColumns = @JoinColumn(name = "test_case_id"),
inverseJoinColumns = @JoinColumn(name = "tag_id")
)
private List<Tag> customTags = new ArrayList<>();

protected TestCase() {
}

Expand Down Expand Up @@ -351,4 +366,12 @@ public void setFlakyScore(String flakyScore) {
public List<TestStep> getSteps() {
return steps;
}

public List<Tag> getCustomTags() {
return customTags;
}

public void setCustomTags(List<Tag> customTags) {
this.customTags = customTags;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,10 @@ public interface TestCaseRepository extends JpaRepository<TestCase, Long> {
and (:tag is null or :tag = ''
or lower(coalesce(testCase.components, '')) like lower(concat('%', :tag, '%'))
or lower(coalesce(testCase.testCaseType, '')) like lower(concat('%', :tag, '%')))
and (
and (:customTagId is null or exists (
select 1 from testCase.customTags ct where ct.id = :customTagId
))
and (
Comment thread
Bazinator marked this conversation as resolved.
:folder is null or :folder = ''
or lower(trim(function('replace', coalesce(testCase.folder, ''), '\\', '/'))) = lower(:folder)
or lower(trim(function('replace', coalesce(testCase.folder, ''), '\\', '/'))) like lower(concat(:folder, '/%'))
Expand All @@ -45,6 +48,10 @@ or exists (
where testStep.testCase = testCase
and lower(coalesce(testStep.stepSummary, '')) like lower(concat('%', :search, '%'))
)
or exists (
select 1 from testCase.customTags ct
where lower(ct.name) like lower(concat('%', :search, '%'))
)
Comment on lines 49 to +54

Copilot AI Apr 7, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Custom tag names are now included in the full-text search filter via an exists subquery on testCase.customTags. This is user-facing behavior and should be covered by an integration test (e.g., searching by a custom tag name returns the tagged case).

Copilot uses AI. Check for mistakes.
)
order by testCase.id desc
""",
Expand All @@ -57,7 +64,10 @@ select count(distinct testCase.id)
and (:tag is null or :tag = ''
or lower(coalesce(testCase.components, '')) like lower(concat('%', :tag, '%'))
or lower(coalesce(testCase.testCaseType, '')) like lower(concat('%', :tag, '%')))
and (
and (:customTagId is null or exists (
select 1 from testCase.customTags ct where ct.id = :customTagId
))
and (
:folder is null or :folder = ''
or lower(trim(function('replace', coalesce(testCase.folder, ''), '\\', '/'))) = lower(:folder)
or lower(trim(function('replace', coalesce(testCase.folder, ''), '\\', '/'))) like lower(concat(:folder, '/%'))
Expand All @@ -75,6 +85,10 @@ or exists (
where testStep.testCase = testCase
and lower(coalesce(testStep.stepSummary, '')) like lower(concat('%', :search, '%'))
)
or exists (
select 1 from testCase.customTags ct
where lower(ct.name) like lower(concat('%', :search, '%'))
)
)
"""
)
Expand All @@ -83,6 +97,7 @@ Page<Long> findWorkspaceCaseIdsByFilters(@Param("teamKey") String teamKey,
@Param("status") String status,
@Param("component") String component,
@Param("tag") String tag,
@Param("customTagId") Long customTagId,
@Param("folder") String folder,
Pageable pageable);

Expand Down Expand Up @@ -133,6 +148,15 @@ int bulkMoveToFolderByTeamKeyAndWorkKeys(@Param("teamKey") String teamKey,
List<TestCase> findAllWithStepsByTeamKeyAndWorkKeyIn(String teamKey,
Collection<String> workKeys);

@Query("""
select distinct testCase
from TestCase testCase
left join fetch testCase.customTags
where testCase.teamKey = :teamKey and testCase.workKey = :workKey
""")
Optional<TestCase> findWithCustomTagsByTeamKeyAndWorkKey(@Param("teamKey") String teamKey,
@Param("workKey") String workKey);

@Query("""
select distinct trim(function('replace', testCase.folder, '\\', '/'))
from TestCase testCase
Expand Down Expand Up @@ -235,4 +259,6 @@ int bulkRepathDescendants(@Param("teamKey") String teamKey,
@Param("suffixStartIndex") int suffixStartIndex);

List<TestCase> findByTeamKeyAndStatus(String teamKey, String status);

List<TestCase> findByTeamKeyAndWorkKeyIn(String teamKey, Collection<String> workKeys);
}
Loading
Loading