Skip to content

Commit

Permalink
Merge pull request #52 from hellokitty-coding-club/feature/#47-groupi…
Browse files Browse the repository at this point in the history
…ng-user-for-AB-Test

Grouping A/B Test User - used in SDUI
  • Loading branch information
great-park authored Oct 10, 2023
2 parents 89eb157 + b790771 commit d191cb9
Show file tree
Hide file tree
Showing 21 changed files with 124 additions and 38 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ public enum ResponseCode {
NOT_EXIST_MISSION(10200, HttpStatus.BAD_REQUEST, "존재하지 않는 미션입니다."),
INVALID_GITHUB_URL(10201, HttpStatus.BAD_REQUEST, "유효하지 않은 Github Repository URL입니다. public repository인 github 주소인지 확인해주세요."),
IMPOSSIBLE_TO_DELETE(10202, HttpStatus.BAD_REQUEST, "이미 참여한 사람이 있어 삭제할 수 없는 미션입니다."),
USER_AB_TEST_GROUP_NOT_FOUND(10203, HttpStatus.INTERNAL_SERVER_ERROR, "사용자의 AB Test Group을 찾을 수 없습니다."),

// 103xx : Mission Registration
MISS_REGISTER_DEADLINE(10300, HttpStatus.BAD_REQUEST, "미션 등록 기간이 이미 마감되었습니다."),
Expand All @@ -68,6 +69,7 @@ public enum ResponseCode {

// 107xx : Review(feedback)
NOT_EXIST_REVIEW_INTERNAL(10700, HttpStatus.INTERNAL_SERVER_ERROR, "DB 조회 과정에서 문제가 발생했습니다: 존재하지 않는 리뷰입니다.")

;

private final Integer code;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,19 +1,21 @@
package swm.hkcc.LGTM.app.modules.mission.constant;

import lombok.Getter;
import lombok.RequiredArgsConstructor;
import swm.hkcc.LGTM.app.modules.serverDrivenUI.ServerDrivenUISequenceByVersion;

import java.util.Arrays;
import java.util.List;
import java.util.Objects;
import java.util.Optional;

import static swm.hkcc.LGTM.app.modules.mission.constant.MissionContentType.*;

@Getter
@RequiredArgsConstructor
public enum HomeServerDrivenUISequenceByVersion implements ServerDrivenUISequenceByVersion {
V1_HOME_SERVER_DRIVEN_UI_SEQUENCE(
1,
A_HOME_SERVER_DRIVEN_UI_SEQUENCE(
"A",
List.of(ON_GOING_MISSION_TITLE_V1,
ON_GOING_MISSION_LIST_V1,
SECTION_DARK_CLOSER_V1,
Expand All @@ -25,8 +27,8 @@ public enum HomeServerDrivenUISequenceByVersion implements ServerDrivenUISequenc
SECTION_LIGHT_CLOSER_V1
)),

V2_HOME_SERVER_DRIVEN_UI_SEQUENCE(
2,
B_HOME_SERVER_DRIVEN_UI_SEQUENCE(
"B",
List.of(RECOMMENDED_MISSION_TITLE_V1,
RECOMMENDED_MISSION_LIST_V1,
SECTION_DARK_CLOSER_V1,
Expand All @@ -38,13 +40,13 @@ public enum HomeServerDrivenUISequenceByVersion implements ServerDrivenUISequenc
SECTION_LIGHT_CLOSER_V1
));

private final int version;
private final String ABTestGroupName;

private final List<MissionContentType> contents;

public static Optional<HomeServerDrivenUISequenceByVersion> findByVersion(int version) {
public static Optional<HomeServerDrivenUISequenceByVersion> find(String ABTestGroupName) {
return Arrays.stream(HomeServerDrivenUISequenceByVersion.values())
.filter(value -> value.version == version)
.filter(value -> Objects.equals(value.ABTestGroupName, ABTestGroupName))
.findFirst();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@

import lombok.Getter;

import swm.hkcc.LGTM.app.modules.serverDrivenUI.Theme;
import swm.hkcc.LGTM.app.modules.serverDrivenUI.ViewType;
import swm.hkcc.LGTM.app.modules.serverDrivenUI.constant.Theme;
import swm.hkcc.LGTM.app.modules.serverDrivenUI.constant.ViewType;

@Getter
public enum MissionContentType {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@

import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
Expand All @@ -11,6 +10,9 @@
import swm.hkcc.LGTM.app.modules.member.domain.custom.CustomUserDetails;
import swm.hkcc.LGTM.app.modules.mission.service.HomeService;
import swm.hkcc.LGTM.app.modules.serverDrivenUI.ServerDrivenScreenResponse;
import swm.hkcc.LGTM.app.modules.serverDrivenUI.domain.ABTestService;

import static swm.hkcc.LGTM.app.modules.serverDrivenUI.constant.ABTest.*;

@Slf4j
@RestController
Expand All @@ -19,16 +21,17 @@
public class HomeController {

private final HomeService homeService;
private final ABTestService abTestService;

@GetMapping
public ApiDataResponse<ServerDrivenScreenResponse> getHomeScreen(
@AuthenticationPrincipal CustomUserDetails customUserDetails
) {
Long memberId = customUserDetails.getMemberId();
int currentVersion = 1;
String ABTestGroupName = abTestService.getGroupName(memberId, HOME_SCREEN_SEQUENCE_TEST.getTestName()); // 해당 유저가 현재 Test에 대해서 어떤 그룹에 속하는지 확인

return ApiDataResponse.of(
homeService.getHomeScreen(memberId, currentVersion)
homeService.getHomeScreen(memberId, ABTestGroupName)
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
import swm.hkcc.LGTM.app.modules.mission.dto.MissionEmptyViewTypeDto;
import swm.hkcc.LGTM.app.modules.mission.service.MissionService;
import swm.hkcc.LGTM.app.modules.serverDrivenUI.ServerDrivenContent;
import swm.hkcc.LGTM.app.modules.serverDrivenUI.ViewType;
import swm.hkcc.LGTM.app.modules.serverDrivenUI.constant.ViewType;

import java.util.function.Function;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@
@Component
public class HomeServerDrivenUISequenceFactory implements ServerDrivenUISequenceFactory {
@Override
public MissionContentSequence getServerDrivenUISequence(int version) {
return HomeServerDrivenUISequenceByVersion.findByVersion(version)
public MissionContentSequence getServerDrivenUISequence(String ABTestGroupName) {
return HomeServerDrivenUISequenceByVersion.find(ABTestGroupName)
.map(HomeServerDrivenUISequenceByVersion::getContents)
.map(MissionContentSequence::new)
.orElseThrow(() -> new GeneralException(ResponseCode.DATA_ACCESS_ERROR));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
@Component
public class MissionProcessServerDrivenUISequenceFactory implements ServerDrivenUISequenceFactory {
@Override
public MissionContentSequence getServerDrivenUISequence(int version) {
public MissionContentSequence getServerDrivenUISequence(String ABTestGroupName) {
// todo: implement this method
return null;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,5 @@
import swm.hkcc.LGTM.app.modules.serverDrivenUI.ServerDrivenScreenResponse;

public interface HomeService {
ServerDrivenScreenResponse getHomeScreen(Long memberId, int version);
ServerDrivenScreenResponse getHomeScreen(Long memberId, String ABTestGroupName);
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
import swm.hkcc.LGTM.app.modules.serverDrivenUI.ServerDrivenContent;
import swm.hkcc.LGTM.app.modules.serverDrivenUI.ServerDrivenContents;
import swm.hkcc.LGTM.app.modules.serverDrivenUI.ServerDrivenScreenResponse;
import swm.hkcc.LGTM.app.modules.serverDrivenUI.ViewType;
import swm.hkcc.LGTM.app.modules.serverDrivenUI.constant.ViewType;

import java.util.ArrayList;
import java.util.List;
Expand All @@ -34,8 +34,8 @@ public class HomeServiceImpl implements HomeService{

@Override
@Transactional(readOnly = true)
public ServerDrivenScreenResponse getHomeScreen(Long memberId, int version) {
MissionContentSequence contentSequence = sequenceFactory.getServerDrivenUISequence(version);
public ServerDrivenScreenResponse getHomeScreen(Long memberId, String ABTestGroupName) {
MissionContentSequence contentSequence = sequenceFactory.getServerDrivenUISequence(ABTestGroupName);
List<ServerDrivenContent> serverDrivenContentList = new ArrayList<>();

contentSequence.getMissionContents()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,8 @@

import lombok.Builder;
import lombok.Getter;
import swm.hkcc.LGTM.app.modules.mission.dto.MissionDetailsDto;
import swm.hkcc.LGTM.app.modules.mission.dto.MissionDto;
import swm.hkcc.LGTM.app.modules.mission.dto.MissionTitleDto;
import swm.hkcc.LGTM.app.modules.serverDrivenUI.constant.Theme;
import swm.hkcc.LGTM.app.modules.serverDrivenUI.constant.ViewType;

@Builder
@Getter
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package swm.hkcc.LGTM.app.modules.serverDrivenUI.constant;

import lombok.Getter;
import lombok.RequiredArgsConstructor;

@Getter
@RequiredArgsConstructor
public enum ABTest {
HOME_SCREEN_SEQUENCE_TEST("homeScreenSequenceTest"), // 홈화면에서 어떤 view type들의 순서가 success metric 을 향상시키는지 테스트
HOME_SCREEN_RECOMMENDATION_FEATURE_TEST("homeScreenRecommendationFeatureTest"), // 홈화면에서 어떤 추천 기능이 success metric 을 향상시키는지 테스트
;
private final String testName;
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package swm.hkcc.LGTM.app.modules.serverDrivenUI;
package swm.hkcc.LGTM.app.modules.serverDrivenUI.constant;

import lombok.Getter;
import lombok.RequiredArgsConstructor;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package swm.hkcc.LGTM.app.modules.serverDrivenUI;
package swm.hkcc.LGTM.app.modules.serverDrivenUI.constant;

import lombok.Getter;
import lombok.RequiredArgsConstructor;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package swm.hkcc.LGTM.app.modules.serverDrivenUI.domain;

import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Service
@RequiredArgsConstructor
@Transactional
public class ABTestService {
private final ABTestUserGroupRepository abTestUserGroupRepository;

public String getGroupName(Long memberId, String testName) {
return abTestUserGroupRepository.findGroupNameByMemberIdAndTestName(memberId, testName)
.orElse("A");
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package swm.hkcc.LGTM.app.modules.serverDrivenUI.domain;

import jakarta.persistence.*;
import lombok.*;

@Getter
@Builder
@ToString
@AllArgsConstructor
@NoArgsConstructor
@Entity
public class ABTestUserGroup {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long abTestUserGroupId;

@Column(nullable = false)
private Long memberId;

@Column(nullable = false)
private String testName;

@Column(nullable = false)
private String groupName;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package swm.hkcc.LGTM.app.modules.serverDrivenUI.domain;

import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;

import java.util.Optional;

public interface ABTestUserGroupRepository extends JpaRepository<ABTestUserGroup, Long> {
@Query("select a.groupName from ABTestUserGroup a where a.memberId = ?1 and a.testName = ?2")
Optional<String> findGroupNameByMemberIdAndTestName(Long memberId, String testName);

}
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,5 @@

public interface ServerDrivenUISequenceFactory {

MissionContentSequence getServerDrivenUISequence(int version);
MissionContentSequence getServerDrivenUISequence(String ABTestGroupName);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package swm.hkcc.LGTM.app.modules.serverDrivenUI.exception;

import swm.hkcc.LGTM.app.global.constant.ResponseCode;
import swm.hkcc.LGTM.app.global.exception.GeneralException;

public class UserABTestGroupNotFound extends GeneralException {
public UserABTestGroupNotFound() {
super(ResponseCode.USER_AB_TEST_GROUP_NOT_FOUND);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
import swm.hkcc.LGTM.app.modules.mission.dto.MissionEmptyViewTypeDto;
import swm.hkcc.LGTM.app.modules.mission.service.MissionService;
import swm.hkcc.LGTM.app.modules.serverDrivenUI.ServerDrivenContent;
import swm.hkcc.LGTM.app.modules.serverDrivenUI.ViewType;
import swm.hkcc.LGTM.app.modules.serverDrivenUI.constant.ViewType;

import java.util.function.Function;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,29 +27,29 @@ public void setup() {
}

@Test
@DisplayName("valid한 version을 입력했을 때, 해당하는 MissionContentSequence를 반환한다.")
@DisplayName("valid한 groupName을 입력했을 때, 해당하는 MissionContentSequence를 반환한다.")
public void testGetServerDrivenUISequenceByVersion_ValidVersion() {
// Given
int validVersion = 1;
List<MissionContentType> contents = HomeServerDrivenUISequenceByVersion.V1_HOME_SERVER_DRIVEN_UI_SEQUENCE.getContents();
String validGroupName = "A";
List<MissionContentType> contents = HomeServerDrivenUISequenceByVersion.A_HOME_SERVER_DRIVEN_UI_SEQUENCE.getContents();

// When
MissionContentSequence result = homeServerDrivenUISequenceFactory.getServerDrivenUISequence(validVersion);
MissionContentSequence result = homeServerDrivenUISequenceFactory.getServerDrivenUISequence(validGroupName);

// Then
assertNotNull(result);
assertEquals(contents, result.getMissionContents());
}

@Test
@DisplayName("invalid한 version을 입력했을 때, Exception이 발생한다.")
@DisplayName("invalid한 groupName을 입력했을 때, Exception이 발생한다.")
public void testGetServerDrivenUISequenceByVersion_InvalidVersion() {
// Given
int invalidVersion = -1;
String invalidGroupName = "invalidGroupName";

// When & Then
assertThrows(GeneralException.class,
() -> homeServerDrivenUISequenceFactory.getServerDrivenUISequence(invalidVersion),
() -> homeServerDrivenUISequenceFactory.getServerDrivenUISequence(invalidGroupName),
ResponseCode.DATA_ACCESS_ERROR.getMessage());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@
import swm.hkcc.LGTM.app.modules.mission.dto.MissionDto;
import swm.hkcc.LGTM.app.modules.serverDrivenUI.ServerDrivenContent;
import swm.hkcc.LGTM.app.modules.serverDrivenUI.ServerDrivenScreenResponse;
import swm.hkcc.LGTM.app.modules.serverDrivenUI.ViewType;
import swm.hkcc.LGTM.app.modules.serverDrivenUI.constant.ABTest;
import swm.hkcc.LGTM.app.modules.serverDrivenUI.constant.ViewType;

import java.util.List;

Expand Down Expand Up @@ -52,9 +53,11 @@ void setUp() {
void getHomeScreen_WithNonEmptyContents() {
// given
Long memberId = 1L;
int version = 1;
String ABTestGroupName = ABTest.HOME_SCREEN_SEQUENCE_TEST.getTestName();

MemberType memberType = MemberType.JUNIOR;


List<MissionContentType> mockMissionContentTypeList = List.of(
MissionContentType.ON_GOING_MISSION_TITLE_V1,
MissionContentType.ON_GOING_MISSION_LIST_V1,
Expand All @@ -67,7 +70,7 @@ void getHomeScreen_WithNonEmptyContents() {
MissionContentType.SECTION_LIGHT_CLOSER_V1
);
MissionContentSequence mockContentSequence = new MissionContentSequence(mockMissionContentTypeList);
when(sequenceFactory.getServerDrivenUISequence(version)).thenReturn(mockContentSequence);
when(sequenceFactory.getServerDrivenUISequence(ABTestGroupName)).thenReturn(mockContentSequence);
when(memberService.getMemberType(memberId)).thenReturn(memberType);

MissionContentData ongoingMissionContent = MissionContentData.of(List.of(createMockMissionDto(), createMockMissionDto()));
Expand All @@ -83,7 +86,7 @@ void getHomeScreen_WithNonEmptyContents() {


// when
ServerDrivenScreenResponse response = homeService.getHomeScreen(memberId, version);
ServerDrivenScreenResponse response = homeService.getHomeScreen(memberId, ABTestGroupName);

// then
assertThat(response).isNotNull();
Expand Down

0 comments on commit d191cb9

Please sign in to comment.