From 5f2ab4724f87953ac29a49314bfd84690a0c699b Mon Sep 17 00:00:00 2001 From: Arsenij Malov Date: Wed, 17 Jun 2026 15:47:13 +0200 Subject: [PATCH] Revert "Revert "[TICKETS] Delete legacy code (custom statuses) (#1187)" (#1192)" This reverts commit 3700bb718e7b88e8dc1ae110f72d3c48c1d84c2d. --- .../data/document/ticket/Ticket.java | 4 -- .../data/document/ticket/TicketStatus.java | 30 ---------- .../ticket/filter/TicketQueryFilter.java | 7 --- .../ticket/CustomTicketRepository.java | 5 -- .../ticket/CustomTicketRepositoryImpl.java | 45 -------------- .../repository/ticket/TicketRepository.java | 36 ----------- .../BackfillTicketOrdersChangeUnit.java | 60 ------------------- .../MigrateTicketStatusesChangeUnit.java | 15 ++--- 8 files changed, 5 insertions(+), 197 deletions(-) delete mode 100644 openframe-data-mongo-common/src/main/java/com/openframe/data/document/ticket/TicketStatus.java delete mode 100644 openframe-management-service-core/src/main/java/com/openframe/management/migration/BackfillTicketOrdersChangeUnit.java diff --git a/openframe-data-mongo-common/src/main/java/com/openframe/data/document/ticket/Ticket.java b/openframe-data-mongo-common/src/main/java/com/openframe/data/document/ticket/Ticket.java index ecbb871c3..c0bf0e9c5 100644 --- a/openframe-data-mongo-common/src/main/java/com/openframe/data/document/ticket/Ticket.java +++ b/openframe-data-mongo-common/src/main/java/com/openframe/data/document/ticket/Ticket.java @@ -26,8 +26,6 @@ @Document(collection = "tickets") @CompoundIndexes({ @CompoundIndex(name = "tenant_ticketNumber_idx", def = "{'tenantId':1,'ticketNumber':1}", unique = true), - // TODO(lifecycle-rollout): drop legacy status_order index after `status` field removal - @CompoundIndex(name = "status_order", def = "{'status': 1, 'order': 1}"), @CompoundIndex(name = "status_kind", def = "{'statusKind': 1}"), @CompoundIndex(name = "status_id_order", def = "{'statusId': 1, 'order': 1}") }) @@ -38,8 +36,6 @@ public class Ticket implements TenantScoped { private Integer ticketNumber; private String title; private String description; - // TODO(lifecycle-rollout): drop legacy status field once all reads/writes use statusKind/statusId - private TicketStatus status; private String statusId; private TicketStatusKind statusKind; private TicketCreationSource creationSource; diff --git a/openframe-data-mongo-common/src/main/java/com/openframe/data/document/ticket/TicketStatus.java b/openframe-data-mongo-common/src/main/java/com/openframe/data/document/ticket/TicketStatus.java deleted file mode 100644 index b3f797683..000000000 --- a/openframe-data-mongo-common/src/main/java/com/openframe/data/document/ticket/TicketStatus.java +++ /dev/null @@ -1,30 +0,0 @@ -package com.openframe.data.document.ticket; - -import java.util.Set; - -/** - * Status of a ticket (matches Figma flow). - * Separate from DialogStatus for feature flag isolation. - */ -// TODO(lifecycle-rollout): remove entire enum after legacy status field is dropped from Ticket -public enum TicketStatus { - ACTIVE, - TECH_REQUIRED, - ON_HOLD, - RESOLVED, - ARCHIVED; - - public boolean canTransitionTo(TicketStatus target) { - return getAllowedTransitions().contains(target); - } - - public Set getAllowedTransitions() { - return switch (this) { - case ACTIVE -> Set.of(TECH_REQUIRED, ON_HOLD, RESOLVED); - case TECH_REQUIRED -> Set.of(ACTIVE, ON_HOLD, RESOLVED); - case ON_HOLD -> Set.of(ACTIVE, TECH_REQUIRED, RESOLVED); - case RESOLVED -> Set.of(ARCHIVED); - case ARCHIVED -> Set.of(RESOLVED); - }; - } -} diff --git a/openframe-data-mongo-common/src/main/java/com/openframe/data/document/ticket/filter/TicketQueryFilter.java b/openframe-data-mongo-common/src/main/java/com/openframe/data/document/ticket/filter/TicketQueryFilter.java index f96f7e403..5c33e9d75 100644 --- a/openframe-data-mongo-common/src/main/java/com/openframe/data/document/ticket/filter/TicketQueryFilter.java +++ b/openframe-data-mongo-common/src/main/java/com/openframe/data/document/ticket/filter/TicketQueryFilter.java @@ -1,6 +1,5 @@ package com.openframe.data.document.ticket.filter; -import com.openframe.data.document.ticket.TicketStatus; import com.openframe.data.document.ticket.TicketStatusKind; import com.openframe.data.document.ticket.TicketCreationSource; import lombok.AllArgsConstructor; @@ -20,15 +19,9 @@ @NoArgsConstructor @AllArgsConstructor public class TicketQueryFilter { - // TODO(lifecycle-rollout): drop legacy `statuses` field after rollout - // ===== Legacy (lifecycle feature flag OFF) ===== - private List statuses; - - // ===== Lifecycle feature (lifecycle feature flag ON) ===== private List statusIds; private List statusKinds; - // ===== Shared ===== private List organizationIds; private List assigneeIds; private List labelIds; diff --git a/openframe-data-mongo-sync/src/main/java/com/openframe/data/repository/ticket/CustomTicketRepository.java b/openframe-data-mongo-sync/src/main/java/com/openframe/data/repository/ticket/CustomTicketRepository.java index b122f4803..9953b2494 100644 --- a/openframe-data-mongo-sync/src/main/java/com/openframe/data/repository/ticket/CustomTicketRepository.java +++ b/openframe-data-mongo-sync/src/main/java/com/openframe/data/repository/ticket/CustomTicketRepository.java @@ -1,7 +1,6 @@ package com.openframe.data.repository.ticket; import com.openframe.data.document.ticket.Ticket; -import com.openframe.data.document.ticket.TicketStatus; import com.openframe.data.document.ticket.TicketStatusKind; import com.openframe.data.document.ticket.filter.TicketQueryFilter; import org.springframework.data.mongodb.core.query.Query; @@ -24,8 +23,6 @@ List findTicketsWithCursor(Query query, String cursor, int limit, long countTickets(Query query); - Map countTicketsByStatus(); - Map countTicketsByStatusKind(); Map countTicketsByStatusId(); @@ -34,8 +31,6 @@ List findTicketsWithCursor(Query query, String cursor, int limit, Optional getAverageResolutionTimeMs(); - int updateStatusBulk(TicketStatus fromStatus, TicketStatus toStatus); - int reassignTicketsToStatus(String fromStatusId, String toStatusId, TicketStatusKind toKind); void updateTitle(String ticketId, String title); diff --git a/openframe-data-mongo-sync/src/main/java/com/openframe/data/repository/ticket/CustomTicketRepositoryImpl.java b/openframe-data-mongo-sync/src/main/java/com/openframe/data/repository/ticket/CustomTicketRepositoryImpl.java index 3646e4c9c..43d8b9729 100644 --- a/openframe-data-mongo-sync/src/main/java/com/openframe/data/repository/ticket/CustomTicketRepositoryImpl.java +++ b/openframe-data-mongo-sync/src/main/java/com/openframe/data/repository/ticket/CustomTicketRepositoryImpl.java @@ -1,7 +1,6 @@ package com.openframe.data.repository.ticket; import com.openframe.data.document.ticket.Ticket; -import com.openframe.data.document.ticket.TicketStatus; import com.openframe.data.document.ticket.TicketStatusKind; import com.openframe.data.document.ticket.filter.TicketQueryFilter; import com.openframe.data.mongo.TenantAwareMongoTemplate; @@ -32,7 +31,6 @@ public class CustomTicketRepositoryImpl extends TenantAwareRepositorySupport imp private static final String ID_FIELD = "_id"; private static final String DEFAULT_SORT_FIELD = "_id"; - private static final String FIELD_STATUS = "status"; private static final String FIELD_STATUS_ID = "statusId"; private static final String FIELD_STATUS_KIND = "statusKind"; private static final String FIELD_TICKET_NUMBER = "ticketNumber"; @@ -57,7 +55,6 @@ public class CustomTicketRepositoryImpl extends TenantAwareRepositorySupport imp private static final List SORTABLE_FIELDS = List.of( ID_FIELD, FIELD_TICKET_NUMBER, - FIELD_STATUS, FIELD_STATUS_KIND, FIELD_ORGANIZATION_NAME, FIELD_ASSIGNED_NAME, @@ -78,7 +75,6 @@ public Query buildTicketQuery(TicketQueryFilter filter, String search, Query query = new Query(); if (filter != null) { - addCriteriaIfNotEmpty(query, FIELD_STATUS, filter.getStatuses()); addCriteriaIfNotEmpty(query, FIELD_STATUS_ID, filter.getStatusIds()); addCriteriaIfNotEmpty(query, FIELD_STATUS_KIND, filter.getStatusKinds()); addCriteriaIfNotEmpty(query, FIELD_ORGANIZATION_ID, filter.getOrganizationIds()); @@ -206,7 +202,6 @@ private Criteria buildIdCriteria(ObjectId cursorId, boolean isDesc) { private Object getSortFieldValue(Ticket ticket, String sortField) { return switch (sortField) { case FIELD_TICKET_NUMBER -> ticket.getTicketNumber(); - case FIELD_STATUS -> ticket.getStatus() != null ? ticket.getStatus().name() : null; case FIELD_STATUS_KIND -> ticket.getStatusKind() != null ? ticket.getStatusKind().name() : null; case FIELD_ORGANIZATION_NAME -> ticket.getOrganizationName(); case FIELD_ASSIGNED_NAME -> ticket.getAssignedName(); @@ -224,33 +219,6 @@ public long countTickets(Query query) { return mongoTemplate.count(query, Ticket.class); } - @Override - public Map countTicketsByStatus() { - Aggregation aggregation = Aggregation.newAggregation( - Aggregation.match(tenantCriteria()), - Aggregation.group(FIELD_STATUS).count().as(AGG_COUNT), - Aggregation.project(AGG_COUNT).and(ID_FIELD).as(FIELD_STATUS) - ); - - AggregationResults results = mongoTemplate.aggregate( - aggregation, Ticket.class, Document.class); - - Map statusCounts = new EnumMap<>(TicketStatus.class); - for (Document doc : results.getMappedResults()) { - String statusStr = doc.getString(FIELD_STATUS); - if (statusStr != null) { - try { - TicketStatus status = TicketStatus.valueOf(statusStr); - statusCounts.put(status, doc.getInteger(AGG_COUNT).longValue()); - } catch (IllegalArgumentException e) { - log.warn("Unknown ticket status: {}", statusStr); - } - } - } - - return statusCounts; - } - @Override public Map countTicketsByStatusKind() { Aggregation aggregation = Aggregation.newAggregation( @@ -328,19 +296,6 @@ public Optional getAverageResolutionTimeMs() { return Optional.empty(); } - @Override - public int updateStatusBulk(TicketStatus fromStatus, TicketStatus toStatus) { - Query query = new Query(Criteria.where(FIELD_STATUS).is(fromStatus)); - Update update = new Update() - .set(FIELD_STATUS, toStatus) - .set(FIELD_UPDATED_AT, Instant.now()); - - long modifiedCount = mongoTemplate.updateMulti(query, update, Ticket.class).getModifiedCount(); - log.debug("Bulk status update: {} -> {}, modified: {}", fromStatus, toStatus, modifiedCount); - - return (int) modifiedCount; - } - @Override public int reassignTicketsToStatus(String fromStatusId, String toStatusId, TicketStatusKind toKind) { Query query = new Query(Criteria.where(FIELD_STATUS_ID).is(fromStatusId)); diff --git a/openframe-data-mongo-sync/src/main/java/com/openframe/data/repository/ticket/TicketRepository.java b/openframe-data-mongo-sync/src/main/java/com/openframe/data/repository/ticket/TicketRepository.java index ad7e71e94..7f3fe90b6 100644 --- a/openframe-data-mongo-sync/src/main/java/com/openframe/data/repository/ticket/TicketRepository.java +++ b/openframe-data-mongo-sync/src/main/java/com/openframe/data/repository/ticket/TicketRepository.java @@ -1,7 +1,6 @@ package com.openframe.data.repository.ticket; import com.openframe.data.document.ticket.Ticket; -import com.openframe.data.document.ticket.TicketStatus; import com.openframe.data.repository.TenantAwareRepository; import org.springframework.data.mongodb.repository.Aggregation; import org.springframework.data.mongodb.repository.MongoRepository; @@ -15,13 +14,8 @@ @TenantAwareRepository public interface TicketRepository extends MongoRepository, CustomTicketRepository { - // TODO(lifecycle-rollout): drop legacy methods (by enum TicketStatus) after rollout - // ===== Legacy (used when lifecycle feature flag is OFF) ===== - Optional findByTicketNumber(Integer ticketNumber); - List findByStatus(TicketStatus status); - List findByOrganizationId(String organizationId); List findByAssignedTo(String assignedTo); @@ -33,36 +27,6 @@ public interface TicketRepository extends MongoRepository, Custo @Query("{ '_id': ?0, 'owner.machineId': ?1 }") Optional findByIdAndOwnerMachineId(String id, String machineId); - @Aggregation(pipeline = { - "{ $match: { 'tenantId': ?1, 'status': ?0, 'order': { $ne: null } } }", - "{ $sort: { 'order': 1 } }", - "{ $limit: 1 }" - }) - Optional findFirstInColumn(TicketStatus status, String tenantId); - - @Aggregation(pipeline = { - "{ $match: { 'tenantId': ?1, 'status': ?0, 'order': { $ne: null } } }", - "{ $sort: { 'order': -1 } }", - "{ $limit: 1 }" - }) - Optional findLastInColumn(TicketStatus status, String tenantId); - - @Aggregation(pipeline = { - "{ $match: { 'tenantId': ?2, 'status': ?0, 'order': { $gt: ?1 } } }", - "{ $sort: { 'order': 1 } }", - "{ $limit: 1 }" - }) - Optional findFirstAfter(TicketStatus status, String order, String tenantId); - - @Aggregation(pipeline = { - "{ $match: { 'tenantId': ?2, 'status': ?0, 'order': { $lt: ?1 } } }", - "{ $sort: { 'order': -1 } }", - "{ $limit: 1 }" - }) - Optional findFirstBefore(TicketStatus status, String order, String tenantId); - - // ===== Lifecycle feature (used when lifecycle feature flag is ON) ===== - long countByStatusId(String statusId); @Aggregation(pipeline = { diff --git a/openframe-management-service-core/src/main/java/com/openframe/management/migration/BackfillTicketOrdersChangeUnit.java b/openframe-management-service-core/src/main/java/com/openframe/management/migration/BackfillTicketOrdersChangeUnit.java deleted file mode 100644 index aca842840..000000000 --- a/openframe-management-service-core/src/main/java/com/openframe/management/migration/BackfillTicketOrdersChangeUnit.java +++ /dev/null @@ -1,60 +0,0 @@ -package com.openframe.management.migration; - -import com.github.pravin.raha.lexorank4j.LexoRank; -import com.openframe.data.document.ticket.Ticket; -import com.openframe.data.document.ticket.TicketStatus; -import com.openframe.data.service.TenantIdProvider; -import io.mongock.api.annotations.ChangeUnit; -import io.mongock.api.annotations.Execution; -import io.mongock.api.annotations.RollbackExecution; -import lombok.extern.slf4j.Slf4j; -import org.springframework.data.domain.Sort; -import org.springframework.data.mongodb.core.MongoTemplate; -import org.springframework.data.mongodb.core.query.Criteria; -import org.springframework.data.mongodb.core.query.Query; -import org.springframework.data.mongodb.core.query.Update; - -import java.util.List; - -@Slf4j -@ChangeUnit(id = "backfill-ticket-orders", order = "002", author = "openframe") -public class BackfillTicketOrdersChangeUnit { - - private static final String STATUS_FIELD = "status"; - private static final String ORDER_FIELD = "order"; - private static final String CREATED_AT_FIELD = "createdAt"; - private static final String ID_FIELD = "_id"; - - @Execution - public void execution(MongoTemplate mongoTemplate, TenantIdProvider tenantIdProvider) { - String tenantId = tenantIdProvider.getTenantId(); - for (TicketStatus status : TicketStatus.values()) { - backfillColumn(mongoTemplate, status, tenantId); - } - } - - @RollbackExecution - public void rollback() { - } - - private void backfillColumn(MongoTemplate mongoTemplate, TicketStatus status, String tenantId) { - Query query = new Query(Criteria.where("tenantId").is(tenantId) - .and(STATUS_FIELD).is(status) - .and(ORDER_FIELD).is(null)); - query.with(Sort.by(Sort.Direction.DESC, CREATED_AT_FIELD)); - List tickets = mongoTemplate.find(query, Ticket.class); - - LexoRank rank = LexoRank.middle(); - for (Ticket ticket : tickets) { - assignOrder(mongoTemplate, ticket.getId(), rank.format()); - rank = rank.genNext(); - } - log.info("Backfilled order on {} tickets in status {}", tickets.size(), status); - } - - private void assignOrder(MongoTemplate mongoTemplate, String ticketId, String order) { - Query byId = new Query(Criteria.where(ID_FIELD).is(ticketId)); - Update update = new Update().set(ORDER_FIELD, order); - mongoTemplate.updateFirst(byId, update, Ticket.class); - } -} diff --git a/openframe-management-service-core/src/main/java/com/openframe/management/migration/MigrateTicketStatusesChangeUnit.java b/openframe-management-service-core/src/main/java/com/openframe/management/migration/MigrateTicketStatusesChangeUnit.java index eb40a5fd2..641f7356f 100644 --- a/openframe-management-service-core/src/main/java/com/openframe/management/migration/MigrateTicketStatusesChangeUnit.java +++ b/openframe-management-service-core/src/main/java/com/openframe/management/migration/MigrateTicketStatusesChangeUnit.java @@ -9,7 +9,6 @@ import io.mongock.api.annotations.RollbackExecution; import lombok.extern.slf4j.Slf4j; import org.bson.Document; -import org.springframework.core.env.Environment; import org.springframework.dao.DuplicateKeyException; import org.springframework.data.mongodb.core.MongoTemplate; import org.springframework.data.mongodb.core.query.Criteria; @@ -28,8 +27,10 @@ import static org.springframework.util.StringUtils.hasText; @Slf4j -// TODO(lifecycle-rollout): drop runAlways=true after flag is permanently on — body becomes a normal one-shot migration -@ChangeUnit(id = "migrate-ticket-status-model", order = "003", author = "openframe", runAlways = true) +// One-shot migration of any remaining legacy enum-status tickets to the custom-status model +// (statusId/statusKind). Reads the raw `status` field via bson Document, so it stays valid after +// the legacy field/enum are removed from the Ticket POJO. +@ChangeUnit(id = "migrate-ticket-status-model", order = "003", author = "openframe") public class MigrateTicketStatusesChangeUnit { private static final String FIELD_ID = "_id"; @@ -40,7 +41,6 @@ public class MigrateTicketStatusesChangeUnit { private static final String FIELD_LEGACY_STATUS = "status"; private static final String FIELD_UPDATED_AT = "updatedAt"; private static final String COLLECTION_TICKETS = "tickets"; - private static final String LIFECYCLE_FLAG = "openframe.features.tickets.lifecycle.enabled"; private static final String LEGACY_ON_HOLD = "ON_HOLD"; @@ -52,12 +52,7 @@ public class MigrateTicketStatusesChangeUnit { ); @Execution - public void execution(MongoTemplate mongoTemplate, Environment environment, TenantIdProvider tenantIdProvider) { - // TODO(lifecycle-rollout): remove flag guard + drop Environment param after rollout - if (!environment.getProperty(LIFECYCLE_FLAG, Boolean.class, false)) { - log.info("Migrate ticket statuses: lifecycle feature disabled; skipping"); - return; - } + public void execution(MongoTemplate mongoTemplate, TenantIdProvider tenantIdProvider) { log.info("Migrate ticket statuses: starting"); String tenantId = tenantIdProvider.getTenantId();