diff --git a/backend/src/main/java/heartbeat/client/BuildKiteFeignClient.java b/backend/src/main/java/heartbeat/client/BuildKiteFeignClient.java index f2b26be7b9..74e30c2b61 100644 --- a/backend/src/main/java/heartbeat/client/BuildKiteFeignClient.java +++ b/backend/src/main/java/heartbeat/client/BuildKiteFeignClient.java @@ -8,6 +8,7 @@ import java.util.List; +import org.springframework.cache.annotation.Cacheable; import org.springframework.cloud.openfeign.FeignClient; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; @@ -21,14 +22,17 @@ @FeignClient(name = "buildKiteFeignClient", url = "${buildKite.url}", configuration = BuildKiteFeignClientDecoder.class) public interface BuildKiteFeignClient { + @Cacheable(cacheNames = "tokenInfo", key = "#token") @GetMapping(path = "v2/access-token") @ResponseStatus(HttpStatus.OK) BuildKiteTokenInfo getTokenInfo(@RequestHeader("Authorization") String token); + @Cacheable(cacheNames = "buildKiteOrganizationInfo", key = "#token") @GetMapping(path = "v2/organizations") @ResponseStatus(HttpStatus.OK) List getBuildKiteOrganizationsInfo(@RequestHeader("Authorization") String token); + @Cacheable(cacheNames = "pipelineInfo", key = "#organizationId+'-'+#page+'-'+#perPage+'-'+#startTime+'-'+#endTime") @GetMapping(path = "v2/organizations/{organizationId}/pipelines?page={page}&per_page={perPage}") @ResponseStatus(HttpStatus.OK) List getPipelineInfo(@RequestHeader("Authorization") String token, @@ -43,6 +47,9 @@ ResponseEntity> getPipelineSteps(@RequestHeader("Author @RequestParam("per_page") String perPage, @RequestParam("created_from") String createdFrom, @RequestParam("created_to") String createdTo, @RequestParam("branch[]") List branch); + @Cacheable(cacheNames = "pipelineStepsInfo", + key = "#organizationId+'-'+#pipelineId+'-'+#page+'-'+#perPage+'-'" + + "+#createdFrom+'-'+#createdTo+'-'+(#branch!=null ? branch.toString() : '')") @GetMapping(path = "v2/organizations/{organizationId}/pipelines/{pipelineId}/builds", consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE) @ResponseStatus(HttpStatus.OK) diff --git a/backend/src/main/java/heartbeat/client/GitHubFeignClient.java b/backend/src/main/java/heartbeat/client/GitHubFeignClient.java index 75ad40fc57..2b6548292b 100644 --- a/backend/src/main/java/heartbeat/client/GitHubFeignClient.java +++ b/backend/src/main/java/heartbeat/client/GitHubFeignClient.java @@ -5,6 +5,7 @@ import heartbeat.client.dto.codebase.github.GitHubRepo; import heartbeat.client.dto.codebase.github.PullRequestInfo; import heartbeat.decoder.GitHubFeignClientDecoder; +import org.springframework.cache.annotation.Cacheable; import org.springframework.cloud.openfeign.FeignClient; import org.springframework.http.HttpStatus; import org.springframework.web.bind.annotation.GetMapping; @@ -24,32 +25,38 @@ public interface GitHubFeignClient { void verifyCanReadTargetBranch(@PathVariable String repository, @PathVariable String branchName, @RequestHeader("Authorization") String token); + @Cacheable(cacheNames = "githubOrganizationInfo", key = "#token") @GetMapping(path = "/user/orgs") @ResponseStatus(HttpStatus.OK) @Deprecated List getGithubOrganizationsInfo(@RequestHeader("Authorization") String token); + @Cacheable(cacheNames = "githubAllRepos", key = "#token") @GetMapping(path = "/user/repos") @ResponseStatus(HttpStatus.OK) @Deprecated List getAllRepos(@RequestHeader("Authorization") String token); + @Cacheable(cacheNames = "githubRepos", key = "#organizationName") @GetMapping(path = "/orgs/{organizationName}/repos") @ResponseStatus(HttpStatus.OK) @Deprecated List getReposByOrganizationName(@PathVariable String organizationName, @RequestHeader("Authorization") String token); + @Cacheable(cacheNames = "commitInfo", key = "#repository+'-'+#commitId") @GetMapping(path = "/repos/{repository}/commits/{commitId}") @ResponseStatus(HttpStatus.OK) CommitInfo getCommitInfo(@PathVariable String repository, @PathVariable String commitId, @RequestHeader("Authorization") String token); + @Cacheable(cacheNames = "pullRequestCommitInfo", key = "#repository+'-'+#mergedPullNumber") @GetMapping(path = "/repos/{repository}/pulls/{mergedPullNumber}/commits") @ResponseStatus(HttpStatus.OK) List getPullRequestCommitInfo(@PathVariable String repository, @PathVariable String mergedPullNumber, @RequestHeader("Authorization") String token); + @Cacheable(cacheNames = "pullRequestListInfo", key = "#repository+'-'+#deployId") @GetMapping(path = "/repos/{repository}/commits/{deployId}/pulls") @ResponseStatus(HttpStatus.OK) List getPullRequestListInfo(@PathVariable String repository, @PathVariable String deployId, diff --git a/backend/src/main/java/heartbeat/client/HolidayFeignClient.java b/backend/src/main/java/heartbeat/client/HolidayFeignClient.java index 51ba6c75a9..e8a73b3db1 100644 --- a/backend/src/main/java/heartbeat/client/HolidayFeignClient.java +++ b/backend/src/main/java/heartbeat/client/HolidayFeignClient.java @@ -2,6 +2,7 @@ import heartbeat.client.dto.board.jira.HolidaysResponseDTO; import heartbeat.config.HolidayFeignClientConfiguration; +import org.springframework.cache.annotation.Cacheable; import org.springframework.cloud.openfeign.FeignClient; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; @@ -10,6 +11,7 @@ configuration = HolidayFeignClientConfiguration.class) public interface HolidayFeignClient { + @Cacheable(cacheNames = "holidayResult", key = "#year") @GetMapping(path = "/{year}.json") HolidaysResponseDTO getHolidays(@PathVariable String year); diff --git a/backend/src/main/java/heartbeat/client/JiraFeignClient.java b/backend/src/main/java/heartbeat/client/JiraFeignClient.java index 1e62c0c44f..0cdf8aeb63 100644 --- a/backend/src/main/java/heartbeat/client/JiraFeignClient.java +++ b/backend/src/main/java/heartbeat/client/JiraFeignClient.java @@ -28,6 +28,7 @@ JiraBoardConfigDTO getJiraBoardConfiguration(URI baseUrl, @PathVariable String b StatusSelfDTO getColumnStatusCategory(URI baseUrl, @PathVariable String statusNum, @RequestHeader String authorization); + @Cacheable(cacheNames = "jiraCards", key = "#boardId+'-'+#queryCount+'-'+#startAt+'-'+#jql") @GetMapping(path = "/rest/agile/1.0/board/{boardId}/issue?maxResults={queryCount}&startAt={startAt}&jql={jql}") String getJiraCards(URI baseUrl, @PathVariable String boardId, @PathVariable int queryCount, @PathVariable int startAt, @PathVariable String jql, @RequestHeader String authorization); diff --git a/backend/src/main/java/heartbeat/client/dto/board/jira/HolidayDTO.java b/backend/src/main/java/heartbeat/client/dto/board/jira/HolidayDTO.java index 345fec0391..7610d6e01e 100644 --- a/backend/src/main/java/heartbeat/client/dto/board/jira/HolidayDTO.java +++ b/backend/src/main/java/heartbeat/client/dto/board/jira/HolidayDTO.java @@ -5,11 +5,13 @@ import lombok.Data; import lombok.NoArgsConstructor; +import java.io.Serializable; + @Data @Builder @NoArgsConstructor @AllArgsConstructor -public class HolidayDTO { +public class HolidayDTO implements Serializable { private String name; diff --git a/backend/src/main/java/heartbeat/client/dto/board/jira/HolidaysResponseDTO.java b/backend/src/main/java/heartbeat/client/dto/board/jira/HolidaysResponseDTO.java index 07e5e11e0e..2a0896791f 100644 --- a/backend/src/main/java/heartbeat/client/dto/board/jira/HolidaysResponseDTO.java +++ b/backend/src/main/java/heartbeat/client/dto/board/jira/HolidaysResponseDTO.java @@ -6,6 +6,7 @@ import lombok.Data; import lombok.NoArgsConstructor; +import java.io.Serializable; import java.util.List; @Data @@ -13,7 +14,7 @@ @NoArgsConstructor @AllArgsConstructor @JsonIgnoreProperties(ignoreUnknown = true) -public class HolidaysResponseDTO { +public class HolidaysResponseDTO implements Serializable { private List days; diff --git a/backend/src/main/java/heartbeat/client/dto/board/jira/JiraBoardProject.java b/backend/src/main/java/heartbeat/client/dto/board/jira/JiraBoardProject.java index fae99c457e..c88baa1b62 100644 --- a/backend/src/main/java/heartbeat/client/dto/board/jira/JiraBoardProject.java +++ b/backend/src/main/java/heartbeat/client/dto/board/jira/JiraBoardProject.java @@ -6,12 +6,14 @@ import lombok.Data; import lombok.NoArgsConstructor; +import java.io.Serializable; + @AllArgsConstructor @Data @Builder @NoArgsConstructor @JsonIgnoreProperties(ignoreUnknown = true) -public class JiraBoardProject { +public class JiraBoardProject implements Serializable { private String style; diff --git a/backend/src/main/java/heartbeat/client/dto/codebase/github/Author.java b/backend/src/main/java/heartbeat/client/dto/codebase/github/Author.java index 415198e2ac..da481a3f19 100644 --- a/backend/src/main/java/heartbeat/client/dto/codebase/github/Author.java +++ b/backend/src/main/java/heartbeat/client/dto/codebase/github/Author.java @@ -5,11 +5,13 @@ import lombok.Data; import lombok.NoArgsConstructor; +import java.io.Serializable; + @Data @Builder @NoArgsConstructor @AllArgsConstructor -public class Author { +public class Author implements Serializable { private String name; diff --git a/backend/src/main/java/heartbeat/client/dto/codebase/github/AuthorOuter.java b/backend/src/main/java/heartbeat/client/dto/codebase/github/AuthorOuter.java index 8be0cc0528..566a81519c 100644 --- a/backend/src/main/java/heartbeat/client/dto/codebase/github/AuthorOuter.java +++ b/backend/src/main/java/heartbeat/client/dto/codebase/github/AuthorOuter.java @@ -6,11 +6,13 @@ import lombok.Data; import lombok.NoArgsConstructor; +import java.io.Serializable; + @Data @Builder @NoArgsConstructor @AllArgsConstructor -public class AuthorOuter { +public class AuthorOuter implements Serializable { private String login; diff --git a/backend/src/main/java/heartbeat/client/dto/codebase/github/Base.java b/backend/src/main/java/heartbeat/client/dto/codebase/github/Base.java index ac96c750e0..e2bc62608b 100644 --- a/backend/src/main/java/heartbeat/client/dto/codebase/github/Base.java +++ b/backend/src/main/java/heartbeat/client/dto/codebase/github/Base.java @@ -5,11 +5,13 @@ import lombok.Data; import lombok.NoArgsConstructor; +import java.io.Serializable; + @Data @Builder @NoArgsConstructor @AllArgsConstructor -public class Base { +public class Base implements Serializable { private String label; diff --git a/backend/src/main/java/heartbeat/client/dto/codebase/github/Comment.java b/backend/src/main/java/heartbeat/client/dto/codebase/github/Comment.java index ff97ba624b..84d4dfbfcb 100644 --- a/backend/src/main/java/heartbeat/client/dto/codebase/github/Comment.java +++ b/backend/src/main/java/heartbeat/client/dto/codebase/github/Comment.java @@ -5,11 +5,13 @@ import lombok.Data; import lombok.NoArgsConstructor; +import java.io.Serializable; + @Data @Builder @NoArgsConstructor @AllArgsConstructor -public class Comment { +public class Comment implements Serializable { private String href; diff --git a/backend/src/main/java/heartbeat/client/dto/codebase/github/Commit.java b/backend/src/main/java/heartbeat/client/dto/codebase/github/Commit.java index 6309b1b558..2af13c7134 100644 --- a/backend/src/main/java/heartbeat/client/dto/codebase/github/Commit.java +++ b/backend/src/main/java/heartbeat/client/dto/codebase/github/Commit.java @@ -6,11 +6,13 @@ import lombok.Data; import lombok.NoArgsConstructor; +import java.io.Serializable; + @Data @Builder @NoArgsConstructor @AllArgsConstructor -public class Commit { +public class Commit implements Serializable { private Author author; diff --git a/backend/src/main/java/heartbeat/client/dto/codebase/github/CommitInfo.java b/backend/src/main/java/heartbeat/client/dto/codebase/github/CommitInfo.java index 1f6a55e9a1..6fdf70289a 100644 --- a/backend/src/main/java/heartbeat/client/dto/codebase/github/CommitInfo.java +++ b/backend/src/main/java/heartbeat/client/dto/codebase/github/CommitInfo.java @@ -6,13 +6,14 @@ import lombok.Data; import lombok.NoArgsConstructor; +import java.io.Serializable; import java.util.List; @Data @Builder @NoArgsConstructor @AllArgsConstructor -public class CommitInfo { +public class CommitInfo implements Serializable { private String sha; diff --git a/backend/src/main/java/heartbeat/client/dto/codebase/github/Commits.java b/backend/src/main/java/heartbeat/client/dto/codebase/github/Commits.java index 1db391606a..6a6087d8fc 100644 --- a/backend/src/main/java/heartbeat/client/dto/codebase/github/Commits.java +++ b/backend/src/main/java/heartbeat/client/dto/codebase/github/Commits.java @@ -5,11 +5,13 @@ import lombok.Data; import lombok.NoArgsConstructor; +import java.io.Serializable; + @Data @Builder @NoArgsConstructor @AllArgsConstructor -public class Commits { +public class Commits implements Serializable { private String href; diff --git a/backend/src/main/java/heartbeat/client/dto/codebase/github/Committer.java b/backend/src/main/java/heartbeat/client/dto/codebase/github/Committer.java index 50fd43c57c..136c19dad6 100644 --- a/backend/src/main/java/heartbeat/client/dto/codebase/github/Committer.java +++ b/backend/src/main/java/heartbeat/client/dto/codebase/github/Committer.java @@ -5,11 +5,13 @@ import lombok.Data; import lombok.NoArgsConstructor; +import java.io.Serializable; + @Data @Builder @NoArgsConstructor @AllArgsConstructor -public class Committer { +public class Committer implements Serializable { private String name; diff --git a/backend/src/main/java/heartbeat/client/dto/codebase/github/CommitterOuter.java b/backend/src/main/java/heartbeat/client/dto/codebase/github/CommitterOuter.java index becaf04070..23eb7d5cef 100644 --- a/backend/src/main/java/heartbeat/client/dto/codebase/github/CommitterOuter.java +++ b/backend/src/main/java/heartbeat/client/dto/codebase/github/CommitterOuter.java @@ -6,11 +6,13 @@ import lombok.Data; import lombok.NoArgsConstructor; +import java.io.Serializable; + @Data @Builder @NoArgsConstructor @AllArgsConstructor -public class CommitterOuter { +public class CommitterOuter implements Serializable { private String login; diff --git a/backend/src/main/java/heartbeat/client/dto/codebase/github/File.java b/backend/src/main/java/heartbeat/client/dto/codebase/github/File.java index 8f7b7c3330..5279069781 100644 --- a/backend/src/main/java/heartbeat/client/dto/codebase/github/File.java +++ b/backend/src/main/java/heartbeat/client/dto/codebase/github/File.java @@ -6,11 +6,13 @@ import lombok.Data; import lombok.NoArgsConstructor; +import java.io.Serializable; + @Data @Builder @NoArgsConstructor @AllArgsConstructor -public class File { +public class File implements Serializable { private String sha; diff --git a/backend/src/main/java/heartbeat/client/dto/codebase/github/GitHubOrganizationsInfo.java b/backend/src/main/java/heartbeat/client/dto/codebase/github/GitHubOrganizationsInfo.java index d7833728ea..a16ecdf8ce 100644 --- a/backend/src/main/java/heartbeat/client/dto/codebase/github/GitHubOrganizationsInfo.java +++ b/backend/src/main/java/heartbeat/client/dto/codebase/github/GitHubOrganizationsInfo.java @@ -6,12 +6,14 @@ import lombok.Getter; import lombok.NoArgsConstructor; +import java.io.Serializable; + @AllArgsConstructor @NoArgsConstructor @Getter @Builder @JsonIgnoreProperties(ignoreUnknown = true) -public class GitHubOrganizationsInfo { +public class GitHubOrganizationsInfo implements Serializable { private String login; diff --git a/backend/src/main/java/heartbeat/client/dto/codebase/github/GitHubRepo.java b/backend/src/main/java/heartbeat/client/dto/codebase/github/GitHubRepo.java index 97d4f42186..e806cf8b88 100644 --- a/backend/src/main/java/heartbeat/client/dto/codebase/github/GitHubRepo.java +++ b/backend/src/main/java/heartbeat/client/dto/codebase/github/GitHubRepo.java @@ -7,12 +7,14 @@ import lombok.Getter; import lombok.NoArgsConstructor; +import java.io.Serializable; + @AllArgsConstructor @NoArgsConstructor @Getter @Builder @JsonIgnoreProperties(ignoreUnknown = true) -public class GitHubRepo { +public class GitHubRepo implements Serializable { @JsonProperty("html_url") private String htmlUrl; diff --git a/backend/src/main/java/heartbeat/client/dto/codebase/github/Head.java b/backend/src/main/java/heartbeat/client/dto/codebase/github/Head.java index c8666a1b87..dab44a8da7 100644 --- a/backend/src/main/java/heartbeat/client/dto/codebase/github/Head.java +++ b/backend/src/main/java/heartbeat/client/dto/codebase/github/Head.java @@ -5,11 +5,13 @@ import lombok.Data; import lombok.NoArgsConstructor; +import java.io.Serializable; + @Data @Builder @NoArgsConstructor @AllArgsConstructor -public class Head { +public class Head implements Serializable { private String label; diff --git a/backend/src/main/java/heartbeat/client/dto/codebase/github/Html.java b/backend/src/main/java/heartbeat/client/dto/codebase/github/Html.java index 4323338dbd..c9f7b0d42c 100644 --- a/backend/src/main/java/heartbeat/client/dto/codebase/github/Html.java +++ b/backend/src/main/java/heartbeat/client/dto/codebase/github/Html.java @@ -5,11 +5,13 @@ import lombok.Data; import lombok.NoArgsConstructor; +import java.io.Serializable; + @Data @Builder @NoArgsConstructor @AllArgsConstructor -public class Html { +public class Html implements Serializable { private String href; diff --git a/backend/src/main/java/heartbeat/client/dto/codebase/github/Issue.java b/backend/src/main/java/heartbeat/client/dto/codebase/github/Issue.java index fe24e2e905..b935061bb3 100644 --- a/backend/src/main/java/heartbeat/client/dto/codebase/github/Issue.java +++ b/backend/src/main/java/heartbeat/client/dto/codebase/github/Issue.java @@ -5,11 +5,13 @@ import lombok.Data; import lombok.NoArgsConstructor; +import java.io.Serializable; + @Data @Builder @NoArgsConstructor @AllArgsConstructor -public class Issue { +public class Issue implements Serializable { private String href; diff --git a/backend/src/main/java/heartbeat/client/dto/codebase/github/License.java b/backend/src/main/java/heartbeat/client/dto/codebase/github/License.java index 74350cf99d..46ce69b932 100644 --- a/backend/src/main/java/heartbeat/client/dto/codebase/github/License.java +++ b/backend/src/main/java/heartbeat/client/dto/codebase/github/License.java @@ -6,11 +6,13 @@ import lombok.Data; import lombok.NoArgsConstructor; +import java.io.Serializable; + @Data @Builder @NoArgsConstructor @AllArgsConstructor -public class License { +public class License implements Serializable { private String key; diff --git a/backend/src/main/java/heartbeat/client/dto/codebase/github/LinkCollection.java b/backend/src/main/java/heartbeat/client/dto/codebase/github/LinkCollection.java index e6d588c70c..5caf031822 100644 --- a/backend/src/main/java/heartbeat/client/dto/codebase/github/LinkCollection.java +++ b/backend/src/main/java/heartbeat/client/dto/codebase/github/LinkCollection.java @@ -6,11 +6,13 @@ import lombok.Data; import lombok.NoArgsConstructor; +import java.io.Serializable; + @Data @Builder @NoArgsConstructor @AllArgsConstructor -public class LinkCollection { +public class LinkCollection implements Serializable { private Self self; diff --git a/backend/src/main/java/heartbeat/client/dto/codebase/github/Owner.java b/backend/src/main/java/heartbeat/client/dto/codebase/github/Owner.java index a33378eec5..24fd8a1a92 100644 --- a/backend/src/main/java/heartbeat/client/dto/codebase/github/Owner.java +++ b/backend/src/main/java/heartbeat/client/dto/codebase/github/Owner.java @@ -6,11 +6,13 @@ import lombok.Data; import lombok.NoArgsConstructor; +import java.io.Serializable; + @Data @Builder @NoArgsConstructor @AllArgsConstructor -public class Owner { +public class Owner implements Serializable { private String login; diff --git a/backend/src/main/java/heartbeat/client/dto/codebase/github/Parent.java b/backend/src/main/java/heartbeat/client/dto/codebase/github/Parent.java index 67523769c8..291a39b40f 100644 --- a/backend/src/main/java/heartbeat/client/dto/codebase/github/Parent.java +++ b/backend/src/main/java/heartbeat/client/dto/codebase/github/Parent.java @@ -6,11 +6,13 @@ import lombok.Data; import lombok.NoArgsConstructor; +import java.io.Serializable; + @Data @Builder @NoArgsConstructor @AllArgsConstructor -public class Parent { +public class Parent implements Serializable { private String sha; diff --git a/backend/src/main/java/heartbeat/client/dto/codebase/github/PullRequestInfo.java b/backend/src/main/java/heartbeat/client/dto/codebase/github/PullRequestInfo.java index b5bf8e20de..f0672a2b36 100644 --- a/backend/src/main/java/heartbeat/client/dto/codebase/github/PullRequestInfo.java +++ b/backend/src/main/java/heartbeat/client/dto/codebase/github/PullRequestInfo.java @@ -7,6 +7,7 @@ import lombok.Getter; import lombok.NoArgsConstructor; +import java.io.Serializable; import java.util.List; @Data @@ -14,7 +15,7 @@ @NoArgsConstructor @AllArgsConstructor @Getter -public class PullRequestInfo { +public class PullRequestInfo implements Serializable { private String url; diff --git a/backend/src/main/java/heartbeat/client/dto/codebase/github/Repo.java b/backend/src/main/java/heartbeat/client/dto/codebase/github/Repo.java index 94991be6a9..ee55d42092 100644 --- a/backend/src/main/java/heartbeat/client/dto/codebase/github/Repo.java +++ b/backend/src/main/java/heartbeat/client/dto/codebase/github/Repo.java @@ -1,20 +1,19 @@ package heartbeat.client.dto.codebase.github; import com.fasterxml.jackson.annotation.JsonProperty; -import heartbeat.client.dto.codebase.github.License; -import heartbeat.client.dto.codebase.github.Owner; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; import lombok.NoArgsConstructor; +import java.io.Serializable; import java.util.List; @Data @Builder @NoArgsConstructor @AllArgsConstructor -public class Repo { +public class Repo implements Serializable { private Integer id; diff --git a/backend/src/main/java/heartbeat/client/dto/codebase/github/ReviewComment.java b/backend/src/main/java/heartbeat/client/dto/codebase/github/ReviewComment.java index 6828b6b3db..52d50eff3e 100644 --- a/backend/src/main/java/heartbeat/client/dto/codebase/github/ReviewComment.java +++ b/backend/src/main/java/heartbeat/client/dto/codebase/github/ReviewComment.java @@ -5,11 +5,13 @@ import lombok.Data; import lombok.NoArgsConstructor; +import java.io.Serializable; + @Data @Builder @NoArgsConstructor @AllArgsConstructor -public class ReviewComment { +public class ReviewComment implements Serializable { private String href; diff --git a/backend/src/main/java/heartbeat/client/dto/codebase/github/ReviewComments.java b/backend/src/main/java/heartbeat/client/dto/codebase/github/ReviewComments.java index f71d3eef76..b040cf3f29 100644 --- a/backend/src/main/java/heartbeat/client/dto/codebase/github/ReviewComments.java +++ b/backend/src/main/java/heartbeat/client/dto/codebase/github/ReviewComments.java @@ -5,11 +5,13 @@ import lombok.Data; import lombok.NoArgsConstructor; +import java.io.Serializable; + @Data @Builder @NoArgsConstructor @AllArgsConstructor -public class ReviewComments { +public class ReviewComments implements Serializable { private String href; diff --git a/backend/src/main/java/heartbeat/client/dto/codebase/github/Self.java b/backend/src/main/java/heartbeat/client/dto/codebase/github/Self.java index b6615ae9ad..20aa3b6afb 100644 --- a/backend/src/main/java/heartbeat/client/dto/codebase/github/Self.java +++ b/backend/src/main/java/heartbeat/client/dto/codebase/github/Self.java @@ -5,11 +5,13 @@ import lombok.Data; import lombok.NoArgsConstructor; +import java.io.Serializable; + @Data @Builder @NoArgsConstructor @AllArgsConstructor -public class Self { +public class Self implements Serializable { private String href; diff --git a/backend/src/main/java/heartbeat/client/dto/codebase/github/Stats.java b/backend/src/main/java/heartbeat/client/dto/codebase/github/Stats.java index 8a35608185..ce1dd29918 100644 --- a/backend/src/main/java/heartbeat/client/dto/codebase/github/Stats.java +++ b/backend/src/main/java/heartbeat/client/dto/codebase/github/Stats.java @@ -5,11 +5,13 @@ import lombok.Data; import lombok.NoArgsConstructor; +import java.io.Serializable; + @Data @Builder @NoArgsConstructor @AllArgsConstructor -public class Stats { +public class Stats implements Serializable { private Integer total; diff --git a/backend/src/main/java/heartbeat/client/dto/codebase/github/Status.java b/backend/src/main/java/heartbeat/client/dto/codebase/github/Status.java index 4f8e827c97..59d115d7f9 100644 --- a/backend/src/main/java/heartbeat/client/dto/codebase/github/Status.java +++ b/backend/src/main/java/heartbeat/client/dto/codebase/github/Status.java @@ -5,11 +5,13 @@ import lombok.Data; import lombok.NoArgsConstructor; +import java.io.Serializable; + @Data @Builder @NoArgsConstructor @AllArgsConstructor -public class Status { +public class Status implements Serializable { private String href; diff --git a/backend/src/main/java/heartbeat/client/dto/codebase/github/Tree.java b/backend/src/main/java/heartbeat/client/dto/codebase/github/Tree.java index db4d8b9936..2bb563eed6 100644 --- a/backend/src/main/java/heartbeat/client/dto/codebase/github/Tree.java +++ b/backend/src/main/java/heartbeat/client/dto/codebase/github/Tree.java @@ -5,11 +5,13 @@ import lombok.Data; import lombok.NoArgsConstructor; +import java.io.Serializable; + @Data @Builder @NoArgsConstructor @AllArgsConstructor -public class Tree { +public class Tree implements Serializable { private String sha; diff --git a/backend/src/main/java/heartbeat/client/dto/codebase/github/User.java b/backend/src/main/java/heartbeat/client/dto/codebase/github/User.java index 181c9e4c3e..4d82094d20 100644 --- a/backend/src/main/java/heartbeat/client/dto/codebase/github/User.java +++ b/backend/src/main/java/heartbeat/client/dto/codebase/github/User.java @@ -6,11 +6,13 @@ import lombok.Data; import lombok.NoArgsConstructor; +import java.io.Serializable; + @Data @Builder @NoArgsConstructor @AllArgsConstructor -public class User { +public class User implements Serializable { private String login; diff --git a/backend/src/main/java/heartbeat/client/dto/codebase/github/Verification.java b/backend/src/main/java/heartbeat/client/dto/codebase/github/Verification.java index abfcb34f66..5991419b87 100644 --- a/backend/src/main/java/heartbeat/client/dto/codebase/github/Verification.java +++ b/backend/src/main/java/heartbeat/client/dto/codebase/github/Verification.java @@ -5,11 +5,13 @@ import lombok.Data; import lombok.NoArgsConstructor; +import java.io.Serializable; + @Data @Builder @NoArgsConstructor @AllArgsConstructor -public class Verification { +public class Verification implements Serializable { private Boolean verified; diff --git a/backend/src/main/java/heartbeat/client/dto/pipeline/buildkite/BuildKiteBuildInfo.java b/backend/src/main/java/heartbeat/client/dto/pipeline/buildkite/BuildKiteBuildInfo.java index 078fa80926..1f17bc75eb 100644 --- a/backend/src/main/java/heartbeat/client/dto/pipeline/buildkite/BuildKiteBuildInfo.java +++ b/backend/src/main/java/heartbeat/client/dto/pipeline/buildkite/BuildKiteBuildInfo.java @@ -8,6 +8,7 @@ import lombok.Data; import lombok.NoArgsConstructor; +import java.io.Serializable; import java.time.Instant; import java.util.Comparator; import java.util.List; @@ -18,7 +19,7 @@ @AllArgsConstructor @NoArgsConstructor @Builder -public class BuildKiteBuildInfo { +public class BuildKiteBuildInfo implements Serializable { private List jobs; @@ -38,7 +39,7 @@ public class BuildKiteBuildInfo { @AllArgsConstructor @NoArgsConstructor @Builder - public static class Author { + public static class Author implements Serializable { private String userName; diff --git a/backend/src/main/java/heartbeat/client/dto/pipeline/buildkite/BuildKiteJob.java b/backend/src/main/java/heartbeat/client/dto/pipeline/buildkite/BuildKiteJob.java index d9fb5b84ce..204c60d905 100644 --- a/backend/src/main/java/heartbeat/client/dto/pipeline/buildkite/BuildKiteJob.java +++ b/backend/src/main/java/heartbeat/client/dto/pipeline/buildkite/BuildKiteJob.java @@ -7,12 +7,14 @@ import lombok.Data; import lombok.NoArgsConstructor; +import java.io.Serializable; + @Data @JsonIgnoreProperties(ignoreUnknown = true) @Builder @NoArgsConstructor @AllArgsConstructor -public class BuildKiteJob { +public class BuildKiteJob implements Serializable { private String name; diff --git a/backend/src/main/java/heartbeat/client/dto/pipeline/buildkite/BuildKiteOrganizationsInfo.java b/backend/src/main/java/heartbeat/client/dto/pipeline/buildkite/BuildKiteOrganizationsInfo.java index 80e5a66a9b..c643b710f7 100644 --- a/backend/src/main/java/heartbeat/client/dto/pipeline/buildkite/BuildKiteOrganizationsInfo.java +++ b/backend/src/main/java/heartbeat/client/dto/pipeline/buildkite/BuildKiteOrganizationsInfo.java @@ -6,12 +6,14 @@ import lombok.Data; import lombok.NoArgsConstructor; +import java.io.Serializable; + @Data @Builder @AllArgsConstructor @NoArgsConstructor @JsonIgnoreProperties(ignoreUnknown = true) -public class BuildKiteOrganizationsInfo { +public class BuildKiteOrganizationsInfo implements Serializable { private String name; diff --git a/backend/src/main/java/heartbeat/client/dto/pipeline/buildkite/BuildKitePipelineDTO.java b/backend/src/main/java/heartbeat/client/dto/pipeline/buildkite/BuildKitePipelineDTO.java index 9acdab32f6..4c6e0ac6bf 100644 --- a/backend/src/main/java/heartbeat/client/dto/pipeline/buildkite/BuildKitePipelineDTO.java +++ b/backend/src/main/java/heartbeat/client/dto/pipeline/buildkite/BuildKitePipelineDTO.java @@ -7,6 +7,7 @@ import lombok.Data; import lombok.NoArgsConstructor; +import java.io.Serializable; import java.util.Date; import java.util.List; @@ -15,7 +16,7 @@ @AllArgsConstructor @NoArgsConstructor @JsonIgnoreProperties(ignoreUnknown = true) -public class BuildKitePipelineDTO { +public class BuildKitePipelineDTO implements Serializable { private String id; diff --git a/backend/src/main/java/heartbeat/client/dto/pipeline/buildkite/BuildKiteTokenInfo.java b/backend/src/main/java/heartbeat/client/dto/pipeline/buildkite/BuildKiteTokenInfo.java index b217c3fa13..ae65912e54 100644 --- a/backend/src/main/java/heartbeat/client/dto/pipeline/buildkite/BuildKiteTokenInfo.java +++ b/backend/src/main/java/heartbeat/client/dto/pipeline/buildkite/BuildKiteTokenInfo.java @@ -6,6 +6,7 @@ import lombok.Getter; import lombok.NoArgsConstructor; +import java.io.Serializable; import java.util.List; @AllArgsConstructor @@ -13,7 +14,7 @@ @Getter @Builder @JsonIgnoreProperties(ignoreUnknown = true) -public class BuildKiteTokenInfo { +public class BuildKiteTokenInfo implements Serializable { private List scopes; diff --git a/backend/src/main/java/heartbeat/client/dto/pipeline/buildkite/CreatedByDTO.java b/backend/src/main/java/heartbeat/client/dto/pipeline/buildkite/CreatedByDTO.java index 39cd559501..5c32e9e343 100644 --- a/backend/src/main/java/heartbeat/client/dto/pipeline/buildkite/CreatedByDTO.java +++ b/backend/src/main/java/heartbeat/client/dto/pipeline/buildkite/CreatedByDTO.java @@ -6,6 +6,7 @@ import lombok.Data; import lombok.NoArgsConstructor; +import java.io.Serializable; import java.util.Date; @Data @@ -13,7 +14,7 @@ @AllArgsConstructor @NoArgsConstructor @JsonIgnoreProperties(ignoreUnknown = true) -public class CreatedByDTO { +public class CreatedByDTO implements Serializable { private String id; diff --git a/backend/src/main/java/heartbeat/client/dto/pipeline/buildkite/EnvDTO.java b/backend/src/main/java/heartbeat/client/dto/pipeline/buildkite/EnvDTO.java index 489e21e75f..011eaa1112 100644 --- a/backend/src/main/java/heartbeat/client/dto/pipeline/buildkite/EnvDTO.java +++ b/backend/src/main/java/heartbeat/client/dto/pipeline/buildkite/EnvDTO.java @@ -5,10 +5,12 @@ import lombok.Builder; import lombok.Data; +import java.io.Serializable; + @Data @Builder @AllArgsConstructor @JsonIgnoreProperties(ignoreUnknown = true) -public class EnvDTO { +public class EnvDTO implements Serializable { } diff --git a/backend/src/main/java/heartbeat/client/dto/pipeline/buildkite/ProviderDTO.java b/backend/src/main/java/heartbeat/client/dto/pipeline/buildkite/ProviderDTO.java index 38522b3cb1..cdaa576862 100644 --- a/backend/src/main/java/heartbeat/client/dto/pipeline/buildkite/ProviderDTO.java +++ b/backend/src/main/java/heartbeat/client/dto/pipeline/buildkite/ProviderDTO.java @@ -7,12 +7,14 @@ import lombok.Data; import lombok.NoArgsConstructor; +import java.io.Serializable; + @Data @Builder @AllArgsConstructor @NoArgsConstructor @JsonIgnoreProperties(ignoreUnknown = true) -public class ProviderDTO { +public class ProviderDTO implements Serializable { private String id; diff --git a/backend/src/main/java/heartbeat/client/dto/pipeline/buildkite/ProviderSettingsDTO.java b/backend/src/main/java/heartbeat/client/dto/pipeline/buildkite/ProviderSettingsDTO.java index b6aaa2eaf5..ff8c893866 100644 --- a/backend/src/main/java/heartbeat/client/dto/pipeline/buildkite/ProviderSettingsDTO.java +++ b/backend/src/main/java/heartbeat/client/dto/pipeline/buildkite/ProviderSettingsDTO.java @@ -7,12 +7,14 @@ import lombok.Data; import lombok.NoArgsConstructor; +import java.io.Serializable; + @Data @Builder @AllArgsConstructor @NoArgsConstructor @JsonIgnoreProperties(ignoreUnknown = true) -public class ProviderSettingsDTO { +public class ProviderSettingsDTO implements Serializable { @JsonProperty("trigger_mode") private String triggerMode; diff --git a/backend/src/main/java/heartbeat/client/dto/pipeline/buildkite/StepsDTO.java b/backend/src/main/java/heartbeat/client/dto/pipeline/buildkite/StepsDTO.java index 00c9323201..df1c5d9c2a 100644 --- a/backend/src/main/java/heartbeat/client/dto/pipeline/buildkite/StepsDTO.java +++ b/backend/src/main/java/heartbeat/client/dto/pipeline/buildkite/StepsDTO.java @@ -7,12 +7,14 @@ import lombok.Data; import lombok.NoArgsConstructor; +import java.io.Serializable; + @Data @Builder @AllArgsConstructor @NoArgsConstructor @JsonIgnoreProperties(ignoreUnknown = true) -public class StepsDTO { +public class StepsDTO implements Serializable { private String type; diff --git a/backend/src/main/java/heartbeat/config/CacheConfig.java b/backend/src/main/java/heartbeat/config/CacheConfig.java index 783c9e46a2..7c777f87eb 100644 --- a/backend/src/main/java/heartbeat/config/CacheConfig.java +++ b/backend/src/main/java/heartbeat/config/CacheConfig.java @@ -7,9 +7,14 @@ import heartbeat.client.dto.board.jira.JiraBoardVerifyDTO; import heartbeat.client.dto.board.jira.StatusSelfDTO; import java.time.Duration; +import java.util.List; import javax.cache.CacheManager; import javax.cache.Caching; import javax.cache.spi.CachingProvider; + +import heartbeat.client.dto.board.jira.HolidaysResponseDTO; +import heartbeat.client.dto.codebase.github.CommitInfo; +import heartbeat.client.dto.pipeline.buildkite.BuildKiteTokenInfo; import lombok.val; import org.ehcache.config.builders.CacheConfigurationBuilder; import org.ehcache.config.builders.ExpiryPolicyBuilder; @@ -35,6 +40,18 @@ public CacheManager ehCacheManager() { cacheManager.createCache("targetField", getCacheConfiguration(FieldResponseDTO.class)); cacheManager.createCache("boardVerification", getCacheConfiguration(JiraBoardVerifyDTO.class)); cacheManager.createCache("boardProject", getCacheConfiguration(JiraBoardProject.class)); + cacheManager.createCache("jiraCards", getCacheConfiguration(String.class)); + cacheManager.createCache("holidayResult", getCacheConfiguration(HolidaysResponseDTO.class)); + cacheManager.createCache("tokenInfo", getCacheConfiguration(BuildKiteTokenInfo.class)); + cacheManager.createCache("buildKiteOrganizationInfo", getCacheConfiguration(List.class)); + cacheManager.createCache("pipelineInfo", getCacheConfiguration(List.class)); + cacheManager.createCache("pipelineStepsInfo", getCacheConfiguration(List.class)); + cacheManager.createCache("githubOrganizationInfo", getCacheConfiguration(List.class)); + cacheManager.createCache("githubAllRepos", getCacheConfiguration(List.class)); + cacheManager.createCache("githubRepos", getCacheConfiguration(List.class)); + cacheManager.createCache("commitInfo", getCacheConfiguration(CommitInfo.class)); + cacheManager.createCache("pullRequestCommitInfo", getCacheConfiguration(List.class)); + cacheManager.createCache("pullRequestListInfo", getCacheConfiguration(List.class)); return cacheManager; } diff --git a/backend/src/main/java/heartbeat/config/WebConfig.java b/backend/src/main/java/heartbeat/config/WebConfig.java index 36f42ef305..91445b5f5e 100644 --- a/backend/src/main/java/heartbeat/config/WebConfig.java +++ b/backend/src/main/java/heartbeat/config/WebConfig.java @@ -2,6 +2,7 @@ import heartbeat.controller.board.dto.request.BoardType; import heartbeat.controller.report.dto.request.DataType; +import heartbeat.controller.report.dto.request.ReportType; import org.springframework.context.annotation.Configuration; import org.springframework.core.convert.converter.Converter; import org.springframework.format.FormatterRegistry; @@ -25,6 +26,13 @@ public DataType convert(String source) { return DataType.fromValue(source); } }); + + registry.addConverter(new Converter() { + @Override + public ReportType convert(String type) { + return ReportType.fromValue(type); + } + }); } } diff --git a/backend/src/main/java/heartbeat/controller/board/JiraController.java b/backend/src/main/java/heartbeat/controller/board/JiraController.java index fbcd51f97a..45725c10d2 100644 --- a/backend/src/main/java/heartbeat/controller/board/JiraController.java +++ b/backend/src/main/java/heartbeat/controller/board/JiraController.java @@ -22,6 +22,7 @@ public class JiraController { private final JiraService jiraService; + @Deprecated @PostMapping("/{boardType}") public BoardConfigDTO getBoard(@PathVariable @NotBlank BoardType boardType, @Valid @RequestBody BoardRequestParam boardRequestParam) { diff --git a/backend/src/main/java/heartbeat/controller/board/dto/request/BoardType.java b/backend/src/main/java/heartbeat/controller/board/dto/request/BoardType.java index 7ede536fba..e6672e69c9 100644 --- a/backend/src/main/java/heartbeat/controller/board/dto/request/BoardType.java +++ b/backend/src/main/java/heartbeat/controller/board/dto/request/BoardType.java @@ -18,4 +18,12 @@ public static BoardType fromValue(String type) { }; } + public static BoardType fromStyle(String style) { + return switch (style) { + case "next-gen" -> JIRA; + case "classic" -> CLASSIC_JIRA; + default -> throw new IllegalArgumentException("Board type does not find!"); + }; + } + } diff --git a/backend/src/main/java/heartbeat/controller/report/GenerateReportController.java b/backend/src/main/java/heartbeat/controller/report/GenerateReportController.java index 7dd11eca1e..83687ddd01 100644 --- a/backend/src/main/java/heartbeat/controller/report/GenerateReportController.java +++ b/backend/src/main/java/heartbeat/controller/report/GenerateReportController.java @@ -1,15 +1,12 @@ package heartbeat.controller.report; import heartbeat.controller.report.dto.request.DataType; +import heartbeat.controller.report.dto.request.ReportType; +import heartbeat.controller.report.dto.request.GenerateReportRequest; import heartbeat.controller.report.dto.request.ExportCSVRequest; -import heartbeat.controller.report.dto.request.GenerateBoardReportRequest; -import heartbeat.controller.report.dto.request.GenerateDoraReportRequest; import heartbeat.controller.report.dto.response.CallbackResponse; import heartbeat.controller.report.dto.response.ReportResponse; -import heartbeat.exception.BaseException; -import heartbeat.handler.AsyncExceptionHandler; import heartbeat.service.report.GenerateReporterService; -import heartbeat.util.IdUtil; import lombok.RequiredArgsConstructor; import lombok.extern.log4j.Log4j2; import org.springframework.beans.factory.annotation.Value; @@ -17,12 +14,12 @@ import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.bind.annotation.PostMapping; import java.util.concurrent.CompletableFuture; @@ -35,8 +32,6 @@ public class GenerateReportController { private final GenerateReporterService generateReporterService; - private final AsyncExceptionHandler asyncExceptionHandler; - @Value("${callback.interval}") private Integer interval; @@ -62,62 +57,15 @@ public ResponseEntity generateReport(@PathVariable String report return ResponseEntity.status(HttpStatus.OK).body(reportResponse); } - @PostMapping("/board") - public ResponseEntity generateBoardReport(@RequestBody GenerateBoardReportRequest request) { - log.info( - "Start to generate board report, _metrics: {}, _considerHoliday: {}, _startTime: {}, _endTime: {}, _boardReportId: {}", - request.getMetrics(), request.getConsiderHoliday(), request.getStartTime(), request.getEndTime(), - IdUtil.getBoardReportId(request.getCsvTimeStamp())); - generateReporterService.initializeMetricsDataReadyInHandler(request.getCsvTimeStamp(), request.getMetrics()); - CompletableFuture.runAsync(() -> { - try { - ReportResponse reportResponse = generateReporterService - .generateReporter(request.convertToReportRequest()); - generateReporterService.saveReporterInHandler(reportResponse, - IdUtil.getBoardReportId(request.getCsvTimeStamp())); - generateReporterService.updateMetricsDataReadyInHandler(request.getCsvTimeStamp(), - request.getMetrics()); - log.info( - "Successfully generate board report, _metrics: {}, _considerHoliday: {}, _startTime: {}, _endTime: {}, _boardReportId: {}", - request.getMetrics(), request.getConsiderHoliday(), request.getStartTime(), - request.getEndTime(), IdUtil.getBoardReportId(request.getCsvTimeStamp())); - - } - catch (BaseException e) { - asyncExceptionHandler.put(IdUtil.getBoardReportId(request.getCsvTimeStamp()), e); - } - }); - - String callbackUrl = "/reports/" + request.getCsvTimeStamp(); - return ResponseEntity.status(HttpStatus.ACCEPTED) - .body(CallbackResponse.builder().callbackUrl(callbackUrl).interval(interval).build()); - } - - @PostMapping("/dora") - public ResponseEntity generateDoraReport(@RequestBody GenerateDoraReportRequest request) { - log.info( - "Start to generate dora report, _metrics: {}, _considerHoliday: {}, _startTime: {}, _endTime: {}, _doraReportId: {}", - request.getMetrics(), request.getConsiderHoliday(), request.getStartTime(), request.getEndTime(), - IdUtil.getDoraReportId(request.getCsvTimeStamp())); - generateReporterService.initializeMetricsDataReadyInHandler(request.getCsvTimeStamp(), request.getMetrics()); + @PostMapping("{reportType}") + public ResponseEntity generateReport(@PathVariable ReportType reportType, + @RequestBody GenerateReportRequest request) { CompletableFuture.runAsync(() -> { - try { - ReportResponse reportResponse = generateReporterService - .generateReporter(request.convertToReportRequest()); - generateReporterService.saveReporterInHandler(reportResponse, - IdUtil.getDoraReportId(request.getCsvTimeStamp())); - generateReporterService.updateMetricsDataReadyInHandler(request.getCsvTimeStamp(), - request.getMetrics()); - log.info( - "Successfully generate dora report, _metrics: {}, _considerHoliday: {}, _startTime: {}, _endTime: {}, _doraReportId: {}", - request.getMetrics(), request.getConsiderHoliday(), request.getStartTime(), - request.getEndTime(), IdUtil.getDoraReportId(request.getCsvTimeStamp())); - } - catch (BaseException e) { - asyncExceptionHandler.put(IdUtil.getDoraReportId(request.getCsvTimeStamp()), e); + switch (reportType) { + case BOARD -> generateReporterService.generateBoardReport(request); + case DORA -> generateReporterService.generateDoraReport(request); } }); - String callbackUrl = "/reports/" + request.getCsvTimeStamp(); return ResponseEntity.status(HttpStatus.ACCEPTED) .body(CallbackResponse.builder().callbackUrl(callbackUrl).interval(interval).build()); diff --git a/backend/src/main/java/heartbeat/controller/report/dto/request/GenerateBoardReportRequest.java b/backend/src/main/java/heartbeat/controller/report/dto/request/GenerateBoardReportRequest.java deleted file mode 100644 index 6c26273c52..0000000000 --- a/backend/src/main/java/heartbeat/controller/report/dto/request/GenerateBoardReportRequest.java +++ /dev/null @@ -1,45 +0,0 @@ -package heartbeat.controller.report.dto.request; - -import com.fasterxml.jackson.annotation.JsonIgnoreProperties; -import jakarta.validation.constraints.NotBlank; -import lombok.AllArgsConstructor; -import lombok.Builder; -import lombok.Data; -import lombok.NoArgsConstructor; - -import java.util.List; - -@Data -@Builder -@NoArgsConstructor -@AllArgsConstructor -@JsonIgnoreProperties(ignoreUnknown = true) -public class GenerateBoardReportRequest { - - private Boolean considerHoliday; - - @NotBlank(message = "StartTime is required") - private String startTime; - - @NotBlank(message = "EndTime is required") - private String endTime; - - private List metrics; - - private JiraBoardSetting jiraBoardSetting; - - @NotBlank - private String csvTimeStamp; - - public GenerateReportRequest convertToReportRequest() { - return GenerateReportRequest.builder() - .considerHoliday(this.considerHoliday) - .startTime(this.startTime) - .endTime(this.endTime) - .metrics(this.metrics) - .jiraBoardSetting(this.jiraBoardSetting) - .csvTimeStamp(this.csvTimeStamp) - .build(); - } - -} diff --git a/backend/src/main/java/heartbeat/controller/report/dto/request/GenerateDoraReportRequest.java b/backend/src/main/java/heartbeat/controller/report/dto/request/GenerateDoraReportRequest.java deleted file mode 100644 index 3bb4539f8d..0000000000 --- a/backend/src/main/java/heartbeat/controller/report/dto/request/GenerateDoraReportRequest.java +++ /dev/null @@ -1,48 +0,0 @@ -package heartbeat.controller.report.dto.request; - -import com.fasterxml.jackson.annotation.JsonIgnoreProperties; -import jakarta.validation.constraints.NotBlank; -import lombok.AllArgsConstructor; -import lombok.Builder; -import lombok.Data; -import lombok.NoArgsConstructor; - -import java.util.List; - -@Data -@Builder -@NoArgsConstructor -@AllArgsConstructor -@JsonIgnoreProperties(ignoreUnknown = true) -public class GenerateDoraReportRequest { - - private Boolean considerHoliday; - - @NotBlank(message = "StartTime is required") - private String startTime; - - @NotBlank(message = "EndTime is required") - private String endTime; - - private List metrics; - - private BuildKiteSetting buildKiteSetting; - - private CodebaseSetting codebaseSetting; - - @NotBlank - private String csvTimeStamp; - - public GenerateReportRequest convertToReportRequest() { - GenerateReportRequest reportRequest = new GenerateReportRequest(); - reportRequest.setConsiderHoliday(this.considerHoliday); - reportRequest.setStartTime(this.startTime); - reportRequest.setEndTime(this.endTime); - reportRequest.setMetrics(this.metrics); - reportRequest.setBuildKiteSetting(this.buildKiteSetting); - reportRequest.setCodebaseSetting(this.codebaseSetting); - reportRequest.setCsvTimeStamp(this.csvTimeStamp); - return reportRequest; - } - -} diff --git a/backend/src/main/java/heartbeat/controller/report/dto/request/GenerateReportRequest.java b/backend/src/main/java/heartbeat/controller/report/dto/request/GenerateReportRequest.java index a4fdb9f0ca..4c5977f565 100644 --- a/backend/src/main/java/heartbeat/controller/report/dto/request/GenerateReportRequest.java +++ b/backend/src/main/java/heartbeat/controller/report/dto/request/GenerateReportRequest.java @@ -1,6 +1,8 @@ package heartbeat.controller.report.dto.request; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.google.gson.Gson; +import heartbeat.util.MetricsUtil; import jakarta.validation.constraints.NotBlank; import lombok.AllArgsConstructor; import lombok.Builder; @@ -35,4 +37,23 @@ public class GenerateReportRequest { @NotBlank private String csvTimeStamp; + public GenerateReportRequest convertToPipelineRequest(GenerateReportRequest request) { + List pipelineMetrics = MetricsUtil + .getPipelineMetrics(request.getMetrics().stream().map(String::toLowerCase).toList()); + Gson gson = new Gson(); + GenerateReportRequest result = gson.fromJson(gson.toJson(request), GenerateReportRequest.class); + + result.setMetrics(pipelineMetrics); + return result; + } + + public GenerateReportRequest convertToSourceControlRequest(GenerateReportRequest request) { + List codebaseMetrics = MetricsUtil + .getCodeBaseMetrics(request.getMetrics().stream().map(String::toLowerCase).toList()); + Gson gson = new Gson(); + GenerateReportRequest result = gson.fromJson(gson.toJson(request), GenerateReportRequest.class); + result.setMetrics(codebaseMetrics); + return result; + } + } diff --git a/backend/src/main/java/heartbeat/controller/report/dto/request/ReportType.java b/backend/src/main/java/heartbeat/controller/report/dto/request/ReportType.java new file mode 100644 index 0000000000..2ef7a05d75 --- /dev/null +++ b/backend/src/main/java/heartbeat/controller/report/dto/request/ReportType.java @@ -0,0 +1,15 @@ +package heartbeat.controller.report.dto.request; + +public enum ReportType { + + BOARD, DORA; + + public static ReportType fromValue(String type) { + return switch (type) { + case "board" -> BOARD; + case "dora" -> DORA; + default -> throw new IllegalArgumentException("ReportType not found!"); + }; + } + +} diff --git a/backend/src/main/java/heartbeat/controller/source/GithubController.java b/backend/src/main/java/heartbeat/controller/source/GithubController.java index 31a2d8b869..3f9b16676d 100644 --- a/backend/src/main/java/heartbeat/controller/source/GithubController.java +++ b/backend/src/main/java/heartbeat/controller/source/GithubController.java @@ -28,11 +28,13 @@ @Log4j2 public class GithubController { + public static final String TOKEN_PATTER = "^(ghp|gho|ghu|ghs|ghr)_([a-zA-Z0-9]{36})$"; + private final GitHubService gitHubService; @PostMapping @CrossOrigin - @Deprecated + @Deprecated(since = "frontend completed") @ResponseStatus(HttpStatus.OK) public GitHubResponse getRepos(@RequestBody @Valid SourceControlDTO sourceControlDTO) { log.info("Start to get repos by token"); @@ -45,24 +47,34 @@ public GitHubResponse getRepos(@RequestBody @Valid SourceControlDTO sourceContro @PostMapping("/{sourceType}/verify") public ResponseEntity verifyToken(@PathVariable @NotBlank String sourceType, @RequestBody @Valid SourceControlDTO sourceControlDTO) { - if (!SourceTypeEnum.isValidType(sourceType)) { - throw new BadRequestException("Source type is incorrect."); + log.info("Start to verify source type: {} token.", sourceType); + switch (SourceType.matchSourceType(sourceType)) { + case GITHUB -> { + gitHubService.verifyTokenV2(sourceControlDTO.getToken()); + log.info("Successfully verify source type: {} token.", sourceType); + } + default -> { + log.error("Failed to verify source type: {} token.", sourceType); + throw new BadRequestException("Source type is incorrect."); + } } - log.info("Start to verify token"); - gitHubService.verifyTokenV2(sourceControlDTO.getToken()); - log.info("Successfully to verify token"); return ResponseEntity.status(HttpStatus.NO_CONTENT).build(); } @PostMapping("/{sourceType}/repos/branches/{branch}/verify") public ResponseEntity verifyBranch(@PathVariable @NotBlank String sourceType, @PathVariable @NotBlank String branch, @RequestBody @Valid VerifyBranchRequest request) { - if (!SourceTypeEnum.isValidType(sourceType)) { - throw new BadRequestException("Source type is incorrect."); + log.info("Start to verify source type: {} branch: {}.", sourceType, branch); + switch (SourceType.matchSourceType(sourceType)) { + case GITHUB -> { + gitHubService.verifyCanReadTargetBranch(request.getRepository(), branch, request.getToken()); + log.info("Successfully verify source type: {} branch: {}.", sourceType, branch); + } + default -> { + log.error("Failed to verify source type: {} branch: {}.", sourceType, branch); + throw new BadRequestException("Source type is incorrect."); + } } - log.info("Start to verify target branch."); - gitHubService.verifyCanReadTargetBranch(request.getRepository(), branch, request.getToken()); - log.info("Successfully to verify target branch."); return ResponseEntity.status(HttpStatus.NO_CONTENT).build(); } diff --git a/backend/src/main/java/heartbeat/controller/source/SourceType.java b/backend/src/main/java/heartbeat/controller/source/SourceType.java new file mode 100644 index 0000000000..daa583dcef --- /dev/null +++ b/backend/src/main/java/heartbeat/controller/source/SourceType.java @@ -0,0 +1,25 @@ +package heartbeat.controller.source; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Getter +@NoArgsConstructor +@AllArgsConstructor +public enum SourceType { + + GITHUB("github"), + + ANOTHERSOURCETYPE("anotherSourceType"); + + private String value; + + public static SourceType matchSourceType(String value) { + return switch (value) { + case "github" -> GITHUB; + default -> ANOTHERSOURCETYPE; + }; + } + +} diff --git a/backend/src/main/java/heartbeat/controller/source/SourceTypeEnum.java b/backend/src/main/java/heartbeat/controller/source/SourceTypeEnum.java deleted file mode 100644 index cd110e9dd1..0000000000 --- a/backend/src/main/java/heartbeat/controller/source/SourceTypeEnum.java +++ /dev/null @@ -1,29 +0,0 @@ -package heartbeat.controller.source; - -import lombok.AllArgsConstructor; -import lombok.Getter; -import lombok.NoArgsConstructor; - -import java.util.EnumSet; -import java.util.Set; -import java.util.stream.Collectors; - -@Getter -@NoArgsConstructor -@AllArgsConstructor -public enum SourceTypeEnum { - - GITHUB("GitHub"); - - private String value; - - private static final Set SourceTypes = EnumSet.allOf(SourceTypeEnum.class) - .stream() - .map(SourceTypeEnum::getValue) - .collect(Collectors.toSet()); - - public static boolean isValidType(String value) { - return SourceTypes.contains(value); - } - -} diff --git a/backend/src/main/java/heartbeat/controller/source/dto/SourceControlDTO.java b/backend/src/main/java/heartbeat/controller/source/dto/SourceControlDTO.java index 0544070452..93c0672e17 100644 --- a/backend/src/main/java/heartbeat/controller/source/dto/SourceControlDTO.java +++ b/backend/src/main/java/heartbeat/controller/source/dto/SourceControlDTO.java @@ -8,6 +8,8 @@ import lombok.NoArgsConstructor; import lombok.Setter; +import static heartbeat.controller.source.GithubController.TOKEN_PATTER; + @Builder @NoArgsConstructor @AllArgsConstructor @@ -16,7 +18,7 @@ public class SourceControlDTO { @NotNull(message = "Token cannot be empty.") - @Pattern(regexp = "^(ghp|gho|ghu|ghs|ghr)_([a-zA-Z0-9]{36})$", message = "token's pattern is incorrect") + @Pattern(regexp = TOKEN_PATTER, message = "token's pattern is incorrect") private String token; } diff --git a/backend/src/main/java/heartbeat/controller/source/dto/VerifyBranchRequest.java b/backend/src/main/java/heartbeat/controller/source/dto/VerifyBranchRequest.java index 1fb7675b55..dc25ef6f80 100644 --- a/backend/src/main/java/heartbeat/controller/source/dto/VerifyBranchRequest.java +++ b/backend/src/main/java/heartbeat/controller/source/dto/VerifyBranchRequest.java @@ -9,6 +9,8 @@ import lombok.NoArgsConstructor; import lombok.Setter; +import static heartbeat.controller.source.GithubController.TOKEN_PATTER; + @Builder @NoArgsConstructor @AllArgsConstructor @@ -17,7 +19,7 @@ public class VerifyBranchRequest { @NotNull(message = "Token cannot be empty.") - @Pattern(regexp = "^(ghp|gho|ghu|ghs|ghr)_([a-zA-Z0-9]{36})$", message = "token's pattern is incorrect") + @Pattern(regexp = TOKEN_PATTER, message = "token's pattern is incorrect") private String token; @NotBlank(message = "Repository is required.") diff --git a/backend/src/main/java/heartbeat/service/board/jira/JiraService.java b/backend/src/main/java/heartbeat/service/board/jira/JiraService.java index b7773dc746..73aed6d84f 100644 --- a/backend/src/main/java/heartbeat/service/board/jira/JiraService.java +++ b/backend/src/main/java/heartbeat/service/board/jira/JiraService.java @@ -119,13 +119,13 @@ public String verify(BoardType boardType, BoardVerifyRequestParam boardVerifyReq } catch (RuntimeException e) { Throwable cause = Optional.ofNullable(e.getCause()).orElse(e); - log.error("Failed when call Jira to verify board, board id: {}, e: {}", - boardVerifyRequestParam.getBoardId(), cause.getMessage()); + log.error("Failed to call Jira to verify board, board id: {}, e: {}", boardVerifyRequestParam.getBoardId(), + cause.getMessage()); if (cause instanceof BaseException baseException) { throw baseException; } throw new InternalServerErrorException( - String.format("Failed when call Jira to verify board, cause is %s", cause.getMessage())); + String.format("Failed to call Jira to verify board, cause is %s", cause.getMessage())); } } @@ -138,7 +138,7 @@ public BoardConfigDTO getInfo(BoardType boardType, BoardRequestParam boardReques String jiraBoardStyle = jiraFeignClient .getProject(baseUrl, boardRequestParam.getProjectKey(), boardRequestParam.getToken()) .getStyle(); - BoardType jiraBoardType = "classic".equals(jiraBoardStyle) ? BoardType.CLASSIC_JIRA : BoardType.JIRA; + BoardType jiraBoardType = BoardType.fromStyle(jiraBoardStyle); Map> partitions = getTargetFieldAsync(baseUrl, boardRequestParam).join() .stream() @@ -159,16 +159,17 @@ public BoardConfigDTO getInfo(BoardType boardType, BoardRequestParam boardReques } catch (RuntimeException e) { Throwable cause = Optional.ofNullable(e.getCause()).orElse(e); - log.error("Failed when call Jira to get board config, project key: {}, board id: {}, e: {}", + log.error("Failed to call Jira to get board info, project key: {}, board id: {}, e: {}", boardRequestParam.getBoardId(), boardRequestParam.getProjectKey(), cause.getMessage()); if (cause instanceof BaseException baseException) { throw baseException; } throw new InternalServerErrorException( - String.format("Failed when call Jira to get board config, cause is %s", cause.getMessage())); + String.format("Failed to call Jira to get board info, cause is %s", cause.getMessage())); } } + @Deprecated public BoardConfigDTO getJiraConfiguration(BoardType boardType, BoardRequestParam boardRequestParam) { URI baseUrl = urlGenerator.getUri(boardRequestParam.getSite()); try { @@ -192,13 +193,13 @@ public BoardConfigDTO getJiraConfiguration(BoardType boardType, BoardRequestPara } catch (RuntimeException e) { Throwable cause = Optional.ofNullable(e.getCause()).orElse(e); - log.error("Failed when call Jira to get board config, project key: {}, board id: {}, e: {}", + log.error("Failed to call Jira to get board config, project key: {}, board id: {}, e: {}", boardRequestParam.getBoardId(), boardRequestParam.getProjectKey(), cause.getMessage()); if (cause instanceof BaseException baseException) { throw baseException; } throw new InternalServerErrorException( - String.format("Failed when call Jira to get board config, cause is %s", cause.getMessage())); + String.format("Failed to call Jira to get board config, cause is %s", cause.getMessage())); } } diff --git a/backend/src/main/java/heartbeat/service/report/GenerateReporterService.java b/backend/src/main/java/heartbeat/service/report/GenerateReporterService.java index 3fa0bd27a8..ec1ca6c9a8 100644 --- a/backend/src/main/java/heartbeat/service/report/GenerateReporterService.java +++ b/backend/src/main/java/heartbeat/service/report/GenerateReporterService.java @@ -28,7 +28,6 @@ import heartbeat.controller.report.dto.request.ExportCSVRequest; import heartbeat.controller.report.dto.request.GenerateReportRequest; import heartbeat.controller.report.dto.request.JiraBoardSetting; -import heartbeat.controller.report.dto.request.RequireDataEnum; import heartbeat.controller.report.dto.response.BoardCSVConfig; import heartbeat.controller.report.dto.response.BoardCSVConfigEnum; import heartbeat.controller.report.dto.response.LeadTimeInfo; @@ -71,16 +70,18 @@ import java.util.Map.Entry; import java.util.Objects; import java.util.Optional; +import java.util.concurrent.CompletableFuture; import java.util.function.Function; import java.util.stream.Stream; import heartbeat.util.IdUtil; +import heartbeat.util.MetricsUtil; import lombok.RequiredArgsConstructor; import lombok.extern.log4j.Log4j2; import lombok.val; +import org.apache.commons.collections.CollectionUtils; import org.springframework.core.io.InputStreamResource; import org.springframework.stereotype.Service; -import org.springframework.util.CollectionUtils; import org.springframework.util.ObjectUtils; import org.springframework.util.StringUtils; @@ -127,21 +128,6 @@ public class GenerateReporterService { private final AsyncExceptionHandler asyncExceptionHandler; - private final List kanbanMetrics = Stream - .of(RequireDataEnum.VELOCITY, RequireDataEnum.CYCLE_TIME, RequireDataEnum.CLASSIFICATION) - .map(RequireDataEnum::getValue) - .toList(); - - private final List buildKiteMetrics = Stream - .of(RequireDataEnum.CHANGE_FAILURE_RATE, RequireDataEnum.DEPLOYMENT_FREQUENCY, - RequireDataEnum.MEAN_TIME_TO_RECOVERY) - .map(RequireDataEnum::getValue) - .toList(); - - private final List codebaseMetrics = Stream.of(RequireDataEnum.LEAD_TIME_FOR_CHANGES) - .map(RequireDataEnum::getValue) - .toList(); - private static StoryPointsAndCycleTimeRequest buildStoryPointsAndCycleTimeRequest(JiraBoardSetting jiraBoardSetting, String startTime, String endTime) { return StoryPointsAndCycleTimeRequest.builder() @@ -213,17 +199,94 @@ else if (jsonObject.has("key")) { return result; } + public void generateBoardReport(GenerateReportRequest request) { + initializeMetricsDataReadyInHandler(request.getCsvTimeStamp(), request.getMetrics()); + log.info( + "Start to generate board report, _metrics: {}, _considerHoliday: {}, _startTime: {}, _endTime: {}, _boardReportId: {}", + request.getMetrics(), request.getConsiderHoliday(), request.getStartTime(), request.getEndTime(), + IdUtil.getBoardReportId(request.getCsvTimeStamp())); + CompletableFuture.runAsync(() -> { + try { + saveReporterInHandler(generateReporter(request), IdUtil.getBoardReportId(request.getCsvTimeStamp())); + updateMetricsDataReadyInHandler(request.getCsvTimeStamp(), request.getMetrics()); + log.info( + "Successfully generate board report, _metrics: {}, _considerHoliday: {}, _startTime: {}, _endTime: {}, _boardReportId: {}", + request.getMetrics(), request.getConsiderHoliday(), request.getStartTime(), + request.getEndTime(), IdUtil.getBoardReportId(request.getCsvTimeStamp())); + } + catch (BaseException e) { + asyncExceptionHandler.put(IdUtil.getBoardReportId(request.getCsvTimeStamp()), e); + } + }); + } + + public void generateDoraReport(GenerateReportRequest request) { + MetricsDataReady metricsDataStatus = getMetricsStatus(request.getMetrics(), Boolean.TRUE); + initializeMetricsDataReadyInHandler(request.getCsvTimeStamp(), request.getMetrics()); + if (Objects.nonNull(metricsDataStatus.isSourceControlMetricsReady()) + && metricsDataStatus.isSourceControlMetricsReady()) { + generateSourceControlReport(request); + } + if (Objects.nonNull(metricsDataStatus.isPipelineMetricsReady()) && metricsDataStatus.isPipelineMetricsReady()) { + generatePipelineReport(request); + } + generateCsvForDora(request); + } + + private void generatePipelineReport(GenerateReportRequest request) { + GenerateReportRequest pipelineRequest = request.convertToPipelineRequest(request); + log.info( + "Start to generate pipeline report, _metrics: {}, _considerHoliday: {}, _startTime: {}, _endTime: {}, _pipelineReportId: {}", + pipelineRequest.getMetrics(), pipelineRequest.getConsiderHoliday(), pipelineRequest.getStartTime(), + pipelineRequest.getEndTime(), IdUtil.getPipelineReportId(request.getCsvTimeStamp())); + CompletableFuture.runAsync(() -> { + try { + saveReporterInHandler(generateReporter(pipelineRequest), + IdUtil.getPipelineReportId(pipelineRequest.getCsvTimeStamp())); + updateMetricsDataReadyInHandler(pipelineRequest.getCsvTimeStamp(), pipelineRequest.getMetrics()); + log.info( + "Successfully generate pipeline report, _metrics: {}, _considerHoliday: {}, _startTime: {}, _endTime: {}, _pipelineReportId: {}", + pipelineRequest.getMetrics(), pipelineRequest.getConsiderHoliday(), + pipelineRequest.getStartTime(), pipelineRequest.getEndTime(), + IdUtil.getPipelineReportId(request.getCsvTimeStamp())); + } + catch (BaseException e) { + asyncExceptionHandler.put(IdUtil.getPipelineReportId(request.getCsvTimeStamp()), e); + } + }); + } + + private void generateSourceControlReport(GenerateReportRequest request) { + GenerateReportRequest sourceControlRequest = request.convertToSourceControlRequest(request); + log.info( + "Start to generate source control report, _metrics: {}, _considerHoliday: {}, _startTime: {}, _endTime: {}, _sourceControlReportId: {}", + sourceControlRequest.getMetrics(), sourceControlRequest.getConsiderHoliday(), + sourceControlRequest.getStartTime(), sourceControlRequest.getEndTime(), + IdUtil.getSourceControlReportId(request.getCsvTimeStamp())); + CompletableFuture.runAsync(() -> { + try { + saveReporterInHandler(generateReporter(sourceControlRequest), + IdUtil.getSourceControlReportId(sourceControlRequest.getCsvTimeStamp())); + updateMetricsDataReadyInHandler(sourceControlRequest.getCsvTimeStamp(), + sourceControlRequest.getMetrics()); + log.info( + "Successfully generate codebase report, _metrics: {}, _considerHoliday: {}, _startTime: {}, _endTime: {}, _sourceControlReportId: {}", + sourceControlRequest.getMetrics(), sourceControlRequest.getConsiderHoliday(), + sourceControlRequest.getStartTime(), sourceControlRequest.getEndTime(), + IdUtil.getSourceControlReportId(request.getCsvTimeStamp())); + } + catch (BaseException e) { + asyncExceptionHandler.put(IdUtil.getSourceControlReportId(request.getCsvTimeStamp()), e); + } + }); + } + public synchronized ReportResponse generateReporter(GenerateReportRequest request) { workDay.changeConsiderHolidayMode(request.getConsiderHoliday()); // fetch data for calculate List lowMetrics = request.getMetrics().stream().map(String::toLowerCase).toList(); FetchedData fetchedData = fetchOriginalData(request, lowMetrics); - if (lowMetrics.stream().anyMatch(this.codebaseMetrics::contains) - || lowMetrics.stream().anyMatch(this.buildKiteMetrics::contains)) { - generateCSVForPipeline(request, fetchedData.getBuildKiteData()); - } - ReportResponse reportResponse = new ReportResponse(EXPORT_CSV_VALIDITY_TIME); JiraBoardSetting jiraBoardSetting = request.getJiraBoardSetting(); @@ -258,21 +321,21 @@ public synchronized ReportResponse generateReporter(GenerateReportRequest reques private FetchedData fetchOriginalData(GenerateReportRequest request, List lowMetrics) { FetchedData fetchedData = new FetchedData(); - if (lowMetrics.stream().anyMatch(this.kanbanMetrics::contains)) { + if (CollectionUtils.isNotEmpty(MetricsUtil.getBoardMetrics(lowMetrics))) { if (request.getJiraBoardSetting() == null) throw new BadRequestException("Failed to fetch Jira info due to Jira board setting is null."); CardCollectionInfo cardCollectionInfo = fetchDataFromKanban(request); fetchedData.setCardCollectionInfo(cardCollectionInfo); } - if (lowMetrics.stream().anyMatch(this.codebaseMetrics::contains)) { + if (CollectionUtils.isNotEmpty(MetricsUtil.getCodeBaseMetrics(lowMetrics))) { if (request.getCodebaseSetting() == null) throw new BadRequestException("Failed to fetch Github info due to code base setting is null."); BuildKiteData buildKiteData = fetchGithubData(request); fetchedData.setBuildKiteData(buildKiteData); } - if (lowMetrics.stream().anyMatch(this.buildKiteMetrics::contains)) { + if (CollectionUtils.isNotEmpty(MetricsUtil.getPipelineMetrics(lowMetrics))) { if (request.getBuildKiteSetting() == null) throw new BadRequestException("Failed to fetch BuildKite info due to BuildKite setting is null."); FetchedData.BuildKiteData buildKiteData = fetchBuildKiteInfo(request); @@ -287,6 +350,14 @@ private FetchedData fetchOriginalData(GenerateReportRequest request, List lowMetrics = request.getMetrics().stream().map(String::toLowerCase).toList(); + FetchedData fetchedData = fetchOriginalData(request, lowMetrics); + + generateCSVForPipeline(request, fetchedData.getBuildKiteData()); + + } + private CardCollection fetchRealDoneCardCollection(GenerateReportRequest request) { JiraBoardSetting jiraBoardSetting = request.getJiraBoardSetting(); StoryPointsAndCycleTimeRequest storyPointsAndCycleTimeRequest = buildStoryPointsAndCycleTimeRequest( @@ -638,20 +709,17 @@ private Boolean checkCurrentMetricsReadyState(Boolean exist, Boolean previousVal } private MetricsDataReady getMetricsStatus(List metrics, Boolean flag) { - boolean boardMetricsExist = metrics.stream().map(String::toLowerCase).anyMatch(this.kanbanMetrics::contains); - boolean codebaseMetricsExist = metrics.stream() - .map(String::toLowerCase) - .anyMatch(this.codebaseMetrics::contains); - boolean buildKiteMetricsExist = metrics.stream() - .map(String::toLowerCase) - .anyMatch(this.buildKiteMetrics::contains); + List lowerMetrics = metrics.stream().map(String::toLowerCase).toList(); + boolean boardMetricsExist = CollectionUtils.isNotEmpty(MetricsUtil.getBoardMetrics(lowerMetrics)); + boolean codebaseMetricsExist = CollectionUtils.isNotEmpty(MetricsUtil.getCodeBaseMetrics(lowerMetrics)); + boolean buildKiteMetricsExist = CollectionUtils.isNotEmpty(MetricsUtil.getPipelineMetrics(lowerMetrics)); Boolean isBoardMetricsReady = boardMetricsExist ? flag : null; - Boolean isCodebaseMetricsReady = codebaseMetricsExist ? flag : null; - Boolean isBuildKiteMetricsReady = buildKiteMetricsExist ? flag : null; + Boolean isPipelineMetricsReady = buildKiteMetricsExist ? flag : null; + Boolean isSourceControlMetricsReady = codebaseMetricsExist ? flag : null; return MetricsDataReady.builder() .isBoardMetricsReady(isBoardMetricsReady) - .isPipelineMetricsReady(isBuildKiteMetricsReady) - .isSourceControlMetricsReady(isCodebaseMetricsReady) + .isPipelineMetricsReady(isPipelineMetricsReady) + .isSourceControlMetricsReady(isSourceControlMetricsReady) .build(); } @@ -691,10 +759,6 @@ private List generateCSVForPipelineWithCodebase(CodebaseSetting String endTime, BuildKiteData buildKiteData, List deploymentEnvironments) { List pipelineCSVInfos = new ArrayList<>(); - if (codebaseSetting == null && CollectionUtils.isEmpty(deploymentEnvironments)) { - return pipelineCSVInfos; - } - Map repoIdMap = getRepoMap(deploymentEnvironments); for (DeploymentEnvironment deploymentEnvironment : deploymentEnvironments) { String repoId = GithubUtil.getGithubUrlFullName(repoIdMap.get(deploymentEnvironment.getId())); @@ -742,7 +806,7 @@ public boolean checkGenerateReportIsDone(String reportTimeStamp) { throw new GenerateReportException("Failed to get report due to report time expires"); } BaseException boardException = asyncExceptionHandler.get(IdUtil.getBoardReportId(reportTimeStamp)); - BaseException doraException = asyncExceptionHandler.get(IdUtil.getDoraReportId(reportTimeStamp)); + BaseException doraException = asyncExceptionHandler.get(IdUtil.getPipelineReportId(reportTimeStamp)); handleAsyncException(boardException); handleAsyncException(doraException); return asyncReportRequestHandler.isReportReady(reportTimeStamp); @@ -805,7 +869,8 @@ public ReportResponse getReportFromHandler(String reportId) { public ReportResponse getComposedReportResponse(String reportId, boolean isReportReady) { ReportResponse boardReportResponse = getReportFromHandler(IdUtil.getBoardReportId(reportId)); - ReportResponse doraReportResponse = getReportFromHandler(IdUtil.getDoraReportId(reportId)); + ReportResponse doraReportResponse = getReportFromHandler(IdUtil.getPipelineReportId(reportId)); + ReportResponse codebaseReportResponse = getReportFromHandler(IdUtil.getSourceControlReportId(reportId)); MetricsDataReady metricsDataReady = asyncReportRequestHandler.getMetricsDataReady(reportId); ReportResponse response = Optional.ofNullable(boardReportResponse).orElse(doraReportResponse); @@ -817,7 +882,7 @@ public ReportResponse getComposedReportResponse(String reportId, boolean isRepor .deploymentFrequency(getValueOrNull(doraReportResponse, ReportResponse::getDeploymentFrequency)) .changeFailureRate(getValueOrNull(doraReportResponse, ReportResponse::getChangeFailureRate)) .meanTimeToRecovery(getValueOrNull(doraReportResponse, ReportResponse::getMeanTimeToRecovery)) - .leadTimeForChanges(getValueOrNull(doraReportResponse, ReportResponse::getLeadTimeForChanges)) + .leadTimeForChanges(getValueOrNull(codebaseReportResponse, ReportResponse::getLeadTimeForChanges)) .isBoardMetricsReady(getValueOrNull(metricsDataReady, MetricsDataReady::isBoardMetricsReady)) .isPipelineMetricsReady(getValueOrNull(metricsDataReady, MetricsDataReady::isPipelineMetricsReady)) .isSourceControlMetricsReady( diff --git a/backend/src/main/java/heartbeat/service/source/github/GitHubService.java b/backend/src/main/java/heartbeat/service/source/github/GitHubService.java index fa035be68d..5838fa3a12 100644 --- a/backend/src/main/java/heartbeat/service/source/github/GitHubService.java +++ b/backend/src/main/java/heartbeat/service/source/github/GitHubService.java @@ -41,6 +41,10 @@ @Log4j2 public class GitHubService { + public static final String TOKEN_TITLE = "token "; + + public static final String BEARER_TITLE = "Bearer "; + private final ThreadPoolTaskExecutor customTaskExecutor; private final GitHubFeignClient gitHubFeignClient; @@ -53,7 +57,7 @@ public void shutdownExecutor() { @Deprecated public GitHubResponse verifyToken(String githubToken) { try { - String token = "token " + githubToken; + String token = TOKEN_TITLE + githubToken; log.info("Start to query repository url by token"); CompletableFuture> githubReposByUserFuture = CompletableFuture .supplyAsync(() -> gitHubFeignClient.getAllRepos(token), customTaskExecutor); @@ -91,7 +95,7 @@ public GitHubResponse verifyToken(String githubToken) { public void verifyTokenV2(String githubToken) { try { - String token = "token " + githubToken; + String token = TOKEN_TITLE + githubToken; log.info("Start to request github with token"); gitHubFeignClient.verifyToken(token); log.info("Successfully verify token from github"); @@ -109,22 +113,23 @@ public void verifyTokenV2(String githubToken) { public void verifyCanReadTargetBranch(String repository, String branch, String githubToken) { try { - String token = "token " + githubToken; - log.info("Start to request github with token"); + String token = TOKEN_TITLE + githubToken; + log.info("Start to request github branch: {}", branch); gitHubFeignClient.verifyCanReadTargetBranch(GithubUtil.getGithubUrlFullName(repository), branch, token); - log.info("Successfully verify token from github"); + log.info("Successfully verify target branch for github, branch: {}", branch); } catch (NotFoundException e) { - throw new PermissionDenyException("Unable to read target branch"); + log.error("Failed to call GitHub with branch: {}, error: {} ", branch, e.getMessage()); + throw new PermissionDenyException(String.format("Unable to read target branch: %s", branch)); } catch (RuntimeException e) { Throwable cause = Optional.ofNullable(e.getCause()).orElse(e); - log.error("Failed to call GitHub with token_error: {} ", cause.getMessage()); + log.error("Failed to call GitHub branch:{} with error: {} ", branch, cause.getMessage()); if (cause instanceof BaseException baseException) { throw baseException; } throw new InternalServerErrorException( - String.format("Failed to call GitHub with token_error: %s", cause.getMessage())); + String.format("Failed to call GitHub branch: %s with error: %s", branch, cause.getMessage())); } } @@ -159,7 +164,7 @@ private CompletableFuture> getAllGitHubReposAsync(String token, public List fetchPipelinesLeadTime(List deployTimes, Map repositories, String token) { try { - String realToken = "Bearer " + token; + String realToken = BEARER_TITLE + token; List pipelineInfoOfRepositories = getInfoOfRepositories(deployTimes, repositories); @@ -329,7 +334,7 @@ public LeadTime mapLeadTimeWithInfo(PullRequestInfo pullRequestInfo, DeployInfo public CommitInfo fetchCommitInfo(String commitId, String repositoryId, String token) { try { - String realToken = "Bearer " + token; + String realToken = BEARER_TITLE + token; log.info("Start to get commit info, repoId: {},commitId: {}", repositoryId, commitId); CommitInfo commitInfo = gitHubFeignClient.getCommitInfo(repositoryId, commitId, realToken); log.info("Successfully get commit info, repoId: {},commitId: {}, author: {}", repositoryId, commitId, diff --git a/backend/src/main/java/heartbeat/util/IdUtil.java b/backend/src/main/java/heartbeat/util/IdUtil.java index 6d74881644..1fdfc1952b 100644 --- a/backend/src/main/java/heartbeat/util/IdUtil.java +++ b/backend/src/main/java/heartbeat/util/IdUtil.java @@ -4,14 +4,20 @@ public interface IdUtil { String BOARD_REPORT_PREFIX = "board-"; - String DORA_REPORT_PREFIX = "dora-"; + String PIPELINE_REPORT_PREFIX = "pipeline-"; + + String SOURCE_CONTROL_PREFIX = "sourceControl-"; static String getBoardReportId(String timeStamp) { return BOARD_REPORT_PREFIX + timeStamp; } - static String getDoraReportId(String timeStamp) { - return DORA_REPORT_PREFIX + timeStamp; + static String getPipelineReportId(String timeStamp) { + return PIPELINE_REPORT_PREFIX + timeStamp; + } + + static String getSourceControlReportId(String timeStamp) { + return SOURCE_CONTROL_PREFIX + timeStamp; } static String getTimeStampFromReportId(String reportId) { diff --git a/backend/src/main/java/heartbeat/util/MetricsUtil.java b/backend/src/main/java/heartbeat/util/MetricsUtil.java new file mode 100644 index 0000000000..58d6dee5eb --- /dev/null +++ b/backend/src/main/java/heartbeat/util/MetricsUtil.java @@ -0,0 +1,39 @@ +package heartbeat.util; + +import heartbeat.controller.report.dto.request.RequireDataEnum; + +import java.util.List; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +public interface MetricsUtil { + + List kanbanMetrics = Stream + .of(RequireDataEnum.VELOCITY, RequireDataEnum.CYCLE_TIME, RequireDataEnum.CLASSIFICATION) + .map(RequireDataEnum::getValue) + .toList(); + + List buildKiteMetrics = Stream + .of(RequireDataEnum.CHANGE_FAILURE_RATE, RequireDataEnum.DEPLOYMENT_FREQUENCY, + RequireDataEnum.MEAN_TIME_TO_RECOVERY) + .map(RequireDataEnum::getValue) + .toList(); + + List codebaseMetrics = Stream.of(RequireDataEnum.LEAD_TIME_FOR_CHANGES) + .map(RequireDataEnum::getValue) + .toList(); + + static List getPipelineMetrics(List metrics) { + return metrics.stream().filter(buildKiteMetrics::contains).collect(Collectors.toList()); + } + + static List getCodeBaseMetrics(List metrics) { + return metrics.stream().filter(codebaseMetrics::contains).collect(Collectors.toList()); + + } + + static List getBoardMetrics(List metrics) { + return metrics.stream().filter(kanbanMetrics::contains).collect(Collectors.toList()); + } + +} diff --git a/backend/src/test/java/heartbeat/controller/board/JiraControllerTest.java b/backend/src/test/java/heartbeat/controller/board/JiraControllerTest.java index bdc58f7ec1..6ce6ea355c 100644 --- a/backend/src/test/java/heartbeat/controller/board/JiraControllerTest.java +++ b/backend/src/test/java/heartbeat/controller/board/JiraControllerTest.java @@ -40,6 +40,7 @@ public class JiraControllerTest { private MockMvc mockMvc; @Test + @Deprecated void shouldReturnCorrectBoardConfigResponseWhenGivenTheCorrectBoardRequest() throws Exception { BoardConfigDTO boardConfigDTO = BOARD_CONFIG_RESPONSE_BUILDER().build(); BoardRequestParam boardRequestParam = BOARD_REQUEST_BUILDER().build(); @@ -85,6 +86,7 @@ void shouldReturnCorrectBoardVerificationResponseWhenGivenTheCorrectBoardRequest } @Test + @Deprecated void shouldHandleServiceExceptionAndReturnWithStatusAndMessage() throws Exception { RequestFailedException mockException = mock(RequestFailedException.class); String message = "message"; diff --git a/backend/src/test/java/heartbeat/controller/board/dto/request/BoardTypeTest.java b/backend/src/test/java/heartbeat/controller/board/dto/request/BoardTypeTest.java index 5cec2d13a1..50d9eadfd3 100644 --- a/backend/src/test/java/heartbeat/controller/board/dto/request/BoardTypeTest.java +++ b/backend/src/test/java/heartbeat/controller/board/dto/request/BoardTypeTest.java @@ -9,8 +9,12 @@ public class BoardTypeTest { public void shouldConvertValueToType() { BoardType jiraBoardType = BoardType.fromValue("jira"); BoardType classicJiraBoardType = BoardType.fromValue("classic-jira"); + BoardType jiraBoardStyle = BoardType.fromStyle("next-gen"); + BoardType classicJiraBoardStyle = BoardType.fromStyle("classic"); assertEquals(jiraBoardType, BoardType.JIRA); assertEquals(classicJiraBoardType, BoardType.CLASSIC_JIRA); + assertEquals(jiraBoardStyle, BoardType.JIRA); + assertEquals(classicJiraBoardStyle, BoardType.CLASSIC_JIRA); } } diff --git a/backend/src/test/java/heartbeat/controller/report/GenerateReporterControllerTest.java b/backend/src/test/java/heartbeat/controller/report/GenerateReporterControllerTest.java index d2b939e491..8c91e7b35c 100644 --- a/backend/src/test/java/heartbeat/controller/report/GenerateReporterControllerTest.java +++ b/backend/src/test/java/heartbeat/controller/report/GenerateReporterControllerTest.java @@ -3,17 +3,11 @@ import com.fasterxml.jackson.databind.ObjectMapper; import com.jayway.jsonpath.JsonPath; import heartbeat.controller.report.dto.request.ExportCSVRequest; -import heartbeat.controller.report.dto.request.GenerateBoardReportRequest; -import heartbeat.controller.report.dto.request.GenerateDoraReportRequest; -import heartbeat.controller.report.dto.response.AvgDeploymentFrequency; -import heartbeat.controller.report.dto.response.DeploymentFrequency; +import heartbeat.controller.report.dto.request.GenerateReportRequest; import heartbeat.controller.report.dto.response.ReportResponse; -import heartbeat.controller.report.dto.response.Velocity; import heartbeat.exception.GenerateReportException; -import heartbeat.exception.RequestFailedException; import heartbeat.service.report.GenerateReporterService; import heartbeat.handler.AsyncExceptionHandler; -import heartbeat.util.IdUtil; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.springframework.beans.factory.annotation.Autowired; @@ -28,11 +22,9 @@ import java.io.ByteArrayInputStream; import java.io.File; - import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -141,136 +133,46 @@ void shouldReturnWhenExportCsv() throws Exception { } @Test - void shouldReturnAcceptedStatusAndCallbackUrlAndIntervalWhenCallBoardReports() throws Exception { - ReportResponse expectedResponse = ReportResponse.builder() - .velocity(Velocity.builder().velocityForSP(10).build()) - .deploymentFrequency(DeploymentFrequency.builder() - .avgDeploymentFrequency(new AvgDeploymentFrequency("Average", 0.10F)) - .build()) - .build(); - + void shouldReturnCallBackUrlWithAcceptedStatusAndInvokeGenerateDoraReportWhenReportTypeIsDora() throws Exception { ObjectMapper mapper = new ObjectMapper(); - GenerateBoardReportRequest request = mapper.readValue(new File(REQUEST_FILE_PATH), - GenerateBoardReportRequest.class); + GenerateReportRequest request = mapper.readValue(new File(REQUEST_FILE_PATH), GenerateReportRequest.class); String currentTimeStamp = "1685010080107"; request.setCsvTimeStamp(currentTimeStamp); - when(generateReporterService.generateReporter(request.convertToReportRequest())).thenReturn(expectedResponse); - doNothing().when(generateReporterService).initializeMetricsDataReadyInHandler(any(), any()); - doNothing().when(generateReporterService).saveReporterInHandler(any(), any()); - doNothing().when(generateReporterService).updateMetricsDataReadyInHandler(any(), any()); - MockHttpServletResponse response = mockMvc - .perform(post("/reports/board").contentType(MediaType.APPLICATION_JSON) + .perform(post("/reports/dora").contentType(MediaType.APPLICATION_JSON) .content(mapper.writeValueAsString(request))) .andExpect(status().isAccepted()) .andReturn() .getResponse(); - final var callbackUrl = JsonPath.parse(response.getContentAsString()).read("$.callbackUrl").toString(); - final var interval = JsonPath.parse(response.getContentAsString()).read("$.interval").toString(); - assertEquals("/reports/" + currentTimeStamp, callbackUrl); - assertEquals("10", interval); - } - - @Test - void shouldGetExceptionAndPutInExceptionMapWhenCallBoardReport() throws Exception { - ObjectMapper mapper = new ObjectMapper(); - GenerateBoardReportRequest request = mapper.readValue(new File(REQUEST_FILE_PATH), - GenerateBoardReportRequest.class); - String currentTimeStamp = "1685010080107"; - request.setCsvTimeStamp(currentTimeStamp); - - RequestFailedException requestFailedException = new RequestFailedException(402, "Client Error"); - when(generateReporterService.generateReporter(request.convertToReportRequest())) - .thenThrow(requestFailedException); - doNothing().when(generateReporterService).initializeMetricsDataReadyInHandler(any(), any()); - doNothing().when(generateReporterService).saveReporterInHandler(any(), any()); - doNothing().when(generateReporterService).updateMetricsDataReadyInHandler(any(), any()); - - MockHttpServletResponse response = mockMvc - .perform(post("/reports/board").contentType(MediaType.APPLICATION_JSON) - .content(mapper.writeValueAsString(request))) - .andExpect(status().isAccepted()) - .andReturn() - .getResponse(); + Thread.sleep(2000); + verify(generateReporterService, times(1)).generateDoraReport(request); + verify(generateReporterService, times(0)).generateBoardReport(request); final var callbackUrl = JsonPath.parse(response.getContentAsString()).read("$.callbackUrl").toString(); final var interval = JsonPath.parse(response.getContentAsString()).read("$.interval").toString(); assertEquals("/reports/" + currentTimeStamp, callbackUrl); assertEquals("10", interval); - - Thread.sleep(2000L); - verify(generateReporterService).initializeMetricsDataReadyInHandler(request.getCsvTimeStamp(), - request.getMetrics()); - verify(generateReporterService, times(0)).saveReporterInHandler(any(), any()); - verify(generateReporterService, times(0)).updateMetricsDataReadyInHandler(request.getCsvTimeStamp(), - request.getMetrics()); - verify(asyncExceptionHandler).put(IdUtil.getBoardReportId(currentTimeStamp), requestFailedException); } @Test - void shouldGetExceptionAndPutInExceptionMapWhenCallDoraReport() throws Exception { + void shouldReturnCallBackUrlWithAcceptedStatusAndInvokeGenerateBoardReportWhenReportTypeIsBoard() throws Exception { ObjectMapper mapper = new ObjectMapper(); - GenerateDoraReportRequest request = mapper.readValue(new File(REQUEST_FILE_PATH), - GenerateDoraReportRequest.class); + GenerateReportRequest request = mapper.readValue(new File(REQUEST_FILE_PATH), GenerateReportRequest.class); String currentTimeStamp = "1685010080107"; request.setCsvTimeStamp(currentTimeStamp); - RequestFailedException requestFailedException = new RequestFailedException(402, "Client Error"); - when(generateReporterService.generateReporter(request.convertToReportRequest())) - .thenThrow(requestFailedException); - doNothing().when(generateReporterService).initializeMetricsDataReadyInHandler(any(), any()); - doNothing().when(generateReporterService).saveReporterInHandler(any(), any()); - doNothing().when(generateReporterService).updateMetricsDataReadyInHandler(any(), any()); - MockHttpServletResponse response = mockMvc - .perform(post("/reports/dora").contentType(MediaType.APPLICATION_JSON) + .perform(post("/reports/board").contentType(MediaType.APPLICATION_JSON) .content(mapper.writeValueAsString(request))) .andExpect(status().isAccepted()) .andReturn() .getResponse(); - final var callbackUrl = JsonPath.parse(response.getContentAsString()).read("$.callbackUrl").toString(); - final var interval = JsonPath.parse(response.getContentAsString()).read("$.interval").toString(); - assertEquals("/reports/" + currentTimeStamp, callbackUrl); - assertEquals("10", interval); - - Thread.sleep(2000L); - verify(generateReporterService).initializeMetricsDataReadyInHandler(request.getCsvTimeStamp(), - request.getMetrics()); - verify(generateReporterService, times(0)).saveReporterInHandler(any(), any()); - verify(generateReporterService, times(0)).updateMetricsDataReadyInHandler(request.getCsvTimeStamp(), - request.getMetrics()); - verify(asyncExceptionHandler).put(IdUtil.getDoraReportId(currentTimeStamp), requestFailedException); - } - - @Test - void shouldReturnAcceptedStatusAndCallbackUrlAndIntervalWhenCallDoraReports() throws Exception { - ReportResponse expectedResponse = ReportResponse.builder() - .deploymentFrequency(DeploymentFrequency.builder() - .avgDeploymentFrequency(new AvgDeploymentFrequency("Average", 0.10F)) - .build()) - .velocity(Velocity.builder().velocityForSP(10).build()) - .deploymentFrequency(DeploymentFrequency.builder() - .avgDeploymentFrequency(new AvgDeploymentFrequency("Average", 0.10F)) - .build()) - .build(); - - ObjectMapper mapper = new ObjectMapper(); - GenerateBoardReportRequest request = mapper.readValue(new File(REQUEST_FILE_PATH), - GenerateBoardReportRequest.class); - String currentTimeStamp = "1685010080107"; - request.setCsvTimeStamp(currentTimeStamp); - - when(generateReporterService.generateReporter(request.convertToReportRequest())).thenReturn(expectedResponse); - - MockHttpServletResponse response = mockMvc - .perform(post("/reports/dora").contentType(MediaType.APPLICATION_JSON) - .content(mapper.writeValueAsString(request))) - .andExpect(status().isAccepted()) - .andReturn() - .getResponse(); + Thread.sleep(2000); + verify(generateReporterService, times(0)).generateDoraReport(request); + verify(generateReporterService, times(1)).generateBoardReport(request); final var callbackUrl = JsonPath.parse(response.getContentAsString()).read("$.callbackUrl").toString(); final var interval = JsonPath.parse(response.getContentAsString()).read("$.interval").toString(); diff --git a/backend/src/test/java/heartbeat/controller/report/dto/request/ReportTypeTest.java b/backend/src/test/java/heartbeat/controller/report/dto/request/ReportTypeTest.java new file mode 100644 index 0000000000..1966306d96 --- /dev/null +++ b/backend/src/test/java/heartbeat/controller/report/dto/request/ReportTypeTest.java @@ -0,0 +1,25 @@ +package heartbeat.controller.report.dto.request; + +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.junit.jupiter.api.Assertions.assertEquals; + +class ReportTypeTest { + + @Test + public void shouldConvertValueToType() { + ReportType boardType = ReportType.fromValue("board"); + ReportType doraType = ReportType.fromValue("dora"); + + assertEquals(boardType, ReportType.BOARD); + assertEquals(doraType, ReportType.DORA); + } + + @Test + public void shouldThrowExceptionWhenDateTypeNotSupported() { + assertThatThrownBy(() -> ReportType.fromValue("unknown")).isInstanceOf(IllegalArgumentException.class) + .hasMessageContaining("ReportType not found!"); + } + +} diff --git a/backend/src/test/java/heartbeat/controller/report/request.json b/backend/src/test/java/heartbeat/controller/report/request.json index e35ab296be..4b30ad4d09 100644 --- a/backend/src/test/java/heartbeat/controller/report/request.json +++ b/backend/src/test/java/heartbeat/controller/report/request.json @@ -9,6 +9,7 @@ ], "startTime": 1661702400000, "endTime": 1662739199000, + "considerHoliday": true, "buildKiteSetting": { "type": "BuildKite", "token": "0d6*********************************a57a", @@ -36,6 +37,11 @@ } ] }, + "codebaseSetting": { + "type": "GitHub", + "token": "ghp*********************************FX", + "leadTime": [] + }, "jiraBoardSetting": { "type": "classic jira", "token": "Basic exxxxxhhbmdxxxxvYixxxxxxZzlxxxxxx3Rqaxxxxxlodxxxxx9BRUU3", diff --git a/backend/src/test/java/heartbeat/controller/source/GithubControllerTest.java b/backend/src/test/java/heartbeat/controller/source/GithubControllerTest.java index e6f36ca419..3a99f3fa1c 100644 --- a/backend/src/test/java/heartbeat/controller/source/GithubControllerTest.java +++ b/backend/src/test/java/heartbeat/controller/source/GithubControllerTest.java @@ -67,7 +67,7 @@ void shouldReturnNoContentStatusWhenVerifyToken() throws Exception { SourceControlDTO sourceControlDTO = SourceControlDTO.builder().token(GITHUB_TOKEN).build(); mockMvc - .perform(post("/source-control/GitHub/verify") + .perform(post("/source-control/github/verify") .content(new ObjectMapper().writeValueAsString(sourceControlDTO)) .contentType(MediaType.APPLICATION_JSON)) .andExpect(status().isNoContent()); @@ -82,7 +82,7 @@ void shouldReturnNoContentStatusWhenVerifyTargetBranch() throws Exception { doNothing().when(gitHubVerifyService).verifyCanReadTargetBranch("fake/repo", "main", GITHUB_TOKEN); mockMvc - .perform(post("/source-control/GitHub/repos/branches/main/verify") + .perform(post("/source-control/github/repos/branches/main/verify") .content(new ObjectMapper().writeValueAsString(verifyBranchRequest)) .contentType(MediaType.APPLICATION_JSON)) .andExpect(status().isNoContent()); @@ -161,7 +161,7 @@ void shouldReturnBadRequestGivenSourceTypeIsWrongWhenVerifyToken() throws Except SourceControlDTO sourceControlDTO = SourceControlDTO.builder().token(GITHUB_TOKEN).build(); final var response = mockMvc - .perform(post("/source-control/github/verify") + .perform(post("/source-control/GitHub/verify") .content(new ObjectMapper().writeValueAsString(sourceControlDTO)) .contentType(MediaType.APPLICATION_JSON)) .andExpect(status().isBadRequest()) @@ -181,7 +181,7 @@ void shouldReturnBadRequestGivenSourceTypeIsWrongWhenVerifyBranch() throws Excep .build(); final var response = mockMvc - .perform(post("/source-control/github/repos/branches/main/verify") + .perform(post("/source-control/GitHub/repos/branches/main/verify") .content(new ObjectMapper().writeValueAsString(request)) .contentType(MediaType.APPLICATION_JSON)) .andExpect(status().isBadRequest()) diff --git a/backend/src/test/java/heartbeat/service/jira/JiraServiceTest.java b/backend/src/test/java/heartbeat/service/jira/JiraServiceTest.java index 983c74bb7d..115b2fe9cb 100644 --- a/backend/src/test/java/heartbeat/service/jira/JiraServiceTest.java +++ b/backend/src/test/java/heartbeat/service/jira/JiraServiceTest.java @@ -112,9 +112,9 @@ class JiraServiceTest { public static final String SITE_ATLASSIAN_NET = "https://site.atlassian.net"; - private final BoardType boardTypeJira = BoardType.fromValue("jira"); + private final BoardType boardTypeJira = BoardType.fromStyle("next-gen"); - private final BoardType boardTypeClassicJira = BoardType.fromValue("classic-jira"); + private final BoardType boardTypeClassicJira = BoardType.fromStyle("classic"); private static final String ALL_CARDS_JQL = "status changed during (%s, %s)"; @@ -159,6 +159,7 @@ public ThreadPoolTaskExecutor getTaskExecutor() { } @Test + @Deprecated void shouldCallJiraFeignClientAndReturnBoardConfigResponseWhenGetJiraBoardConfig() throws JsonProcessingException { JiraBoardConfigDTO jiraBoardConfigDTO = JIRA_BOARD_CONFIG_RESPONSE_BUILDER().build(); StatusSelfDTO doneStatusSelf = DONE_STATUS_SELF_RESPONSE_BUILDER().build(); @@ -260,6 +261,7 @@ void shouldCallJiraFeignClientAndReturnBoardVerifyResponseWhenVerifyJiraBoard() } @Test + @Deprecated void shouldCallJiraFeignClientAndReturnBoardConfigResponseWhenGetJiraBoardConfigHasTwoPage() throws IOException { JiraBoardConfigDTO jiraBoardConfigDTO = JIRA_BOARD_CONFIG_RESPONSE_BUILDER().build(); StatusSelfDTO doneStatusSelf = DONE_STATUS_SELF_RESPONSE_BUILDER().build(); @@ -340,6 +342,7 @@ void shouldCallJiraFeignClientAndReturnBoardInfoResponseWhenGetJiraBoardInfoHasT } @Test + @Deprecated void shouldCallJiraFeignClientAndReturnBoardConfigResponseWhenGetClassicJiraBoardConfig() throws JsonProcessingException { JiraBoardConfigDTO jiraBoardConfigDTO = CLASSIC_JIRA_BOARD_CONFIG_RESPONSE_BUILDER().build(); @@ -424,6 +427,7 @@ void shouldCallJiraFeignClientAndReturnBoardInfoResponseWhenGetClassicJiraBoardI } @Test + @Deprecated void shouldCallJiraFeignClientAndThrowParamExceptionWhenGetJiraBoardConfig() { JiraBoardConfigDTO jiraBoardConfigDTO = JIRA_BOARD_CONFIG_RESPONSE_BUILDER().build(); StatusSelfDTO doneStatusSelf = DONE_STATUS_SELF_RESPONSE_BUILDER().build(); @@ -472,10 +476,11 @@ void shouldCallJiraFeignClientAndThrowNonColumnWhenVerifyJiraBoard() { Throwable thrown = catchThrowable(() -> jiraService.verify(boardTypeJira, boardVerifyRequestParam)); assertThat(thrown).isInstanceOf(InternalServerErrorException.class) - .hasMessageContaining("Failed when call Jira to verify board, cause is"); + .hasMessageContaining("Failed to call Jira to verify board, cause is"); } @Test + @Deprecated void shouldCallJiraFeignClientAndThrowNotFoundExceptionWhenGetJiraBoardConfig() throws JsonProcessingException { JiraBoardConfigDTO jiraBoardConfigDTO = JIRA_BOARD_CONFIG_RESPONSE_BUILDER().build(); StatusSelfDTO doneStatusSelf = DONE_STATUS_SELF_RESPONSE_BUILDER().build(); @@ -532,6 +537,7 @@ void shouldCallJiraFeignClientTwiceGivenTwoPageHistoryDataWhenGetJiraBoardConfig } @Test + @Deprecated void shouldCallJiraFeignClientAndThrowNonContentCodeWhenGetJiraBoardConfig() throws JsonProcessingException { JiraBoardConfigDTO jiraBoardConfigDTO = JIRA_BOARD_CONFIG_RESPONSE_BUILDER().build(); StatusSelfDTO doneStatusSelf = DONE_STATUS_SELF_RESPONSE_BUILDER().build(); @@ -582,6 +588,7 @@ void shouldCallJiraFeignClientAndThrowNonContentCodeWhenGetJiraBoardInfo() throw } @Test + @Deprecated void shouldCallJiraFeignClientAndThrowNonColumnWhenGetJiraBoardConfig() { JiraBoardConfigDTO jiraBoardConfigDTO = JIRA_BOARD_CONFIG_RESPONSE_BUILDER().build(); StatusSelfDTO noneStatusSelf = NONE_STATUS_SELF_RESPONSE_BUILDER().build(); @@ -600,7 +607,7 @@ void shouldCallJiraFeignClientAndThrowNonColumnWhenGetJiraBoardConfig() { jiraService.getJiraConfiguration(boardTypeJira, boardRequestParam); }); assertThat(thrown).isInstanceOf(InternalServerErrorException.class) - .hasMessageContaining("Failed when call Jira to get board config, cause is"); + .hasMessageContaining("Failed to call Jira to get board config, cause is"); } @Test @@ -624,7 +631,7 @@ void shouldCallJiraFeignClientAndThrowNonColumnWhenGetJiraBoardInfo() { jiraService.getInfo(boardTypeJira, boardRequestParam); }); assertThat(thrown).isInstanceOf(InternalServerErrorException.class) - .hasMessageContaining("Failed when call Jira to get board config, cause is"); + .hasMessageContaining("Failed to call Jira to get board info, cause is"); } @Test @@ -661,6 +668,7 @@ void shouldGetCardsWhenCallGetStoryPointsAndCycleTimeGiveStoryPointKey() throws } @Test + @Deprecated void shouldThrowExceptionWhenGetJiraConfigurationThrowsUnExpectedException() { URI baseUrl = URI.create(SITE_ATLASSIAN_NET); BoardRequestParam boardRequestParam = BOARD_REQUEST_BUILDER().build(); @@ -692,6 +700,7 @@ void shouldThrowExceptionWhenGetJiraInfoThrowsUnExpectedException() { } @Test + @Deprecated void shouldReturnAssigneeNameFromDoneCardWhenGetAssigneeSet() throws JsonProcessingException { JiraBoardConfigDTO jiraBoardConfigDTO = JIRA_BOARD_CONFIG_RESPONSE_BUILDER().build(); StatusSelfDTO doneStatusSelf = DONE_STATUS_SELF_RESPONSE_BUILDER().build(); @@ -750,6 +759,7 @@ void shouldReturnAssigneeNameFromDoneCardWhenGetBoardInfoAndGetAssigneeSet() thr } @Test + @Deprecated void shouldThrowExceptionWhenGetTargetFieldFailed() { URI baseUrl = URI.create(SITE_ATLASSIAN_NET); String token = "token"; @@ -782,6 +792,7 @@ void shouldThrowExceptionWhenGetBoardInfoAndGetTargetFieldFailed() { } @Test + @Deprecated void shouldThrowExceptionWhenGetTargetFieldReturnNull() { URI baseUrl = URI.create(SITE_ATLASSIAN_NET); String token = "token"; @@ -812,6 +823,7 @@ void shouldThrowExceptionWhenGetBoardInfoAndGetTargetFieldReturnNull() { } @Test + @Deprecated void shouldThrowExceptionWhenGetTargetFieldReturnEmpty() { URI baseUrl = URI.create(SITE_ATLASSIAN_NET); String token = "token"; @@ -850,6 +862,7 @@ void shouldThrowExceptionWhenGetBoardInfoAndGetTargetFieldReturnEmpty() { } @Test + @Deprecated void shouldThrowCustomExceptionWhenGetJiraBoardConfig() { when(urlGenerator.getUri(any())).thenReturn(URI.create(SITE_ATLASSIAN_NET)); @@ -872,6 +885,7 @@ void shouldThrowCustomExceptionWhenGetJiraBoardInfo() { } @Test + @Deprecated void shouldThrowCustomExceptionWhenCallJiraFeignClientToGetBoardConfigFailed() { URI baseUrl = URI.create(SITE_ATLASSIAN_NET); when(urlGenerator.getUri(any())).thenReturn(URI.create(SITE_ATLASSIAN_NET)); @@ -998,6 +1012,16 @@ void shouldReturnBadRequestExceptionWhenBoardTypeIsNotCorrect() { .hasMessageContaining("Board type does not find!"); } + @Test + void shouldReturnBadRequestExceptionWhenBoardStyleIsNotCorrect() { + BoardRequestParam boardRequestParam = BOARD_REQUEST_BUILDER().build(); + when(jiraFeignClient.getProject(any(), any(), any())) + .thenReturn(JiraBoardProject.builder().style("unknown").build()); + assertThatThrownBy(() -> jiraService.getInfo(boardTypeJira, boardRequestParam)) + .isInstanceOf(InternalServerErrorException.class) + .hasMessageContaining("Board type does not find!"); + } + @Test public void shouldProcessCustomFieldsForCardsWhenCallGetStoryPointsAndCycleTime() { URI baseUrl = URI.create(SITE_ATLASSIAN_NET); @@ -1255,6 +1279,7 @@ void shouldGetRealDoneCardGivenCallGetStoryPointsAndCycleTimeWhenUseLastAssignee } @Test + @Deprecated void shouldFilterOutUnreasonableTargetField() throws JsonProcessingException { JiraBoardConfigDTO jiraBoardConfigDTO = CLASSIC_JIRA_BOARD_CONFIG_RESPONSE_BUILDER().build(); StatusSelfDTO doneStatusSelf = DONE_STATUS_SELF_RESPONSE_BUILDER().build(); diff --git a/backend/src/test/java/heartbeat/service/report/GenerateReporterServiceTest.java b/backend/src/test/java/heartbeat/service/report/GenerateReporterServiceTest.java index 571d69a7ed..0a7e24dd12 100644 --- a/backend/src/test/java/heartbeat/service/report/GenerateReporterServiceTest.java +++ b/backend/src/test/java/heartbeat/service/report/GenerateReporterServiceTest.java @@ -103,13 +103,16 @@ import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.ArgumentMatchers.anyString; + +import static heartbeat.TestFixtures.BUILDKITE_TOKEN; import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; - -import static heartbeat.TestFixtures.BUILDKITE_TOKEN; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.doThrow; import static org.mockito.internal.verification.VerificationModeFactory.times; @ExtendWith(MockitoExtension.class) @@ -118,12 +121,19 @@ class GenerateReporterServiceTest { public static final String SITE_ATLASSIAN_NET = "https://site.atlassian.net"; + private static final String REQUEST_FILE_PATH = "src/test/java/heartbeat/controller/report/request.json"; + + private static final String RESPONSE_FILE_PATH = "src/test/java/heartbeat/controller/report/reportResponse.json"; + @InjectMocks GenerateReporterService generateReporterService; @Mock JiraService jiraService; + @Mock + IdUtil idUtil; + @Mock WorkDay workDay; @@ -577,7 +587,7 @@ void shouldGenerateCsvForPipelineWithPipelineMetricAndBuildInfoIsEmpty() throws return null; }).when(csvFileGenerator).convertPipelineDataToCSV(any(), any()); - generateReporterService.generateReporter(request); + generateReporterService.generateDoraReport(request); boolean isExists = Files.exists(csvFilePath); Assertions.assertTrue(isExists); @@ -627,7 +637,7 @@ void shouldGenerateCsvForPipelineWithPipelineMetric() throws IOException { return null; }).when(csvFileGenerator).convertPipelineDataToCSV(any(), any()); - generateReporterService.generateReporter(request); + generateReporterService.generateDoraReport(request); boolean isExists = Files.exists(mockPipelineCsvPath); Assertions.assertTrue(isExists); @@ -673,7 +683,7 @@ void shouldNotGenerateCsvForPipelineWithPipelineLeadTimeIsNull() throws IOExcept when(gitHubService.fetchPipelinesLeadTime(any(), any(), any())).thenReturn(null); when(buildKiteService.getStepsBeforeEndStep(any(), any())).thenReturn(List.of("xx")); - generateReporterService.generateReporter(request); + generateReporterService.generateDoraReport(request); boolean isExists = Files.exists(mockPipelineCsvPath); Assertions.assertFalse(isExists); @@ -718,7 +728,7 @@ void shouldNotGenerateCsvForPipelineWithCommitIsNull() throws IOException { when(gitHubService.fetchPipelinesLeadTime(any(), any(), any())).thenReturn(null); when(buildKiteService.getStepsBeforeEndStep(any(), any())).thenReturn(List.of("xx")); - generateReporterService.generateReporter(request); + generateReporterService.generateDoraReport(request); boolean isExists = Files.exists(mockPipelineCsvPath); Assertions.assertFalse(isExists); @@ -762,7 +772,7 @@ void shouldNotGenerateCsvForPipeline() throws IOException { when(gitHubService.fetchPipelinesLeadTime(any(), any(), any())).thenReturn(null); when(buildKiteService.getStepsBeforeEndStep(any(), any())).thenReturn(List.of("xx")); - generateReporterService.generateReporter(request); + generateReporterService.generateDoraReport(request); boolean isExists = Files.exists(mockPipelineCsvPath); Assertions.assertFalse(isExists); @@ -958,7 +968,7 @@ void shouldReturnReportResponse() { @Test void shouldThrowUnauthorizedExceptionWhenCheckGenerateReportIsDone() { String timeStamp = Long.toString(System.currentTimeMillis()); - String reportId = IdUtil.getDoraReportId(timeStamp); + String reportId = IdUtil.getPipelineReportId(timeStamp); when(asyncExceptionHandler.get(reportId)) .thenReturn(new UnauthorizedException("Failed to get GitHub info_status: 401, reason: PermissionDeny")); @@ -973,7 +983,7 @@ void shouldThrowUnauthorizedExceptionWhenCheckGenerateReportIsDone() { @Test void shouldThrowPermissionDenyExceptionWhenCheckGenerateReportIsDone() { String timeStamp = Long.toString(System.currentTimeMillis()); - String reportId = IdUtil.getDoraReportId(timeStamp); + String reportId = IdUtil.getPipelineReportId(timeStamp); asyncExceptionHandler.put(reportId, new PermissionDenyException("Failed to get GitHub info_status: 403, reason: PermissionDeny")); @@ -989,7 +999,7 @@ void shouldThrowPermissionDenyExceptionWhenCheckGenerateReportIsDone() { @Test void shouldThrowNotFoundExceptionWhenCheckGenerateReportIsDone() { String timeStamp = Long.toString(System.currentTimeMillis()); - String reportId = IdUtil.getDoraReportId(timeStamp); + String reportId = IdUtil.getPipelineReportId(timeStamp); when(asyncExceptionHandler.get(reportId)) .thenReturn(new NotFoundException("Failed to get GitHub info_status: 404, reason: NotFound")); @@ -1003,7 +1013,7 @@ void shouldThrowNotFoundExceptionWhenCheckGenerateReportIsDone() { @Test void shouldThrowGenerateReportExceptionWhenCheckGenerateReportIsDone() { String timeStamp = Long.toString(System.currentTimeMillis()); - String reportId = IdUtil.getDoraReportId(timeStamp); + String reportId = IdUtil.getPipelineReportId(timeStamp); when(asyncExceptionHandler.get(reportId)) .thenReturn(new GenerateReportException("Failed to get GitHub info_status: 500, reason: GenerateReport")); @@ -1017,7 +1027,7 @@ void shouldThrowGenerateReportExceptionWhenCheckGenerateReportIsDone() { @Test void shouldThrowServiceUnavailableExceptionWhenCheckGenerateReportIsDone() { String timeStamp = Long.toString(System.currentTimeMillis()); - String reportId = IdUtil.getDoraReportId(timeStamp); + String reportId = IdUtil.getPipelineReportId(timeStamp); when(asyncExceptionHandler.get(reportId)).thenReturn( new ServiceUnavailableException("Failed to get GitHub info_status: 503, reason: ServiceUnavailable")); @@ -1031,7 +1041,7 @@ void shouldThrowServiceUnavailableExceptionWhenCheckGenerateReportIsDone() { @Test void shouldThrowRequestFailedExceptionWhenCheckGenerateReportIsDone() { String timeStamp = Long.toString(System.currentTimeMillis()); - String reportId = IdUtil.getDoraReportId(timeStamp); + String reportId = IdUtil.getPipelineReportId(timeStamp); when(asyncExceptionHandler.get(reportId)).thenReturn(new RequestFailedException(405, "RequestFailedException")); BaseException exception = assertThrows(RequestFailedException.class, @@ -1197,10 +1207,10 @@ void shouldReturnComposedReportResponseWhenBothBoardResponseAndDoraResponseReady String timeStamp = "1683734399999"; String boardTimeStamp = "board-1683734399999"; - String doraTimestamp = "dora-1683734399999"; + String pipelineTimestamp = "pipeline-1683734399999"; when(generateReporterService.getReportFromHandler(boardTimeStamp)).thenReturn(boardResponse); - when(generateReporterService.getReportFromHandler(doraTimestamp)).thenReturn(pipelineResponse); + when(generateReporterService.getReportFromHandler(pipelineTimestamp)).thenReturn(pipelineResponse); when(asyncReportRequestHandler.getMetricsDataReady(timeStamp)) .thenReturn(new MetricsDataReady(Boolean.TRUE, Boolean.TRUE, null)); @@ -1254,8 +1264,8 @@ void shouldDoConvertMetricDataToCSVWhenCallGenerateCSVForMetrics() throws IOExce @Test void shouldPutReportInHandlerWhenCallSaveReporterInHandler() throws IOException { - String timeStamp = "1683734399999"; - String reportId = IdUtil.getDoraReportId(timeStamp); + String timeStamp = "20240109232359"; + String reportId = IdUtil.getPipelineReportId(timeStamp); ObjectMapper mapper = new ObjectMapper(); ReportResponse reportResponse = mapper .readValue(new File("src/test/java/heartbeat/controller/report/reportResponse.json"), ReportResponse.class); @@ -1266,6 +1276,126 @@ void shouldPutReportInHandlerWhenCallSaveReporterInHandler() throws IOException verify(asyncReportRequestHandler, times(1)).putReport(reportId, reportResponse); } + @Test + void shouldPutBoardReportIntoHandlerWhenCallGenerateBoardReport() throws IOException, InterruptedException { + ObjectMapper mapper = new ObjectMapper(); + GenerateReportRequest reportRequest = mapper.readValue(new File(REQUEST_FILE_PATH), + GenerateReportRequest.class); + ReportResponse reportResponse = mapper.readValue(new File(RESPONSE_FILE_PATH), ReportResponse.class); + reportRequest.setCsvTimeStamp("20240109232359"); + MetricsDataReady previousMetricsReady = MetricsDataReady.builder() + .isBoardMetricsReady(true) + .isPipelineMetricsReady(false) + .isSourceControlMetricsReady(false) + .build(); + GenerateReporterService spyGenerateReporterService = spy(generateReporterService); + + doReturn(reportResponse).when(spyGenerateReporterService).generateReporter(reportRequest); + when(asyncReportRequestHandler.getMetricsDataReady(reportRequest.getCsvTimeStamp())) + .thenReturn(previousMetricsReady); + + spyGenerateReporterService.generateBoardReport(reportRequest); + + Thread.sleep(2000); + verify(spyGenerateReporterService, times(1)).generateReporter(reportRequest); + verify(spyGenerateReporterService, times(1)) + .initializeMetricsDataReadyInHandler(reportRequest.getCsvTimeStamp(), reportRequest.getMetrics()); + verify(spyGenerateReporterService, times(1)).saveReporterInHandler( + spyGenerateReporterService.generateReporter(reportRequest), reportRequest.getCsvTimeStamp()); + verify(spyGenerateReporterService, times(1)).updateMetricsDataReadyInHandler(reportRequest.getCsvTimeStamp(), + reportRequest.getMetrics()); + } + + @Test + void shouldPutExceptionInHandlerWhenCallGenerateBoardReportThrowException() + throws IOException, InterruptedException { + ObjectMapper mapper = new ObjectMapper(); + GenerateReportRequest reportRequest = mapper.readValue(new File(REQUEST_FILE_PATH), + GenerateReportRequest.class); + ReportResponse reportResponse = mapper.readValue(new File(RESPONSE_FILE_PATH), ReportResponse.class); + reportRequest.setCsvTimeStamp("20240109232359"); + String boardTimeStamp = "board-20240109232359"; + GenerateReporterService spyGenerateReporterService = spy(generateReporterService); + GenerateReportException e = new GenerateReportException( + "Failed to update metrics data ready through this timestamp."); + + doReturn(reportResponse).when(spyGenerateReporterService).generateReporter(reportRequest); + doThrow(e).when(spyGenerateReporterService).updateMetricsDataReadyInHandler(any(), any()); + when(asyncReportRequestHandler.getMetricsDataReady(reportRequest.getCsvTimeStamp())).thenReturn(null); + + spyGenerateReporterService.generateBoardReport(reportRequest); + + Thread.sleep(2000L); + verify(spyGenerateReporterService, times(1)) + .initializeMetricsDataReadyInHandler(reportRequest.getCsvTimeStamp(), reportRequest.getMetrics()); + verify(spyGenerateReporterService, times(1)) + .saveReporterInHandler(spyGenerateReporterService.generateReporter(reportRequest), boardTimeStamp); + verify(asyncExceptionHandler, times(1)).put(boardTimeStamp, e); + } + + @Test + void shouldGeneratePipelineReportAndUpdatePipelineMetricsReadyWhenCallGeneratePipelineReport() + throws IOException, InterruptedException { + ObjectMapper mapper = new ObjectMapper(); + GenerateReportRequest reportRequest = mapper.readValue(new File(REQUEST_FILE_PATH), + GenerateReportRequest.class); + reportRequest.setMetrics(List.of("Deployment frequency")); + ReportResponse reportResponse = mapper.readValue(new File(RESPONSE_FILE_PATH), ReportResponse.class); + GenerateReporterService spyGenerateReporterService = spy(generateReporterService); + reportRequest.setCsvTimeStamp("20240109232359"); + String doraTimeStamp = "dora-20240109232359"; + MetricsDataReady previousMetricsReady = MetricsDataReady.builder() + .isBoardMetricsReady(null) + .isPipelineMetricsReady(false) + .isSourceControlMetricsReady(false) + .build(); + + doReturn(reportResponse).when(spyGenerateReporterService).generateReporter(reportRequest); + when(asyncReportRequestHandler.getMetricsDataReady(reportRequest.getCsvTimeStamp())) + .thenReturn(previousMetricsReady); + + spyGenerateReporterService.generateDoraReport(reportRequest); + + Thread.sleep(2000); + verify(spyGenerateReporterService, times(1)).generateReporter(any()); + verify(spyGenerateReporterService, times(1)).initializeMetricsDataReadyInHandler(any(), any()); + verify(spyGenerateReporterService, times(1)) + .saveReporterInHandler(spyGenerateReporterService.generateReporter(any()), doraTimeStamp); + verify(spyGenerateReporterService, times(1)).updateMetricsDataReadyInHandler(any(), any()); + } + + @Test + void shouldGenerateCodebaseReportAndUpdateCodebaseMetricsReadyWhenCallGeneratePipelineReport() + throws IOException, InterruptedException { + ObjectMapper mapper = new ObjectMapper(); + GenerateReportRequest reportRequest = mapper.readValue(new File(REQUEST_FILE_PATH), + GenerateReportRequest.class); + reportRequest.setMetrics(List.of("Lead time for changes")); + ReportResponse reportResponse = mapper.readValue(new File(RESPONSE_FILE_PATH), ReportResponse.class); + GenerateReporterService spyGenerateReporterService = spy(generateReporterService); + reportRequest.setCsvTimeStamp("20240109232359"); + String codebaseTimeStamp = "github-20240109232359"; + MetricsDataReady previousMetricsReady = MetricsDataReady.builder() + .isBoardMetricsReady(null) + .isPipelineMetricsReady(false) + .isSourceControlMetricsReady(false) + .build(); + + doReturn(reportResponse).when(spyGenerateReporterService).generateReporter(reportRequest); + when(asyncReportRequestHandler.getMetricsDataReady(reportRequest.getCsvTimeStamp())) + .thenReturn(previousMetricsReady); + + spyGenerateReporterService.generateDoraReport(reportRequest); + + Thread.sleep(2000); + verify(spyGenerateReporterService, times(1)).generateReporter(any()); + verify(spyGenerateReporterService, times(1)).initializeMetricsDataReadyInHandler(any(), any()); + verify(spyGenerateReporterService, times(1)) + .saveReporterInHandler(spyGenerateReporterService.generateReporter(any()), codebaseTimeStamp); + verify(spyGenerateReporterService, times(1)).updateMetricsDataReadyInHandler(any(), any()); + + } + private JiraBoardSetting buildJiraBoardSetting() { return JiraBoardSetting.builder() .treatFlagCardAsBlock(true) diff --git a/backend/src/test/java/heartbeat/service/source/github/GithubServiceTest.java b/backend/src/test/java/heartbeat/service/source/github/GithubServiceTest.java index 099cda6ef4..da07d3658a 100644 --- a/backend/src/test/java/heartbeat/service/source/github/GithubServiceTest.java +++ b/backend/src/test/java/heartbeat/service/source/github/GithubServiceTest.java @@ -30,6 +30,7 @@ import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; import static heartbeat.TestFixtures.GITHUB_REPOSITORY; +import static heartbeat.TestFixtures.GITHUB_TOKEN; import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNull; @@ -38,7 +39,6 @@ import static org.junit.jupiter.api.Assertions.assertThrows; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyString; -import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.doThrow; import static org.mockito.Mockito.times; @@ -168,21 +168,20 @@ public ThreadPoolTaskExecutor getTaskExecutor() { @Test @Deprecated void shouldReturnNonRedundantGithubReposWhenCallGithubFeignClientApi() { - String githubToken = "123456"; + String githubToken = GITHUB_TOKEN; String token = "token " + githubToken; when(gitHubFeignClient.getAllRepos(token)).thenReturn(List.of(GitHubRepo.builder().htmlUrl("11111").build(), GitHubRepo.builder().htmlUrl("22222").build(), GitHubRepo.builder().htmlUrl("33333").build())); - when(gitHubFeignClient.getGithubOrganizationsInfo(token)) .thenReturn(List.of(GitHubOrganizationsInfo.builder().login("org1").build(), GitHubOrganizationsInfo.builder().login("org2").build())); - when(gitHubFeignClient.getReposByOrganizationName("org1", token)) .thenReturn(List.of(GitHubRepo.builder().htmlUrl("22222").build(), GitHubRepo.builder().htmlUrl("33333").build(), GitHubRepo.builder().htmlUrl("44444").build())); final var response = githubService.verifyToken(githubToken); githubService.shutdownExecutor(); + assertThat(response.getGithubRepos()).hasSize(4); assertThat(response.getGithubRepos()) .isEqualTo(new LinkedHashSet<>(List.of("11111", "22222", "33333", "44444"))); @@ -190,7 +189,7 @@ void shouldReturnNonRedundantGithubReposWhenCallGithubFeignClientApi() { @Test void shouldReturnGithubTokenIsVerifyWhenVerifyToken() { - String githubToken = "123456"; + String githubToken = GITHUB_TOKEN; String token = "token " + githubToken; doNothing().when(gitHubFeignClient).verifyToken(token); @@ -200,19 +199,19 @@ void shouldReturnGithubTokenIsVerifyWhenVerifyToken() { @Test void shouldReturnGithubBranchIsVerifyWhenVerifyBranch() { - String githubToken = "123456"; + String githubToken = GITHUB_TOKEN; String token = "token " + githubToken; - doNothing().when(gitHubFeignClient).verifyCanReadTargetBranch(any(), any(), any()); - assertDoesNotThrow(() -> githubService.verifyCanReadTargetBranch(GITHUB_REPOSITORY, "main", githubToken)); + githubService.verifyCanReadTargetBranch(GITHUB_REPOSITORY, "main", githubToken); + verify(gitHubFeignClient, times(1)).verifyCanReadTargetBranch("fake/repo", "main", token); } @Test @Deprecated void shouldReturnUnauthorizedStatusWhenCallGithubFeignClientApiWithWrongToken() { - String wrongGithubToken = "123456"; + String wrongGithubToken = GITHUB_TOKEN; String token = "token " + wrongGithubToken; when(gitHubFeignClient.getAllRepos(token)) @@ -239,7 +238,7 @@ void shouldThrowExceptionWhenVerifyGitHubThrowUnExpectedException() { @Test @Deprecated void shouldGithubReturnEmptyWhenVerifyGithubThrowGithubRepoEmptyException() { - String githubEmptyToken = "123456"; + String githubEmptyToken = GITHUB_TOKEN; when(gitHubFeignClient.getReposByOrganizationName("org1", githubEmptyToken)).thenReturn(new ArrayList<>()); assertThatThrownBy(() -> githubService.verifyToken(githubEmptyToken)) @@ -249,7 +248,7 @@ void shouldGithubReturnEmptyWhenVerifyGithubThrowGithubRepoEmptyException() { @Test void shouldThrowExceptionWhenGithubReturnUnExpectedException() { - String githubEmptyToken = "123456"; + String githubEmptyToken = GITHUB_TOKEN; doThrow(new UnauthorizedException("Failed to get GitHub info_status: 401 UNAUTHORIZED, reason: ...")) .when(gitHubFeignClient) .verifyToken("token " + githubEmptyToken); @@ -260,7 +259,7 @@ void shouldThrowExceptionWhenGithubReturnUnExpectedException() { @Test void shouldThrowExceptionGivenGithubReturnUnExpectedExceptionWhenVerifyBranch() { - String githubEmptyToken = "123456"; + String githubEmptyToken = GITHUB_TOKEN; doThrow(new UnauthorizedException("Failed to get GitHub info_status: 401 UNAUTHORIZED, reason: ...")) .when(gitHubFeignClient) .verifyCanReadTargetBranch("fake/repo", "main", "token " + githubEmptyToken); @@ -272,32 +271,35 @@ void shouldThrowExceptionGivenGithubReturnUnExpectedExceptionWhenVerifyBranch() @Test void shouldThrowExceptionGivenGithubReturnPermissionDenyExceptionWhenVerifyBranch() { - String githubEmptyToken = "123456"; + String githubEmptyToken = GITHUB_TOKEN; doThrow(new NotFoundException("Failed to get GitHub info_status: 404, reason: ...")).when(gitHubFeignClient) .verifyCanReadTargetBranch("fake/repo", "main", "token " + githubEmptyToken); var exception = assertThrows(PermissionDenyException.class, () -> githubService.verifyCanReadTargetBranch(GITHUB_REPOSITORY, "main", githubEmptyToken)); - assertEquals("Unable to read target branch", exception.getMessage()); + assertEquals("Unable to read target branch: main", exception.getMessage()); } @Test void shouldThrowExceptionGivenGithubReturnCompletionExceptionExceptionWhenVerifyToken() { - String githubEmptyToken = "123456"; + String githubEmptyToken = GITHUB_TOKEN; doThrow(new CompletionException(new Exception("UnExpected Exception"))).when(gitHubFeignClient) .verifyToken("token " + githubEmptyToken); - assertThrows(InternalServerErrorException.class, () -> githubService.verifyTokenV2(githubEmptyToken)); + var exception = assertThrows(InternalServerErrorException.class, + () -> githubService.verifyTokenV2(githubEmptyToken)); + assertEquals("Failed to call GitHub with token_error: UnExpected Exception", exception.getMessage()); } @Test void shouldThrowExceptionGivenGithubReturnUnauthorizedExceptionWhenVerifyBranch() { - String githubEmptyToken = "123456"; + String githubEmptyToken = GITHUB_TOKEN; doThrow(new CompletionException(new Exception("UnExpected Exception"))).when(gitHubFeignClient) .verifyCanReadTargetBranch("fake/repo", "main", "token " + githubEmptyToken); - assertThrows(InternalServerErrorException.class, + var exception = assertThrows(InternalServerErrorException.class, () -> githubService.verifyCanReadTargetBranch(GITHUB_REPOSITORY, "main", githubEmptyToken)); + assertEquals("Failed to call GitHub branch: main with error: UnExpected Exception", exception.getMessage()); } @Test @@ -305,6 +307,7 @@ void shouldReturnNullWhenMergeTimeIsNull() { PullRequestInfo pullRequestInfo = PullRequestInfo.builder().build(); DeployInfo deployInfo = DeployInfo.builder().build(); CommitInfo commitInfo = CommitInfo.builder().build(); + LeadTime result = githubService.mapLeadTimeWithInfo(pullRequestInfo, deployInfo, commitInfo); assertNull(result); @@ -312,7 +315,6 @@ void shouldReturnNullWhenMergeTimeIsNull() { @Test void shouldReturnLeadTimeWhenMergedTimeIsNotNull() { - LeadTime result = githubService.mapLeadTimeWithInfo(pullRequestInfo, deployInfo, commitInfo); LeadTime expect = LeadTime.builder() .commitId("111") .prCreatedTime(1658548980000L) @@ -326,13 +328,14 @@ void shouldReturnLeadTimeWhenMergedTimeIsNotNull() { .totalTime(180000) .build(); + LeadTime result = githubService.mapLeadTimeWithInfo(pullRequestInfo, deployInfo, commitInfo); + assertEquals(expect, result); } @Test void CommitTimeInPrShouldBeZeroWhenCommitInfoIsNull() { commitInfo = CommitInfo.builder().build(); - LeadTime result = githubService.mapLeadTimeWithInfo(pullRequestInfo, deployInfo, commitInfo); LeadTime expect = LeadTime.builder() .commitId("111") .prCreatedTime(1658548980000L) @@ -346,13 +349,14 @@ void CommitTimeInPrShouldBeZeroWhenCommitInfoIsNull() { .totalTime(180000) .build(); + LeadTime result = githubService.mapLeadTimeWithInfo(pullRequestInfo, deployInfo, commitInfo); + assertEquals(expect, result); } @Test void shouldReturnFirstCommitTimeInPrZeroWhenCommitInfoIsNull() { commitInfo = CommitInfo.builder().build(); - LeadTime result = githubService.mapLeadTimeWithInfo(pullRequestInfo, deployInfo, commitInfo); LeadTime expect = LeadTime.builder() .commitId("111") .prCreatedTime(1658548980000L) @@ -366,17 +370,18 @@ void shouldReturnFirstCommitTimeInPrZeroWhenCommitInfoIsNull() { .totalTime(180000L) .build(); + LeadTime result = githubService.mapLeadTimeWithInfo(pullRequestInfo, deployInfo, commitInfo); + assertEquals(expect, result); } @Test void shouldReturnPipeLineLeadTimeWhenDeployITimesIsNotEmpty() { String mockToken = "mockToken"; - when(gitHubFeignClient.getPullRequestListInfo(any(), any(), any())).thenReturn(List.of(pullRequestInfo)); - when(gitHubFeignClient.getPullRequestCommitInfo(any(), any(), any())).thenReturn(List.of(commitInfo)); when(gitHubFeignClient.getCommitInfo(any(), any(), any())).thenReturn(commitInfo); + List result = githubService.fetchPipelinesLeadTime(deployTimes, repositoryMap, mockToken); assertEquals(pipelineLeadTimes, result); @@ -385,14 +390,13 @@ void shouldReturnPipeLineLeadTimeWhenDeployITimesIsNotEmpty() { @Test void shouldReturnEmptyLeadTimeWhenDeployTimesIsEmpty() { String mockToken = "mockToken"; - + List expect = List.of(PipelineLeadTime.builder().build()); when(gitHubFeignClient.getPullRequestListInfo(any(), any(), any())).thenReturn(List.of(pullRequestInfo)); - when(gitHubFeignClient.getPullRequestCommitInfo(any(), any(), any())).thenReturn(List.of(commitInfo)); List emptyDeployTimes = List.of(DeployTimes.builder().build()); + List result = githubService.fetchPipelinesLeadTime(emptyDeployTimes, repositoryMap, mockToken); - List expect = List.of(PipelineLeadTime.builder().build()); assertEquals(expect, result); } @@ -400,14 +404,6 @@ void shouldReturnEmptyLeadTimeWhenDeployTimesIsEmpty() { @Test void shouldReturnEmptyMergeLeadTimeWhenPullRequestInfoIsEmpty() { String mockToken = "mockToken"; - - when(gitHubFeignClient.getPullRequestListInfo(any(), any(), any())).thenReturn(List.of()); - - when(gitHubFeignClient.getPullRequestCommitInfo(any(), any(), any())).thenReturn(List.of()); - when(gitHubFeignClient.getCommitInfo(any(), any(), any())).thenReturn(new CommitInfo()); - - List result = githubService.fetchPipelinesLeadTime(deployTimes, repositoryMap, mockToken); - List expect = List.of(PipelineLeadTime.builder() .pipelineStep("Step") .pipelineName("Name") @@ -420,6 +416,11 @@ void shouldReturnEmptyMergeLeadTimeWhenPullRequestInfoIsEmpty() { .totalTime(120000) .build())) .build()); + when(gitHubFeignClient.getPullRequestListInfo(any(), any(), any())).thenReturn(List.of()); + when(gitHubFeignClient.getPullRequestCommitInfo(any(), any(), any())).thenReturn(List.of()); + when(gitHubFeignClient.getCommitInfo(any(), any(), any())).thenReturn(new CommitInfo()); + + List result = githubService.fetchPipelinesLeadTime(deployTimes, repositoryMap, mockToken); assertEquals(expect, result); } @@ -427,14 +428,6 @@ void shouldReturnEmptyMergeLeadTimeWhenPullRequestInfoIsEmpty() { @Test void shouldReturnEmptyMergeLeadTimeWhenPullRequestInfoGot404Error() { String mockToken = "mockToken"; - - when(gitHubFeignClient.getPullRequestListInfo(any(), any(), any())).thenThrow(new NotFoundException("")); - - when(gitHubFeignClient.getPullRequestCommitInfo(any(), any(), any())).thenReturn(List.of()); - when(gitHubFeignClient.getCommitInfo(any(), any(), any())).thenReturn(new CommitInfo()); - - List result = githubService.fetchPipelinesLeadTime(deployTimes, repositoryMap, mockToken); - List expect = List.of(PipelineLeadTime.builder() .pipelineStep("Step") .pipelineName("Name") @@ -447,6 +440,11 @@ void shouldReturnEmptyMergeLeadTimeWhenPullRequestInfoGot404Error() { .totalTime(120000) .build())) .build()); + when(gitHubFeignClient.getPullRequestListInfo(any(), any(), any())).thenThrow(new NotFoundException("")); + when(gitHubFeignClient.getPullRequestCommitInfo(any(), any(), any())).thenReturn(List.of()); + when(gitHubFeignClient.getCommitInfo(any(), any(), any())).thenReturn(new CommitInfo()); + + List result = githubService.fetchPipelinesLeadTime(deployTimes, repositoryMap, mockToken); assertEquals(expect, result); } @@ -455,12 +453,6 @@ void shouldReturnEmptyMergeLeadTimeWhenPullRequestInfoGot404Error() { void shouldReturnEmptyMergeLeadTimeWhenMergeTimeIsEmpty() { String mockToken = "mockToken"; pullRequestInfo.setMergedAt(null); - when(gitHubFeignClient.getPullRequestListInfo(any(), any(), any())).thenReturn(List.of(pullRequestInfo)); - - when(gitHubFeignClient.getPullRequestCommitInfo(any(), any(), any())).thenReturn(List.of()); - when(gitHubFeignClient.getCommitInfo(any(), any(), any())).thenReturn(new CommitInfo()); - List result = githubService.fetchPipelinesLeadTime(deployTimes, repositoryMap, mockToken); - List expect = List.of(PipelineLeadTime.builder() .pipelineStep("Step") .pipelineName("Name") @@ -473,6 +465,11 @@ void shouldReturnEmptyMergeLeadTimeWhenMergeTimeIsEmpty() { .totalTime(120000) .build())) .build()); + when(gitHubFeignClient.getPullRequestListInfo(any(), any(), any())).thenReturn(List.of(pullRequestInfo)); + when(gitHubFeignClient.getPullRequestCommitInfo(any(), any(), any())).thenReturn(List.of()); + when(gitHubFeignClient.getCommitInfo(any(), any(), any())).thenReturn(new CommitInfo()); + + List result = githubService.fetchPipelinesLeadTime(deployTimes, repositoryMap, mockToken); assertEquals(expect, result); } @@ -504,7 +501,7 @@ void shouldThrowCompletableExceptionIfGetPullRequestListInfoHasExceptionWhenFetc } @Test - public void shouldFetchCommitInfo() { + void shouldFetchCommitInfo() { CommitInfo commitInfo = CommitInfo.builder() .commit(Commit.builder() .author(Author.builder().name("XXXX").email("XXX@test.com").date("2023-05-10T06:43:02.653Z").build()) @@ -520,7 +517,7 @@ public void shouldFetchCommitInfo() { } @Test - public void shouldThrowPermissionDenyExceptionWhenFetchCommitInfo403Forbidden() { + void shouldThrowPermissionDenyExceptionWhenFetchCommitInfo403Forbidden() { when(gitHubFeignClient.getCommitInfo(anyString(), anyString(), anyString())) .thenThrow(new PermissionDenyException("request forbidden")); @@ -530,7 +527,7 @@ public void shouldThrowPermissionDenyExceptionWhenFetchCommitInfo403Forbidden() } @Test - public void shouldThrowInternalServerErrorExceptionWhenFetchCommitInfo500Exception() { + void shouldThrowInternalServerErrorExceptionWhenFetchCommitInfo500Exception() { when(gitHubFeignClient.getCommitInfo(anyString(), anyString(), anyString())).thenReturn(null); assertThatThrownBy(() -> githubService.fetchCommitInfo("12344", "", "")) @@ -539,7 +536,7 @@ public void shouldThrowInternalServerErrorExceptionWhenFetchCommitInfo500Excepti } @Test - public void shouldReturnNullWhenFetchCommitInfo404Exception() { + void shouldReturnNullWhenFetchCommitInfo404Exception() { when(gitHubFeignClient.getCommitInfo(anyString(), anyString(), anyString())).thenThrow(new NotFoundException("")); assertNull(githubService.fetchCommitInfo("12344", "", "")); @@ -548,12 +545,11 @@ public void shouldReturnNullWhenFetchCommitInfo404Exception() { @Test void shouldReturnPipeLineLeadTimeWhenDeployITimesIsNotEmptyAndCommitInfoError() { String mockToken = "mockToken"; - when(gitHubFeignClient.getPullRequestListInfo(any(), any(), any())).thenReturn(List.of(pullRequestInfo)); - when(gitHubFeignClient.getPullRequestCommitInfo(any(), any(), any())).thenReturn(List.of(commitInfo)); when(gitHubFeignClient.getCommitInfo(any(), any(), any())) .thenThrow(new NotFoundException("Failed to get commit")); + List result = githubService.fetchPipelinesLeadTime(deployTimes, repositoryMap, mockToken); assertEquals(pipelineLeadTimes, result); @@ -562,14 +558,12 @@ void shouldReturnPipeLineLeadTimeWhenDeployITimesIsNotEmptyAndCommitInfoError() @Test void shouldReturnPipeLineLeadTimeWhenDeployCommitShaIsDifferent() { String mockToken = "mockToken"; - pullRequestInfo = PullRequestInfo.builder() .mergedAt("2022-07-23T04:04:00.000+00:00") .createdAt("2022-07-23T04:03:00.000+00:00") .mergeCommitSha("222") .number(1) .build(); - pipelineLeadTimes = List.of(PipelineLeadTime.builder() .pipelineName("Name") .pipelineStep("Step") @@ -583,12 +577,11 @@ void shouldReturnPipeLineLeadTimeWhenDeployCommitShaIsDifferent() { .totalTime(120000) .build())) .build()); - when(gitHubFeignClient.getPullRequestListInfo(any(), any(), any())).thenReturn(List.of(pullRequestInfo)); - when(gitHubFeignClient.getPullRequestCommitInfo(any(), any(), any())).thenReturn(List.of(commitInfo)); when(gitHubFeignClient.getCommitInfo(any(), any(), any())) .thenThrow(new NotFoundException("Failed to get commit")); + List result = githubService.fetchPipelinesLeadTime(deployTimes, repositoryMap, mockToken); assertEquals(pipelineLeadTimes, result); diff --git a/docs/src/content/docs/en/designs/refinement-on-generate-report.mdx b/docs/src/content/docs/en/designs/refinement-on-generate-report.mdx index 90b90d5747..88f027ed60 100644 --- a/docs/src/content/docs/en/designs/refinement-on-generate-report.mdx +++ b/docs/src/content/docs/en/designs/refinement-on-generate-report.mdx @@ -269,7 +269,7 @@ end + Async generate board report ``` -POST /reports/board +POST /reports/{board} Request payload: { @@ -319,7 +319,7 @@ Response: ``` + Async generate dora report ``` -POST /reports/dora +POST /reports/{dora} Request payload: { diff --git a/frontend/.eslintrc.json b/frontend/.eslintrc.json index 8193acc2ee..6a71b2c0fc 100644 --- a/frontend/.eslintrc.json +++ b/frontend/.eslintrc.json @@ -22,9 +22,10 @@ }, "plugins": ["react", "react-hooks", "@typescript-eslint", "prettier"], "rules": { - "semi": 0, + "semi": 2, "react/react-in-jsx-scope": "off", "import/prefer-default-export": "off", + "react/display-name": "off", "react/jsx-filename-extension": [1, { "extensions": [".jsx", ".tsx"] }] }, "settings": { diff --git a/frontend/.prettierrc b/frontend/.prettierrc index 7489e24578..eb862bdd00 100644 --- a/frontend/.prettierrc +++ b/frontend/.prettierrc @@ -4,6 +4,5 @@ "jsxSingleQuote": true, "useTabs": false, "tabWidth": 2, - "semi": false, "bracketSpacing": true } diff --git a/frontend/__tests__/__mocks__/svgTransformer.ts b/frontend/__tests__/__mocks__/svgTransformer.ts index d67ee4cdfb..14f39e293c 100644 --- a/frontend/__tests__/__mocks__/svgTransformer.ts +++ b/frontend/__tests__/__mocks__/svgTransformer.ts @@ -1,9 +1,9 @@ module.exports = { process() { - return { code: 'module.exports = {};' } + return { code: 'module.exports = {};' }; }, getCacheKey() { // The output is always the same. - return 'svgTransform' + return 'svgTransform'; }, -} +}; diff --git a/frontend/__tests__/setupTests.ts b/frontend/__tests__/setupTests.ts index 118a726320..0ca2ff4014 100644 --- a/frontend/__tests__/setupTests.ts +++ b/frontend/__tests__/setupTests.ts @@ -1,12 +1,12 @@ -import '@testing-library/jest-dom' -import { configure } from '@testing-library/react' +import '@testing-library/jest-dom'; +import { configure } from '@testing-library/react'; -export const navigateMock = jest.fn() +export const navigateMock = jest.fn(); // This configuration will affect the waitFor default timeout which is 1000ms. -configure({ asyncUtilTimeout: 2000 }) +configure({ asyncUtilTimeout: 2000 }); jest.mock('react-router-dom', () => ({ ...(jest.requireActual('react-router-dom') as Record), useNavigate: () => navigateMock, -})) +})); diff --git a/frontend/__tests__/src/App.test.tsx b/frontend/__tests__/src/App.test.tsx index 173afa7cbd..6078568266 100644 --- a/frontend/__tests__/src/App.test.tsx +++ b/frontend/__tests__/src/App.test.tsx @@ -1,20 +1,20 @@ -import { render, RenderResult, waitFor } from '@testing-library/react' -import App from '@src/App' -import { Provider } from 'react-redux' -import { store } from '@src/store' -jest.useFakeTimers() +import { render, RenderResult, waitFor } from '@testing-library/react'; +import App from '@src/App'; +import { Provider } from 'react-redux'; +import { store } from '@src/store'; +jest.useFakeTimers(); describe('render app', () => { const setup = (): RenderResult => render( - ) + ); it('should show hello World when render app', async () => { - const { container } = setup() + const { container } = setup(); await waitFor(() => { - expect(container.getElementsByTagName('span')[0].getAttribute('role')).toEqual('progressbar') - }) - }) -}) + expect(container.getElementsByTagName('span')[0].getAttribute('role')).toEqual('progressbar'); + }); + }); +}); diff --git a/frontend/__tests__/src/client/BoardClient.test.ts b/frontend/__tests__/src/client/BoardClient.test.ts index 1af259c9f3..abd803a74e 100644 --- a/frontend/__tests__/src/client/BoardClient.test.ts +++ b/frontend/__tests__/src/client/BoardClient.test.ts @@ -1,69 +1,69 @@ -import { setupServer } from 'msw/node' -import { rest } from 'msw' +import { setupServer } from 'msw/node'; +import { rest } from 'msw'; import { MOCK_BOARD_URL_FOR_CLASSIC_JIRA, MOCK_BOARD_URL_FOR_JIRA, MOCK_BOARD_VERIFY_REQUEST_PARAMS, MOCK_CLASSIC_JIRA_BOARD_VERIFY_REQUEST_PARAMS, VERIFY_ERROR_MESSAGE, -} from '../fixtures' -import { boardClient } from '@src/clients/board/BoardClient' -import { HttpStatusCode } from 'axios' +} from '../fixtures'; +import { boardClient } from '@src/clients/board/BoardClient'; +import { HttpStatusCode } from 'axios'; const server = setupServer( rest.post(MOCK_BOARD_URL_FOR_JIRA, (req, res, ctx) => res(ctx.status(HttpStatusCode.Ok))), rest.post(MOCK_BOARD_URL_FOR_CLASSIC_JIRA, (req, res, ctx) => res(ctx.status(HttpStatusCode.Ok))) -) +); describe('verify board request', () => { - beforeAll(() => server.listen()) - afterAll(() => server.close()) + beforeAll(() => server.listen()); + afterAll(() => server.close()); it('should isBoardVerify is true when board verify response status 200', async () => { - const result = await boardClient.getVerifyBoard(MOCK_BOARD_VERIFY_REQUEST_PARAMS) + const result = await boardClient.getVerifyBoard(MOCK_BOARD_VERIFY_REQUEST_PARAMS); - expect(result.isBoardVerify).toEqual(true) - }) + expect(result.isBoardVerify).toEqual(true); + }); it('should isBoardVerify is true when select classic jira and board verify response status 200', async () => { - const result = await boardClient.getVerifyBoard(MOCK_CLASSIC_JIRA_BOARD_VERIFY_REQUEST_PARAMS) + const result = await boardClient.getVerifyBoard(MOCK_CLASSIC_JIRA_BOARD_VERIFY_REQUEST_PARAMS); - expect(result.isBoardVerify).toEqual(true) - }) + expect(result.isBoardVerify).toEqual(true); + }); it('should isNoDoneCard is true when board verify response status 204', async () => { - server.use(rest.post(MOCK_BOARD_URL_FOR_JIRA, (req, res, ctx) => res(ctx.status(HttpStatusCode.NoContent)))) + server.use(rest.post(MOCK_BOARD_URL_FOR_JIRA, (req, res, ctx) => res(ctx.status(HttpStatusCode.NoContent)))); - const result = await boardClient.getVerifyBoard(MOCK_BOARD_VERIFY_REQUEST_PARAMS) + const result = await boardClient.getVerifyBoard(MOCK_BOARD_VERIFY_REQUEST_PARAMS); - expect(result.haveDoneCard).toEqual(false) - expect(result.isBoardVerify).toEqual(false) - }) + expect(result.haveDoneCard).toEqual(false); + expect(result.isBoardVerify).toEqual(false); + }); it('should throw error when board verify response status 400', async () => { server.use( rest.post(MOCK_BOARD_URL_FOR_JIRA, (req, res, ctx) => res(ctx.status(HttpStatusCode.BadRequest), ctx.json({ hintInfo: VERIFY_ERROR_MESSAGE.BAD_REQUEST })) ) - ) + ); boardClient.getVerifyBoard(MOCK_BOARD_VERIFY_REQUEST_PARAMS).catch((e) => { - expect(e).toBeInstanceOf(Error) - expect((e as Error).message).toMatch(VERIFY_ERROR_MESSAGE.BAD_REQUEST) - }) - }) + expect(e).toBeInstanceOf(Error); + expect((e as Error).message).toMatch(VERIFY_ERROR_MESSAGE.BAD_REQUEST); + }); + }); it('should throw error when board verify response status 401', async () => { server.use( rest.post(MOCK_BOARD_URL_FOR_JIRA, (req, res, ctx) => res(ctx.status(HttpStatusCode.Unauthorized), ctx.json({ hintInfo: VERIFY_ERROR_MESSAGE.UNAUTHORIZED })) ) - ) + ); await expect(async () => { - await boardClient.getVerifyBoard(MOCK_BOARD_VERIFY_REQUEST_PARAMS) - }).rejects.toThrow(VERIFY_ERROR_MESSAGE.UNAUTHORIZED) - }) + await boardClient.getVerifyBoard(MOCK_BOARD_VERIFY_REQUEST_PARAMS); + }).rejects.toThrow(VERIFY_ERROR_MESSAGE.UNAUTHORIZED); + }); it('should throw error when board verify response status 500', async () => { server.use( @@ -75,38 +75,38 @@ describe('verify board request', () => { }) ) ) - ) + ); await expect(async () => { - await boardClient.getVerifyBoard(MOCK_BOARD_VERIFY_REQUEST_PARAMS) - }).rejects.toThrow(VERIFY_ERROR_MESSAGE.INTERNAL_SERVER_ERROR) - }) + await boardClient.getVerifyBoard(MOCK_BOARD_VERIFY_REQUEST_PARAMS); + }).rejects.toThrow(VERIFY_ERROR_MESSAGE.INTERNAL_SERVER_ERROR); + }); it('should throw error when board verify response status 503', async () => { server.use( rest.post(MOCK_BOARD_URL_FOR_JIRA, (req, res, ctx) => res(ctx.status(HttpStatusCode.ServiceUnavailable), ctx.json({ hintInfo: VERIFY_ERROR_MESSAGE.REQUEST_TIMEOUT })) ) - ) + ); await expect(async () => { - await boardClient.getVerifyBoard(MOCK_BOARD_VERIFY_REQUEST_PARAMS) - }).rejects.toThrow(VERIFY_ERROR_MESSAGE.REQUEST_TIMEOUT) - }) + await boardClient.getVerifyBoard(MOCK_BOARD_VERIFY_REQUEST_PARAMS); + }).rejects.toThrow(VERIFY_ERROR_MESSAGE.REQUEST_TIMEOUT); + }); it('should throw error when board verify response status 300', async () => { - server.use(rest.post(MOCK_BOARD_URL_FOR_JIRA, (req, res, ctx) => res(ctx.status(HttpStatusCode.MultipleChoices)))) + server.use(rest.post(MOCK_BOARD_URL_FOR_JIRA, (req, res, ctx) => res(ctx.status(HttpStatusCode.MultipleChoices)))); await expect(async () => { - await boardClient.getVerifyBoard(MOCK_BOARD_VERIFY_REQUEST_PARAMS) - }).rejects.toThrow(VERIFY_ERROR_MESSAGE.UNKNOWN) - }) + await boardClient.getVerifyBoard(MOCK_BOARD_VERIFY_REQUEST_PARAMS); + }).rejects.toThrow(VERIFY_ERROR_MESSAGE.UNKNOWN); + }); it('should throw unknown error when board verify response empty', async () => { - server.use(rest.post(MOCK_BOARD_URL_FOR_JIRA, (req, res) => res.networkError('Network Error'))) + server.use(rest.post(MOCK_BOARD_URL_FOR_JIRA, (req, res) => res.networkError('Network Error'))); await expect(async () => { - await boardClient.getVerifyBoard(MOCK_BOARD_VERIFY_REQUEST_PARAMS) - }).rejects.toThrow(VERIFY_ERROR_MESSAGE.UNKNOWN) - }) -}) + await boardClient.getVerifyBoard(MOCK_BOARD_VERIFY_REQUEST_PARAMS); + }).rejects.toThrow(VERIFY_ERROR_MESSAGE.UNKNOWN); + }); +}); diff --git a/frontend/__tests__/src/client/CSVClient.test.ts b/frontend/__tests__/src/client/CSVClient.test.ts index bdb43c8286..4e03c24f01 100644 --- a/frontend/__tests__/src/client/CSVClient.test.ts +++ b/frontend/__tests__/src/client/CSVClient.test.ts @@ -1,39 +1,39 @@ -import { setupServer } from 'msw/node' -import { rest } from 'msw' -import { HttpStatusCode } from 'axios' -import { MOCK_EXPORT_CSV_REQUEST_PARAMS, MOCK_EXPORT_CSV_URL, VERIFY_ERROR_MESSAGE } from '../fixtures' -import { csvClient } from '@src/clients/report/CSVClient' +import { setupServer } from 'msw/node'; +import { rest } from 'msw'; +import { HttpStatusCode } from 'axios'; +import { MOCK_EXPORT_CSV_REQUEST_PARAMS, MOCK_EXPORT_CSV_URL, VERIFY_ERROR_MESSAGE } from '../fixtures'; +import { csvClient } from '@src/clients/report/CSVClient'; -const server = setupServer(rest.get(MOCK_EXPORT_CSV_URL, (req, res, ctx) => res(ctx.status(HttpStatusCode.Ok)))) +const server = setupServer(rest.get(MOCK_EXPORT_CSV_URL, (req, res, ctx) => res(ctx.status(HttpStatusCode.Ok)))); describe('verify export csv', () => { - beforeAll(() => server.listen()) - afterAll(() => server.close()) + beforeAll(() => server.listen()); + afterAll(() => server.close()); it('should download the pipeline CSV file when export csv request status 200', async () => { - const mockBlob = new Blob(['CSV data'], { type: 'text/csv' }) - const mockResponse = { data: mockBlob } - const mockGet = jest.fn().mockResolvedValue(mockResponse) + const mockBlob = new Blob(['CSV data'], { type: 'text/csv' }); + const mockResponse = { data: mockBlob }; + const mockGet = jest.fn().mockResolvedValue(mockResponse); const mockCreateObjectURL = jest.fn().mockImplementation((blob) => { - return `mock-url:${blob}` - }) - const appendChildSpy = jest.spyOn(document.body, 'appendChild') - const removeChildSpy = jest.spyOn(document.body, 'removeChild') - window.URL.createObjectURL = mockCreateObjectURL + return `mock-url:${blob}`; + }); + const appendChildSpy = jest.spyOn(document.body, 'appendChild'); + const removeChildSpy = jest.spyOn(document.body, 'removeChild'); + window.URL.createObjectURL = mockCreateObjectURL; - const mockAxiosInstance = { get: mockGet } - await csvClient.exportCSVData.call({ axiosInstance: mockAxiosInstance }, MOCK_EXPORT_CSV_REQUEST_PARAMS) + const mockAxiosInstance = { get: mockGet }; + await csvClient.exportCSVData.call({ axiosInstance: mockAxiosInstance }, MOCK_EXPORT_CSV_REQUEST_PARAMS); - expect(mockCreateObjectURL).toHaveBeenCalledWith(mockBlob) - expect(appendChildSpy).toHaveBeenCalled() - expect(removeChildSpy).toHaveBeenCalled() - }) + expect(mockCreateObjectURL).toHaveBeenCalledWith(mockBlob); + expect(appendChildSpy).toHaveBeenCalled(); + expect(removeChildSpy).toHaveBeenCalled(); + }); it('should throw error when export csv request status 500', async () => { - server.use(rest.get(MOCK_EXPORT_CSV_URL, (req, res, ctx) => res(ctx.status(HttpStatusCode.InternalServerError)))) + server.use(rest.get(MOCK_EXPORT_CSV_URL, (req, res, ctx) => res(ctx.status(HttpStatusCode.InternalServerError)))); await expect(async () => { - await csvClient.exportCSVData(MOCK_EXPORT_CSV_REQUEST_PARAMS) - }).rejects.toThrow(VERIFY_ERROR_MESSAGE.INTERNAL_SERVER_ERROR) - }) -}) + await csvClient.exportCSVData(MOCK_EXPORT_CSV_REQUEST_PARAMS); + }).rejects.toThrow(VERIFY_ERROR_MESSAGE.INTERNAL_SERVER_ERROR); + }); +}); diff --git a/frontend/__tests__/src/client/HeaderClient.test.ts b/frontend/__tests__/src/client/HeaderClient.test.ts index 6bb6924d4b..b4e8bd5e7a 100644 --- a/frontend/__tests__/src/client/HeaderClient.test.ts +++ b/frontend/__tests__/src/client/HeaderClient.test.ts @@ -1,26 +1,26 @@ -import { setupServer } from 'msw/node' -import { rest } from 'msw' -import { MOCK_VERSION_URL, VERIFY_ERROR_MESSAGE, VERSION_RESPONSE } from '../fixtures' -import { HttpStatusCode } from 'axios' -import { headerClient } from '@src/clients/header/HeaderClient' +import { setupServer } from 'msw/node'; +import { rest } from 'msw'; +import { MOCK_VERSION_URL, VERIFY_ERROR_MESSAGE, VERSION_RESPONSE } from '../fixtures'; +import { HttpStatusCode } from 'axios'; +import { headerClient } from '@src/clients/header/HeaderClient'; -const server = setupServer(rest.get(MOCK_VERSION_URL, (req, res, ctx) => res(ctx.status(HttpStatusCode.Ok)))) +const server = setupServer(rest.get(MOCK_VERSION_URL, (req, res, ctx) => res(ctx.status(HttpStatusCode.Ok)))); describe('header client', () => { - beforeAll(() => server.listen()) - afterEach(() => server.resetHandlers()) - afterAll(() => server.close()) + beforeAll(() => server.listen()); + afterEach(() => server.resetHandlers()); + afterAll(() => server.close()); it('should get response when get header status 200', async () => { - const excepted = '1.11' + const excepted = '1.11'; server.use( rest.get(MOCK_VERSION_URL, (req, res, ctx) => res(ctx.status(HttpStatusCode.Accepted), ctx.json(VERSION_RESPONSE)) ) - ) + ); - await expect(headerClient.getVersion()).resolves.toEqual(excepted) - }) + await expect(headerClient.getVersion()).resolves.toEqual(excepted); + }); it('should throw error when get version response status 500', () => { server.use( @@ -32,10 +32,10 @@ describe('header client', () => { }) ) ) - ) + ); expect(async () => { - await headerClient.getVersion() - }).rejects.toThrow(VERIFY_ERROR_MESSAGE.INTERNAL_SERVER_ERROR) - }) -}) + await headerClient.getVersion(); + }).rejects.toThrow(VERIFY_ERROR_MESSAGE.INTERNAL_SERVER_ERROR); + }); +}); diff --git a/frontend/__tests__/src/client/MetricsClient.test.ts b/frontend/__tests__/src/client/MetricsClient.test.ts index 513ecaf3e4..cf07ed3331 100644 --- a/frontend/__tests__/src/client/MetricsClient.test.ts +++ b/frontend/__tests__/src/client/MetricsClient.test.ts @@ -1,27 +1,27 @@ -import { setupServer } from 'msw/node' -import { rest } from 'msw' -import { metricsClient } from '@src/clients/MetricsClient' -import { BASE_URL, MOCK_GET_STEPS_PARAMS, VERIFY_ERROR_MESSAGE } from '../fixtures' -import { HttpStatusCode } from 'axios' +import { setupServer } from 'msw/node'; +import { rest } from 'msw'; +import { metricsClient } from '@src/clients/MetricsClient'; +import { BASE_URL, MOCK_GET_STEPS_PARAMS, VERIFY_ERROR_MESSAGE } from '../fixtures'; +import { HttpStatusCode } from 'axios'; describe('get steps from metrics response', () => { - const { params, buildId, organizationId, pipelineType, token } = MOCK_GET_STEPS_PARAMS - const getStepsUrl = `${BASE_URL}/pipelines/:type/:orgId/pipelines/:buildId/steps` - const server = setupServer() - beforeAll(() => server.listen()) - afterAll(() => server.close()) + const { params, buildId, organizationId, pipelineType, token } = MOCK_GET_STEPS_PARAMS; + const getStepsUrl = `${BASE_URL}/pipelines/:type/:orgId/pipelines/:buildId/steps`; + const server = setupServer(); + beforeAll(() => server.listen()); + afterAll(() => server.close()); it('should return steps when getSteps response status 200', async () => { server.use( rest.get(getStepsUrl, (req, res, ctx) => { - return res(ctx.status(HttpStatusCode.Ok), ctx.json({ steps: ['step1'] })) + return res(ctx.status(HttpStatusCode.Ok), ctx.json({ steps: ['step1'] })); }) - ) + ); - const result = await metricsClient.getSteps(params, buildId, organizationId, pipelineType, token) + const result = await metricsClient.getSteps(params, buildId, organizationId, pipelineType, token); - expect(result).toEqual({ response: ['step1'], haveStep: true }) - }) + expect(result).toEqual({ response: ['step1'], haveStep: true }); + }); it('should throw error when getSteps response status 500', async () => { server.use( @@ -33,30 +33,30 @@ describe('get steps from metrics response', () => { }) ) ) - ) + ); await expect(async () => { - await metricsClient.getSteps(params, buildId, organizationId, pipelineType, token) - }).rejects.toThrow(VERIFY_ERROR_MESSAGE.INTERNAL_SERVER_ERROR) - }) + await metricsClient.getSteps(params, buildId, organizationId, pipelineType, token); + }).rejects.toThrow(VERIFY_ERROR_MESSAGE.INTERNAL_SERVER_ERROR); + }); it('should throw error when getSteps response status 400', async () => { server.use( rest.get(getStepsUrl, (req, res, ctx) => res(ctx.status(HttpStatusCode.BadRequest), ctx.json({ hintInfo: VERIFY_ERROR_MESSAGE.BAD_REQUEST })) ) - ) + ); await expect(async () => { - await metricsClient.getSteps(params, buildId, organizationId, pipelineType, token) - }).rejects.toThrow(VERIFY_ERROR_MESSAGE.BAD_REQUEST) - }) + await metricsClient.getSteps(params, buildId, organizationId, pipelineType, token); + }).rejects.toThrow(VERIFY_ERROR_MESSAGE.BAD_REQUEST); + }); it('should show isNoStep True when getSteps response status 204', async () => { - server.use(rest.get(getStepsUrl, (req, res, ctx) => res(ctx.status(HttpStatusCode.NoContent)))) + server.use(rest.get(getStepsUrl, (req, res, ctx) => res(ctx.status(HttpStatusCode.NoContent)))); - const result = await metricsClient.getSteps(params, buildId, organizationId, pipelineType, token) + const result = await metricsClient.getSteps(params, buildId, organizationId, pipelineType, token); - expect(result).toEqual({ branches: [], response: [], haveStep: false, pipelineCrews: [] }) - }) -}) + expect(result).toEqual({ branches: [], response: [], haveStep: false, pipelineCrews: [] }); + }); +}); diff --git a/frontend/__tests__/src/client/PipelineToolClient.test.ts b/frontend/__tests__/src/client/PipelineToolClient.test.ts index af1c7e2a04..f9917dc5bf 100644 --- a/frontend/__tests__/src/client/PipelineToolClient.test.ts +++ b/frontend/__tests__/src/client/PipelineToolClient.test.ts @@ -1,57 +1,57 @@ -import { setupServer } from 'msw/node' -import { rest } from 'msw' -import { MOCK_PIPELINE_URL, MOCK_PIPELINE_VERIFY_REQUEST_PARAMS, VERIFY_ERROR_MESSAGE } from '../fixtures' -import { pipelineToolClient } from '@src/clients/pipeline/PipelineToolClient' -import { HttpStatusCode } from 'axios' +import { setupServer } from 'msw/node'; +import { rest } from 'msw'; +import { MOCK_PIPELINE_URL, MOCK_PIPELINE_VERIFY_REQUEST_PARAMS, VERIFY_ERROR_MESSAGE } from '../fixtures'; +import { pipelineToolClient } from '@src/clients/pipeline/PipelineToolClient'; +import { HttpStatusCode } from 'axios'; const server = setupServer( rest.post(MOCK_PIPELINE_URL, (req, res, ctx) => { - return res(ctx.status(HttpStatusCode.Ok)) + return res(ctx.status(HttpStatusCode.Ok)); }) -) +); describe('verify pipelineTool request', () => { - beforeAll(() => server.listen()) - afterAll(() => server.close()) + beforeAll(() => server.listen()); + afterAll(() => server.close()); it('should isPipelineVerified is true when pipelineTool verify response status 200', async () => { - const result = await pipelineToolClient.verifyPipelineTool(MOCK_PIPELINE_VERIFY_REQUEST_PARAMS) + const result = await pipelineToolClient.verifyPipelineTool(MOCK_PIPELINE_VERIFY_REQUEST_PARAMS); - expect(result.isPipelineToolVerified).toEqual(true) - }) + expect(result.isPipelineToolVerified).toEqual(true); + }); it('should throw error when pipelineTool verify response status 400', async () => { server.use( rest.post(MOCK_PIPELINE_URL, (req, res, ctx) => res(ctx.status(HttpStatusCode.BadRequest), ctx.json({ hintInfo: VERIFY_ERROR_MESSAGE.BAD_REQUEST })) ) - ) + ); await expect(() => pipelineToolClient.verifyPipelineTool(MOCK_PIPELINE_VERIFY_REQUEST_PARAMS)).rejects.toThrow( VERIFY_ERROR_MESSAGE.BAD_REQUEST - ) - }) + ); + }); it('should throw error when pipelineTool verify response status is 401', async () => { server.use( rest.post(MOCK_PIPELINE_URL, (req, res, ctx) => res(ctx.status(HttpStatusCode.Unauthorized), ctx.json({ hintInfo: VERIFY_ERROR_MESSAGE.UNAUTHORIZED })) ) - ) + ); await expect(() => pipelineToolClient.verifyPipelineTool(MOCK_PIPELINE_VERIFY_REQUEST_PARAMS)).rejects.toThrow( VERIFY_ERROR_MESSAGE.UNAUTHORIZED - ) - }) + ); + }); it('should throw error when pipelineTool verify response status is 403', async () => { server.use( rest.post(MOCK_PIPELINE_URL, (req, res, ctx) => res(ctx.status(HttpStatusCode.Forbidden), ctx.json({ hintInfo: VERIFY_ERROR_MESSAGE.PERMISSION_DENIED })) ) - ) + ); await expect(() => pipelineToolClient.verifyPipelineTool(MOCK_PIPELINE_VERIFY_REQUEST_PARAMS)).rejects.toThrow( VERIFY_ERROR_MESSAGE.PERMISSION_DENIED - ) - }) + ); + }); it('should throw error when pipelineTool verify response status 500', async () => { server.use( @@ -63,11 +63,11 @@ describe('verify pipelineTool request', () => { }) ) ) - ) + ); await expect(() => pipelineToolClient.verifyPipelineTool(MOCK_PIPELINE_VERIFY_REQUEST_PARAMS)).rejects.toThrow( VERIFY_ERROR_MESSAGE.INTERNAL_SERVER_ERROR - ) - }) + ); + }); it('should throw error when board verify response status 300', async () => { server.use( @@ -79,10 +79,10 @@ describe('verify pipelineTool request', () => { }) ) ) - ) + ); await expect(() => pipelineToolClient.verifyPipelineTool(MOCK_PIPELINE_VERIFY_REQUEST_PARAMS)).rejects.toThrow( VERIFY_ERROR_MESSAGE.UNKNOWN - ) - }) -}) + ); + }); +}); diff --git a/frontend/__tests__/src/client/ReportClient.test.ts b/frontend/__tests__/src/client/ReportClient.test.ts index 36a06e11b7..ee6490e411 100644 --- a/frontend/__tests__/src/client/ReportClient.test.ts +++ b/frontend/__tests__/src/client/ReportClient.test.ts @@ -1,39 +1,39 @@ -import { setupServer } from 'msw/node' -import { rest } from 'msw' +import { setupServer } from 'msw/node'; +import { rest } from 'msw'; import { MOCK_GENERATE_REPORT_REQUEST_PARAMS, MOCK_REPORT_RESPONSE, MOCK_RETRIEVE_REPORT_RESPONSE, VERIFY_ERROR_MESSAGE, -} from '../fixtures' -import { HttpStatusCode } from 'axios' -import { reportClient } from '@src/clients/report/ReportClient' +} from '../fixtures'; +import { HttpStatusCode } from 'axios'; +import { reportClient } from '@src/clients/report/ReportClient'; -const MOCK_REPORT_URL = 'http://localhost/api/v1/reports' +const MOCK_REPORT_URL = 'http://localhost/api/v1/reports'; const server = setupServer( rest.post(MOCK_REPORT_URL, (req, res, ctx) => res(ctx.status(HttpStatusCode.Ok))), rest.get(MOCK_REPORT_URL, (req, res, ctx) => res(ctx.status(HttpStatusCode.Ok))) -) +); describe('report client', () => { - beforeAll(() => server.listen()) - afterEach(() => server.resetHandlers()) - afterAll(() => server.close()) + beforeAll(() => server.listen()); + afterEach(() => server.resetHandlers()); + afterAll(() => server.close()); it('should get response when generate report request status 202', async () => { const excepted = { response: MOCK_RETRIEVE_REPORT_RESPONSE, - } + }; server.use( rest.post(MOCK_REPORT_URL, (req, res, ctx) => res(ctx.status(HttpStatusCode.Accepted), ctx.json(MOCK_RETRIEVE_REPORT_RESPONSE)) ) - ) + ); await expect( reportClient.retrieveReportByUrl(MOCK_GENERATE_REPORT_REQUEST_PARAMS, '/reports') - ).resolves.toStrictEqual(excepted) - }) + ).resolves.toStrictEqual(excepted); + }); it('should throw error when generate report response status 500', async () => { server.use( @@ -45,12 +45,12 @@ describe('report client', () => { }) ) ) - ) + ); await expect(async () => { - await reportClient.retrieveReportByUrl(MOCK_GENERATE_REPORT_REQUEST_PARAMS, '/reports') - }).rejects.toThrow(VERIFY_ERROR_MESSAGE.INTERNAL_SERVER_ERROR) - }) + await reportClient.retrieveReportByUrl(MOCK_GENERATE_REPORT_REQUEST_PARAMS, '/reports'); + }).rejects.toThrow(VERIFY_ERROR_MESSAGE.INTERNAL_SERVER_ERROR); + }); it('should throw error when generate report response status 400', async () => { server.use( @@ -62,12 +62,12 @@ describe('report client', () => { }) ) ) - ) + ); await expect(async () => { - await reportClient.retrieveReportByUrl(MOCK_GENERATE_REPORT_REQUEST_PARAMS, '/reports') - }).rejects.toThrow(VERIFY_ERROR_MESSAGE.BAD_REQUEST) - }) + await reportClient.retrieveReportByUrl(MOCK_GENERATE_REPORT_REQUEST_PARAMS, '/reports'); + }).rejects.toThrow(VERIFY_ERROR_MESSAGE.BAD_REQUEST); + }); it('should throw error when calling pollingReport given response status 500', () => { server.use( @@ -79,24 +79,24 @@ describe('report client', () => { }) ) ) - ) + ); expect(async () => { - await reportClient.pollingReport(MOCK_REPORT_URL) - }).rejects.toThrow(VERIFY_ERROR_MESSAGE.INTERNAL_SERVER_ERROR) - }) + await reportClient.pollingReport(MOCK_REPORT_URL); + }).rejects.toThrow(VERIFY_ERROR_MESSAGE.INTERNAL_SERVER_ERROR); + }); it('should return status and response when calling pollingReport given response status 201', async () => { const excepted = { status: HttpStatusCode.Created, response: MOCK_REPORT_RESPONSE, - } + }; server.use( rest.get(MOCK_REPORT_URL, (req, res, ctx) => res(ctx.status(HttpStatusCode.Created), ctx.json(MOCK_REPORT_RESPONSE)) ) - ) + ); - await expect(reportClient.pollingReport(MOCK_REPORT_URL)).resolves.toEqual(excepted) - }) -}) + await expect(reportClient.pollingReport(MOCK_REPORT_URL)).resolves.toEqual(excepted); + }); +}); diff --git a/frontend/__tests__/src/client/SourceControlClient.test.ts b/frontend/__tests__/src/client/SourceControlClient.test.ts index 5cb7b26fb5..62c13c6f91 100644 --- a/frontend/__tests__/src/client/SourceControlClient.test.ts +++ b/frontend/__tests__/src/client/SourceControlClient.test.ts @@ -1,46 +1,46 @@ -import { setupServer } from 'msw/node' -import { rest } from 'msw' -import { MOCK_SOURCE_CONTROL_URL, MOCK_SOURCE_CONTROL_VERIFY_REQUEST_PARAMS, VERIFY_ERROR_MESSAGE } from '../fixtures' -import { sourceControlClient } from '@src/clients/sourceControl/SourceControlClient' -import { HttpStatusCode } from 'axios' +import { setupServer } from 'msw/node'; +import { rest } from 'msw'; +import { MOCK_SOURCE_CONTROL_URL, MOCK_SOURCE_CONTROL_VERIFY_REQUEST_PARAMS, VERIFY_ERROR_MESSAGE } from '../fixtures'; +import { sourceControlClient } from '@src/clients/sourceControl/SourceControlClient'; +import { HttpStatusCode } from 'axios'; -const server = setupServer(rest.post(MOCK_SOURCE_CONTROL_URL, (req, res, ctx) => res(ctx.status(200)))) +const server = setupServer(rest.post(MOCK_SOURCE_CONTROL_URL, (req, res, ctx) => res(ctx.status(200)))); describe('verify sourceControl request', () => { - beforeAll(() => server.listen()) - afterAll(() => server.close()) + beforeAll(() => server.listen()); + afterAll(() => server.close()); it('should return isSourceControlVerify true when sourceControl verify response status is 200', async () => { - const result = await sourceControlClient.getVerifySourceControl(MOCK_SOURCE_CONTROL_VERIFY_REQUEST_PARAMS) + const result = await sourceControlClient.getVerifySourceControl(MOCK_SOURCE_CONTROL_VERIFY_REQUEST_PARAMS); - expect(result.isSourceControlVerify).toEqual(true) - }) + expect(result.isSourceControlVerify).toEqual(true); + }); it('should throw error when sourceControl verify response status is 400', () => { server.use( rest.post(MOCK_SOURCE_CONTROL_URL, (req, res, ctx) => res(ctx.status(HttpStatusCode.BadRequest), ctx.json({ hintInfo: VERIFY_ERROR_MESSAGE.BAD_REQUEST })) ) - ) + ); sourceControlClient.getVerifySourceControl(MOCK_SOURCE_CONTROL_VERIFY_REQUEST_PARAMS).catch((e) => { - expect(e).toBeInstanceOf(Error) - expect((e as Error).message).toMatch(VERIFY_ERROR_MESSAGE.BAD_REQUEST) - }) - }) + expect(e).toBeInstanceOf(Error); + expect((e as Error).message).toMatch(VERIFY_ERROR_MESSAGE.BAD_REQUEST); + }); + }); it('should throw error when sourceControl verify response status is 404', async () => { server.use( rest.post(MOCK_SOURCE_CONTROL_URL, (req, res, ctx) => res(ctx.status(HttpStatusCode.NotFound), ctx.json({ hintInfo: VERIFY_ERROR_MESSAGE.NOT_FOUND })) ) - ) + ); sourceControlClient.getVerifySourceControl(MOCK_SOURCE_CONTROL_VERIFY_REQUEST_PARAMS).catch((e) => { - expect(e).toBeInstanceOf(Error) - expect((e as Error).message).toMatch(VERIFY_ERROR_MESSAGE.NOT_FOUND) - }) - }) + expect(e).toBeInstanceOf(Error); + expect((e as Error).message).toMatch(VERIFY_ERROR_MESSAGE.NOT_FOUND); + }); + }); it('should throw error when sourceControl verify response status 500', async () => { server.use( @@ -52,13 +52,13 @@ describe('verify sourceControl request', () => { }) ) ) - ) + ); sourceControlClient.getVerifySourceControl(MOCK_SOURCE_CONTROL_VERIFY_REQUEST_PARAMS).catch((e) => { - expect(e).toBeInstanceOf(Error) - expect((e as Error).message).toMatch(VERIFY_ERROR_MESSAGE.INTERNAL_SERVER_ERROR) - }) - }) + expect(e).toBeInstanceOf(Error); + expect((e as Error).message).toMatch(VERIFY_ERROR_MESSAGE.INTERNAL_SERVER_ERROR); + }); + }); it('should throw error when sourceControl verify response status is 300', async () => { server.use( @@ -70,10 +70,10 @@ describe('verify sourceControl request', () => { }) ) ) - ) + ); await expect(async () => { - await sourceControlClient.getVerifySourceControl(MOCK_SOURCE_CONTROL_VERIFY_REQUEST_PARAMS) - }).rejects.toThrow(VERIFY_ERROR_MESSAGE.UNKNOWN) - }) -}) + await sourceControlClient.getVerifySourceControl(MOCK_SOURCE_CONTROL_VERIFY_REQUEST_PARAMS); + }).rejects.toThrow(VERIFY_ERROR_MESSAGE.UNKNOWN); + }); +}); diff --git a/frontend/__tests__/src/components/Common/DateRangeViewer/DateRangeViewer.test.tsx b/frontend/__tests__/src/components/Common/DateRangeViewer/DateRangeViewer.test.tsx index 3d24e564e4..eb15b7b1e9 100644 --- a/frontend/__tests__/src/components/Common/DateRangeViewer/DateRangeViewer.test.tsx +++ b/frontend/__tests__/src/components/Common/DateRangeViewer/DateRangeViewer.test.tsx @@ -1,11 +1,10 @@ -import { render } from '@testing-library/react' -import DateRangeViewer from '@src/components/Common/DateRangeViewer' +import React from 'react'; +import { render, screen } from '@testing-library/react'; +import DateRangeViewer from '@src/components/Common/DateRangeViewer'; describe('DateRangeVier', () => { it('should show date when render component given startDate and endDate', () => { - const { getByText } = render( - - ) - expect(getByText(/2022\/01\/01/g)).toBeInTheDocument() - expect(getByText(/2022\/01\/02/g)).toBeInTheDocument() - }) -}) + render(); + expect(screen.getByText(/2022\/01\/01/)).toBeInTheDocument(); + expect(screen.getByText(/2022\/01\/02/)).toBeInTheDocument(); + }); +}); diff --git a/frontend/__tests__/src/components/Common/EllipsisText/EllipsisText.test.tsx b/frontend/__tests__/src/components/Common/EllipsisText/EllipsisText.test.tsx index 9a6c82a2a2..215afd381a 100644 --- a/frontend/__tests__/src/components/Common/EllipsisText/EllipsisText.test.tsx +++ b/frontend/__tests__/src/components/Common/EllipsisText/EllipsisText.test.tsx @@ -1,47 +1,47 @@ -import React from 'react' -import { render } from '@testing-library/react' -import EllipsisText from '@src/components/Common/EllipsisText' +import React from 'react'; +import { render, screen } from '@testing-library/react'; +import EllipsisText from '@src/components/Common/EllipsisText'; describe('EllipsisText', () => { - const WIDTH = '500rem' + const WIDTH = '500rem'; it('should forward ref properly', () => { - const ref = React.createRef() - const { getByLabelText } = render( + const ref = React.createRef(); + render(
test
- ) + ); - const childDOM = getByLabelText('test-ref') - expect(ref.current).toEqual(childDOM) - }) + const childDOM = screen.getByLabelText('test-ref'); + expect(ref.current).toEqual(childDOM); + }); it('should apply fit-content as its width when `fitContent` specified', async () => { - const { getByLabelText } = render( + render(
test
- ) + ); - const targetElement = getByLabelText('test-ellipsis-text') - expect(targetElement).toHaveStyle({ width: 'fit-content' }) - }) + const targetElement = screen.getByLabelText('test-ellipsis-text'); + expect(targetElement).toHaveStyle({ width: 'fit-content' }); + }); it('should apply fit-content as its width when `fitContent` explicitly set to false', async () => { - const { getByLabelText } = render( + render(
test
- ) + ); - const targetElement = getByLabelText('test-ellipsis-text') - expect(targetElement).toHaveStyle({ width: 'auto' }) - }) -}) + const targetElement = screen.getByLabelText('test-ellipsis-text'); + expect(targetElement).toHaveStyle({ width: 'auto' }); + }); +}); diff --git a/frontend/__tests__/src/components/Common/NotificationButton/NotificationButton.test.tsx b/frontend/__tests__/src/components/Common/NotificationButton/NotificationButton.test.tsx index 9a3cae106b..d8ae510a8e 100644 --- a/frontend/__tests__/src/components/Common/NotificationButton/NotificationButton.test.tsx +++ b/frontend/__tests__/src/components/Common/NotificationButton/NotificationButton.test.tsx @@ -1,110 +1,110 @@ -import { render, renderHook, waitFor, screen } from '@testing-library/react' -import { NotificationButton } from '@src/components/Common/NotificationButton' -import React from 'react' -import { useNotificationLayoutEffect } from '@src/hooks/useNotificationLayoutEffect' -import { act } from 'react-dom/test-utils' -import userEvent from '@testing-library/user-event' - -const notificationIcon = 'NotificationIcon' +import { render, renderHook, waitFor, screen } from '@testing-library/react'; +import { NotificationButton } from '@src/components/Common/NotificationButton'; +import React from 'react'; +import { useNotificationLayoutEffect } from '@src/hooks/useNotificationLayoutEffect'; +import { act } from 'react-dom/test-utils'; +import userEvent from '@testing-library/user-event'; + +const notificationIcon = 'NotificationIcon'; describe('NotificationButton', () => { - const closeNotificationProps = { open: false, title: 'NotificationPopper', closeAutomatically: false } - const openNotificationProps = { open: true, title: 'NotificationPopper', closeAutomatically: false } - const { result } = renderHook(() => useNotificationLayoutEffect()) + const closeNotificationProps = { open: false, title: 'NotificationPopper', closeAutomatically: false }; + const openNotificationProps = { open: true, title: 'NotificationPopper', closeAutomatically: false }; + const { result } = renderHook(() => useNotificationLayoutEffect()); it('should show NotificationIcon when render NotificationButton component', () => { - const { getByTestId } = render() + const { getByTestId } = render(); - expect(getByTestId(notificationIcon)).toBeInTheDocument() - }) + expect(getByTestId(notificationIcon)).toBeInTheDocument(); + }); it('should show NotificationPopper when clicking the component given the "open" value is true', async () => { act(() => { - result.current.notificationProps = openNotificationProps - }) - const { getByText } = render() - await userEvent.click(screen.getByTestId(notificationIcon)) - expect(getByText('NotificationPopper')).toBeInTheDocument() - }) + result.current.notificationProps = openNotificationProps; + }); + const { getByText } = render(); + await userEvent.click(screen.getByTestId(notificationIcon)); + expect(getByText('NotificationPopper')).toBeInTheDocument(); + }); it('should hide NotificationPopper when clicking the component given the "open" value is false', async () => { act(() => { - result.current.notificationProps = closeNotificationProps - }) - const { getByTestId, queryByText } = render() - await userEvent.click(getByTestId(notificationIcon)) + result.current.notificationProps = closeNotificationProps; + }); + const { getByTestId, queryByText } = render(); + await userEvent.click(getByTestId(notificationIcon)); - expect(queryByText('NotificationPopper')).not.toBeInTheDocument() - }) + expect(queryByText('NotificationPopper')).not.toBeInTheDocument(); + }); it('should call updateProps when clicking outside the component given the "open" value.', async () => { - let checkProps = openNotificationProps + let checkProps = openNotificationProps; act(() => { - result.current.notificationProps = openNotificationProps - result.current.updateProps = jest.fn().mockImplementation(() => (checkProps = closeNotificationProps)) - }) + result.current.notificationProps = openNotificationProps; + result.current.updateProps = jest.fn().mockImplementation(() => (checkProps = closeNotificationProps)); + }); const { getByRole, getByText } = render(
OutSideSection
- ) + ); - expect(getByRole('tooltip')).toBeInTheDocument() + expect(getByRole('tooltip')).toBeInTheDocument(); - const content = await waitFor(() => getByText('OutSideSection')) - await userEvent.click(content) + const content = await waitFor(() => getByText('OutSideSection')); + await userEvent.click(content); - expect(result.current.updateProps).toBeCalledTimes(1) - expect(checkProps).toEqual(closeNotificationProps) - }) + expect(result.current.updateProps).toBeCalledTimes(1); + expect(checkProps).toEqual(closeNotificationProps); + }); it('should hide the NotificationPopper component when call render given notificationProps is undefined.', () => { act(() => { - result.current.notificationProps = undefined - }) - const { queryByText } = render() + result.current.notificationProps = undefined; + }); + const { queryByText } = render(); - expect(queryByText('NotificationPopper')).not.toBeInTheDocument() - }) + expect(queryByText('NotificationPopper')).not.toBeInTheDocument(); + }); it('should hide the NotificationPopper component when call render given updateProps is undefined.', () => { act(() => { - result.current.updateProps = undefined - }) - const { queryByText } = render() + result.current.updateProps = undefined; + }); + const { queryByText } = render(); - expect(queryByText('NotificationPopper')).not.toBeInTheDocument() - }) + expect(queryByText('NotificationPopper')).not.toBeInTheDocument(); + }); it('should hide the Notification component when call render given updateProps and notificationProps are undefined.', () => { act(() => { - result.current.notificationProps = undefined - result.current.updateProps = undefined - }) - const { queryByText } = render() + result.current.notificationProps = undefined; + result.current.updateProps = undefined; + }); + const { queryByText } = render(); - expect(queryByText('NotificationPopper')).not.toBeInTheDocument() - }) + expect(queryByText('NotificationPopper')).not.toBeInTheDocument(); + }); it('should not call updateProps when clicking outside the component given the notificationProps is undefined.', async () => { act(() => { - result.current.notificationProps = undefined - result.current.updateProps = jest.fn() - }) + result.current.notificationProps = undefined; + result.current.updateProps = jest.fn(); + }); const { getByText, getByTestId } = render(
OutSideSection
- ) + ); - expect(getByTestId(notificationIcon)).toBeInTheDocument() + expect(getByTestId(notificationIcon)).toBeInTheDocument(); - const content = await waitFor(() => getByText('OutSideSection')) - await userEvent.click(content) + const content = await waitFor(() => getByText('OutSideSection')); + await userEvent.click(content); - expect(result.current.updateProps).not.toBeCalled() - }) -}) + expect(result.current.updateProps).not.toBeCalled(); + }); +}); diff --git a/frontend/__tests__/src/components/Common/ReportGrid/ReportCard.test.tsx b/frontend/__tests__/src/components/Common/ReportGrid/ReportCard.test.tsx index c6cd7fe25f..5f9763446b 100644 --- a/frontend/__tests__/src/components/Common/ReportGrid/ReportCard.test.tsx +++ b/frontend/__tests__/src/components/Common/ReportGrid/ReportCard.test.tsx @@ -1,5 +1,6 @@ -import { render } from '@testing-library/react' -import { ReportCard } from '@src/components/Common/ReportGrid/ReportCard' +import React from 'react'; +import { render, screen } from '@testing-library/react'; +import { ReportCard } from '@src/components/Common/ReportGrid/ReportCard'; describe('Report Card', () => { it('should not show exceeding items', () => { @@ -16,12 +17,12 @@ describe('Report Card', () => { value: 3, subtitle: 'Total Lead Time', }, - ] + ]; - const { getByText, queryByText } = render() + render(); - expect(getByText('1.00')).toBeInTheDocument() - expect(getByText('2.00')).toBeInTheDocument() - expect(queryByText('3.00')).not.toBeInTheDocument() - }) -}) + expect(screen.getByText('1.00')).toBeInTheDocument(); + expect(screen.getByText('2.00')).toBeInTheDocument(); + expect(screen.queryByText('3.00')).not.toBeInTheDocument(); + }); +}); diff --git a/frontend/__tests__/src/components/DateDisplay/CollectionDuration.test.tsx b/frontend/__tests__/src/components/DateDisplay/CollectionDuration.test.tsx index c964dc497c..74c789cef8 100644 --- a/frontend/__tests__/src/components/DateDisplay/CollectionDuration.test.tsx +++ b/frontend/__tests__/src/components/DateDisplay/CollectionDuration.test.tsx @@ -1,7 +1,7 @@ -import { render } from '@testing-library/react' -import CollectionDuration from '@src/components/Common/CollectionDuration' -import React from 'react' -import { IMPORTED_NEW_CONFIG_FIXTURE, TIME_DISPLAY_TITTLE_END, TIME_DISPLAY_TITTLE_START } from '../../fixtures' +import { render, screen } from '@testing-library/react'; +import CollectionDuration from '@src/components/Common/CollectionDuration'; +import React from 'react'; +import { IMPORTED_NEW_CONFIG_FIXTURE, TIME_DISPLAY_TITTLE_END, TIME_DISPLAY_TITTLE_START } from '../../fixtures'; describe('Collection Duration', () => { const setup = () => @@ -10,18 +10,18 @@ describe('Collection Duration', () => { startDate={IMPORTED_NEW_CONFIG_FIXTURE.dateRange.startDate} endDate={IMPORTED_NEW_CONFIG_FIXTURE.dateRange.endDate} /> - ) + ); it('should render the start and end text correctly', () => { - const { getByText } = setup() + setup(); - expect(getByText(TIME_DISPLAY_TITTLE_START)).toBeInTheDocument() - expect(getByText(TIME_DISPLAY_TITTLE_END)).toBeInTheDocument() - }) + expect(screen.getByText(TIME_DISPLAY_TITTLE_START)).toBeInTheDocument(); + expect(screen.getByText(TIME_DISPLAY_TITTLE_END)).toBeInTheDocument(); + }); it('should render the start and end time with correct format', () => { - const { getByText, getAllByText } = setup() + setup(); - expect(getByText('16')).toBeInTheDocument() - expect(getAllByText('Mar 23')).toHaveLength(2) - expect(getByText('30')).toBeInTheDocument() - }) -}) + expect(screen.getByText('16')).toBeInTheDocument(); + expect(screen.getAllByText('Mar 23')).toHaveLength(2); + expect(screen.getByText('30')).toBeInTheDocument(); + }); +}); diff --git a/frontend/__tests__/src/components/ErrorContent/index.test.tsx b/frontend/__tests__/src/components/ErrorContent/index.test.tsx index aeeba5fda1..55e43ce6b5 100644 --- a/frontend/__tests__/src/components/ErrorContent/index.test.tsx +++ b/frontend/__tests__/src/components/ErrorContent/index.test.tsx @@ -1,14 +1,14 @@ -import { render } from '@testing-library/react' -import ErrorPage from '@src/pages/ErrorPage' -import { BASE_PAGE_ROUTE, ERROR_PAGE_MESSAGE, RETRY_BUTTON } from '../../fixtures' -import React from 'react' -import { BrowserRouter } from 'react-router-dom' -import userEvent from '@testing-library/user-event' -import { navigateMock } from '../../../setupTests' -import { ErrorContent } from '@src/components/ErrorContent' -import { headerClient } from '@src/clients/header/HeaderClient' -import { setupStore } from '../../utils/setupStoreUtil' -import { Provider } from 'react-redux' +import { render } from '@testing-library/react'; +import ErrorPage from '@src/pages/ErrorPage'; +import { BASE_PAGE_ROUTE, ERROR_PAGE_MESSAGE, RETRY_BUTTON } from '../../fixtures'; +import React from 'react'; +import { BrowserRouter } from 'react-router-dom'; +import userEvent from '@testing-library/user-event'; +import { navigateMock } from '../../../setupTests'; +import { ErrorContent } from '@src/components/ErrorContent'; +import { headerClient } from '@src/clients/header/HeaderClient'; +import { setupStore } from '../../utils/setupStoreUtil'; +import { Provider } from 'react-redux'; describe('error content', () => { it('should show error message when render error page', () => { @@ -16,23 +16,23 @@ describe('error content', () => { - ) + ); - expect(getByText(ERROR_PAGE_MESSAGE)).toBeInTheDocument() - expect(getByText(RETRY_BUTTON)).toBeInTheDocument() - }) + expect(getByText(ERROR_PAGE_MESSAGE)).toBeInTheDocument(); + expect(getByText(RETRY_BUTTON)).toBeInTheDocument(); + }); it('should go to home page when click button', async () => { - headerClient.getVersion = jest.fn().mockResolvedValue('') + headerClient.getVersion = jest.fn().mockResolvedValue(''); const { getByText } = render( - ) - await userEvent.click(getByText(RETRY_BUTTON)) + ); + await userEvent.click(getByText(RETRY_BUTTON)); - expect(navigateMock).toHaveBeenCalledWith(BASE_PAGE_ROUTE) - }) -}) + expect(navigateMock).toHaveBeenCalledWith(BASE_PAGE_ROUTE); + }); +}); diff --git a/frontend/__tests__/src/components/ErrorNotification/ErrorNotification.test.tsx b/frontend/__tests__/src/components/ErrorNotification/ErrorNotification.test.tsx index 642c7fedaf..cb0b1ae7ab 100644 --- a/frontend/__tests__/src/components/ErrorNotification/ErrorNotification.test.tsx +++ b/frontend/__tests__/src/components/ErrorNotification/ErrorNotification.test.tsx @@ -1,13 +1,13 @@ -import { render } from '@testing-library/react' -import { ErrorNotification } from '@src/components/ErrorNotification' -import { BOARD_TYPES, VERIFY_ERROR_MESSAGE } from '../../fixtures' +import { render } from '@testing-library/react'; +import { ErrorNotification } from '@src/components/ErrorNotification'; +import { BOARD_TYPES, VERIFY_ERROR_MESSAGE } from '../../fixtures'; describe('error notification', () => { it('should show error message when render error notification', () => { const { getByText } = render( - ) + ); - expect(getByText(`${BOARD_TYPES.JIRA} ${VERIFY_ERROR_MESSAGE.BAD_REQUEST}`)).toBeInTheDocument() - }) -}) + expect(getByText(`${BOARD_TYPES.JIRA} ${VERIFY_ERROR_MESSAGE.BAD_REQUEST}`)).toBeInTheDocument(); + }); +}); diff --git a/frontend/__tests__/src/components/ErrorNotification/WarningNotification.test.tsx b/frontend/__tests__/src/components/ErrorNotification/WarningNotification.test.tsx index e3f961762d..b7f042df46 100644 --- a/frontend/__tests__/src/components/ErrorNotification/WarningNotification.test.tsx +++ b/frontend/__tests__/src/components/ErrorNotification/WarningNotification.test.tsx @@ -1,38 +1,38 @@ -import { act, render, waitFor } from '@testing-library/react' -import { setupStore } from '../../utils/setupStoreUtil' -import { Provider } from 'react-redux' -import React from 'react' -import { WarningNotification } from '@src/components/Common/WarningNotification' -import { ERROR_MESSAGE_TIME_DURATION } from '../../fixtures' +import { act, render, waitFor } from '@testing-library/react'; +import { setupStore } from '../../utils/setupStoreUtil'; +import { Provider } from 'react-redux'; +import React from 'react'; +import { WarningNotification } from '@src/components/Common/WarningNotification'; +import { ERROR_MESSAGE_TIME_DURATION } from '../../fixtures'; -let store = null -jest.useFakeTimers() +let store = null; +jest.useFakeTimers(); describe('ErrorNotificationAutoDismiss', () => { - store = setupStore() - const message = 'Test error message' + store = setupStore(); + const message = 'Test error message'; const setup = () => { - store = setupStore() + store = setupStore(); return render( - ) - } + ); + }; afterEach(() => { - jest.clearAllMocks() - }) + jest.clearAllMocks(); + }); test('renders error message and dismisses after 2 seconds', async () => { - const { getByText, queryByText } = setup() + const { getByText, queryByText } = setup(); - expect(getByText(message)).toBeInTheDocument() + expect(getByText(message)).toBeInTheDocument(); act(() => { - jest.advanceTimersByTime(ERROR_MESSAGE_TIME_DURATION) - }) + jest.advanceTimersByTime(ERROR_MESSAGE_TIME_DURATION); + }); await waitFor(() => { - expect(queryByText(message)).not.toBeInTheDocument() - }) - }) -}) + expect(queryByText(message)).not.toBeInTheDocument(); + }); + }); +}); diff --git a/frontend/__tests__/src/components/HomeGuide/HomeGuide.test.tsx b/frontend/__tests__/src/components/HomeGuide/HomeGuide.test.tsx index f149221aba..a4712189ba 100644 --- a/frontend/__tests__/src/components/HomeGuide/HomeGuide.test.tsx +++ b/frontend/__tests__/src/components/HomeGuide/HomeGuide.test.tsx @@ -1,123 +1,123 @@ -import { HomeGuide } from '@src/components/HomeGuide' -import { fireEvent, render, waitFor, screen } from '@testing-library/react' -import { setupStore } from '../../utils/setupStoreUtil' -import { Provider } from 'react-redux' +import { HomeGuide } from '@src/components/HomeGuide'; +import { fireEvent, render, waitFor, screen } from '@testing-library/react'; +import { setupStore } from '../../utils/setupStoreUtil'; +import { Provider } from 'react-redux'; import { CREATE_NEW_PROJECT, HOME_VERIFY_IMPORT_WARNING_MESSAGE, IMPORT_PROJECT_FROM_FILE, METRICS_PAGE_ROUTE, IMPORTED_NEW_CONFIG_FIXTURE, -} from '../../fixtures' -import userEvent from '@testing-library/user-event' -import { navigateMock } from '../../../setupTests' +} from '../../fixtures'; +import userEvent from '@testing-library/user-event'; +import { navigateMock } from '../../../setupTests'; -const mockedUseAppDispatch = jest.fn() +const mockedUseAppDispatch = jest.fn(); jest.mock('@src/hooks/useAppDispatch', () => ({ ...jest.requireActual('react-router-dom'), useAppDispatch: () => mockedUseAppDispatch, -})) +})); -let store = setupStore() +let store = setupStore(); const setup = () => { - store = setupStore() + store = setupStore(); return render( - ) -} + ); +}; const setupInputFile = async (configJson: object) => { - const { queryByText, getByTestId } = setup() + const { queryByText, getByTestId } = setup(); const file = new File([`${JSON.stringify(configJson)}`], 'test.json', { type: 'file', - }) + }); - const input = getByTestId('testInput') + const input = getByTestId('testInput'); Object.defineProperty(input, 'files', { value: [file], - }) + }); - await fireEvent.change(input) - return queryByText -} + await fireEvent.change(input); + return queryByText; +}; describe('HomeGuide', () => { beforeEach(() => { - store = setupStore() - }) + store = setupStore(); + }); afterEach(() => { - jest.clearAllMocks() - }) + jest.clearAllMocks(); + }); it('should show 2 buttons', () => { - const { getByText } = setup() + const { getByText } = setup(); - expect(getByText(IMPORT_PROJECT_FROM_FILE)).toBeInTheDocument() - expect(getByText(CREATE_NEW_PROJECT)).toBeInTheDocument() - }) + expect(getByText(IMPORT_PROJECT_FROM_FILE)).toBeInTheDocument(); + expect(getByText(CREATE_NEW_PROJECT)).toBeInTheDocument(); + }); it('should render input when click guide button', async () => { - const { getByTestId } = setup() - const fileInput = getByTestId('testInput') + const { getByTestId } = setup(); + const fileInput = getByTestId('testInput'); - const clickSpy = jest.spyOn(fileInput, 'click') - await userEvent.click(screen.getByText(IMPORT_PROJECT_FROM_FILE)) - expect(clickSpy).toHaveBeenCalled() - }) + const clickSpy = jest.spyOn(fileInput, 'click'); + await userEvent.click(screen.getByText(IMPORT_PROJECT_FROM_FILE)); + expect(clickSpy).toHaveBeenCalled(); + }); it('should go to Metrics page and read file when click import file button', async () => { - const { getByTestId } = setup() + const { getByTestId } = setup(); const file = new File([`${JSON.stringify(IMPORTED_NEW_CONFIG_FIXTURE)}`], 'test.json', { type: 'file', - }) + }); - const input = getByTestId('testInput') + const input = getByTestId('testInput'); Object.defineProperty(input, 'files', { value: [file], - }) + }); - fireEvent.change(input) + fireEvent.change(input); await waitFor(() => { - expect(mockedUseAppDispatch).toHaveBeenCalledTimes(3) - expect(navigateMock).toHaveBeenCalledWith(METRICS_PAGE_ROUTE) - }) - }) + expect(mockedUseAppDispatch).toHaveBeenCalledTimes(3); + expect(navigateMock).toHaveBeenCalledWith(METRICS_PAGE_ROUTE); + }); + }); it('should go to Metrics page when click create a new project button', async () => { - setup() - await userEvent.click(screen.getByText(CREATE_NEW_PROJECT)) - expect(navigateMock).toHaveBeenCalledTimes(1) - expect(navigateMock).toHaveBeenCalledWith(METRICS_PAGE_ROUTE) - }) + setup(); + await userEvent.click(screen.getByText(CREATE_NEW_PROJECT)); + expect(navigateMock).toHaveBeenCalledTimes(1); + expect(navigateMock).toHaveBeenCalledWith(METRICS_PAGE_ROUTE); + }); describe('isValidImportedConfig', () => { it('should show warning message when no projectName dateRange metrics all exist', async () => { - const emptyConfig = {} - const queryByText = await setupInputFile(emptyConfig) + const emptyConfig = {}; + const queryByText = await setupInputFile(emptyConfig); await waitFor(() => { - expect(mockedUseAppDispatch).toHaveBeenCalledTimes(0) - expect(queryByText(HOME_VERIFY_IMPORT_WARNING_MESSAGE)).toBeInTheDocument() - }) - }) + expect(mockedUseAppDispatch).toHaveBeenCalledTimes(0); + expect(queryByText(HOME_VERIFY_IMPORT_WARNING_MESSAGE)).toBeInTheDocument(); + }); + }); it('should no display warning message when projectName dateRange metrics all exist', async () => { - const queryByText = await setupInputFile(IMPORTED_NEW_CONFIG_FIXTURE) + const queryByText = await setupInputFile(IMPORTED_NEW_CONFIG_FIXTURE); await waitFor(() => { - expect(mockedUseAppDispatch).toHaveBeenCalledTimes(0) - expect(queryByText(HOME_VERIFY_IMPORT_WARNING_MESSAGE)).not.toBeInTheDocument() - }) - }) + expect(mockedUseAppDispatch).toHaveBeenCalledTimes(0); + expect(queryByText(HOME_VERIFY_IMPORT_WARNING_MESSAGE)).not.toBeInTheDocument(); + }); + }); it.each([ ['projectName', { projectName: '', metrics: [], dateRange: {} }], @@ -125,12 +125,12 @@ describe('HomeGuide', () => { ['endDate', { projectName: '', metrics: [], dateRange: { startDate: '', endDate: '2023-02-01' } }], ['metrics', { projectName: '', metrics: ['Metric 1', 'Metric 2'], dateRange: {} }], ])('should not display warning message when only %s exists', async (_, validConfig) => { - const queryByText = await setupInputFile(validConfig) + const queryByText = await setupInputFile(validConfig); await waitFor(() => { - expect(mockedUseAppDispatch).toHaveBeenCalledTimes(0) - expect(queryByText(HOME_VERIFY_IMPORT_WARNING_MESSAGE)).not.toBeInTheDocument() - }) - }) - }) -}) + expect(mockedUseAppDispatch).toHaveBeenCalledTimes(0); + expect(queryByText(HOME_VERIFY_IMPORT_WARNING_MESSAGE)).not.toBeInTheDocument(); + }); + }); + }); +}); diff --git a/frontend/__tests__/src/components/Loading/Loading.test.tsx b/frontend/__tests__/src/components/Loading/Loading.test.tsx index f88d0eed99..60218db399 100644 --- a/frontend/__tests__/src/components/Loading/Loading.test.tsx +++ b/frontend/__tests__/src/components/Loading/Loading.test.tsx @@ -1,17 +1,18 @@ -import { render } from '@testing-library/react' -import { Loading } from '@src/components/Loading' +import React from 'react'; +import { render } from '@testing-library/react'; +import { Loading } from '@src/components/Loading'; describe('Loading', () => { it('should show Loading', () => { - const { container } = render() + const { container } = render(); - expect(container.getElementsByTagName('svg')).toHaveLength(1) - expect(container.getElementsByTagName('span')[0].getAttribute('role')).toEqual('progressbar') - }) + expect(container.getElementsByTagName('svg')).toHaveLength(1); + expect(container.getElementsByTagName('span')[0].getAttribute('role')).toEqual('progressbar'); + }); it('should show Loading message when has message', () => { - const { getByText } = render() + const { getByText } = render(); - expect(getByText('loading...')).toBeInTheDocument() - }) -}) + expect(getByText('loading...')).toBeInTheDocument(); + }); +}); diff --git a/frontend/__tests__/src/components/Metrics/ConfigStep/Board.test.tsx b/frontend/__tests__/src/components/Metrics/ConfigStep/Board.test.tsx index 596ebd7179..6733406a3d 100644 --- a/frontend/__tests__/src/components/Metrics/ConfigStep/Board.test.tsx +++ b/frontend/__tests__/src/components/Metrics/ConfigStep/Board.test.tsx @@ -1,5 +1,6 @@ -import { fireEvent, render, screen, waitFor, within } from '@testing-library/react' -import { Board } from '@src/components/Metrics/ConfigStep/Board' +import React from 'react'; +import { fireEvent, render, screen, waitFor, within } from '@testing-library/react'; +import { Board } from '@src/components/Metrics/ConfigStep/Board'; import { BOARD_FIELDS, BOARD_TYPES, @@ -12,221 +13,221 @@ import { VERIFY, VERIFY_ERROR_MESSAGE, VERIFY_FAILED, -} from '../../../fixtures' -import { Provider } from 'react-redux' -import { setupStore } from '../../../utils/setupStoreUtil' -import { setupServer } from 'msw/node' -import { rest } from 'msw' -import { HttpStatusCode } from 'axios' +} from '../../../fixtures'; +import { Provider } from 'react-redux'; +import { setupStore } from '../../../utils/setupStoreUtil'; +import { setupServer } from 'msw/node'; +import { rest } from 'msw'; +import { HttpStatusCode } from 'axios'; export const fillBoardFieldsInformation = () => { - const fields = ['Board Id', 'Email', 'Project Key', 'Site', 'Token'] - const mockInfo = ['2', 'mockEmail@qq.com', 'mockKey', '1', 'mockToken'] - const fieldInputs = fields.map((label) => screen.getByTestId(label).querySelector('input') as HTMLInputElement) + const fields = ['Board Id', 'Email', 'Project Key', 'Site', 'Token']; + const mockInfo = ['2', 'mockEmail@qq.com', 'mockKey', '1', 'mockToken']; + const fieldInputs = fields.map((label) => screen.getByTestId(label).querySelector('input') as HTMLInputElement); fieldInputs.map((input, index) => { - fireEvent.change(input, { target: { value: mockInfo[index] } }) - }) + fireEvent.change(input, { target: { value: mockInfo[index] } }); + }); fieldInputs.map((input, index) => { - expect(input.value).toEqual(mockInfo[index]) - }) -} + expect(input.value).toEqual(mockInfo[index]); + }); +}; -let store = null +let store = null; -const server = setupServer(rest.post(MOCK_BOARD_URL_FOR_JIRA, (req, res, ctx) => res(ctx.status(200)))) +const server = setupServer(rest.post(MOCK_BOARD_URL_FOR_JIRA, (req, res, ctx) => res(ctx.status(200)))); describe('Board', () => { - beforeAll(() => server.listen()) - afterAll(() => server.close()) + beforeAll(() => server.listen()); + afterAll(() => server.close()); - store = setupStore() + store = setupStore(); const setup = () => { - store = setupStore() + store = setupStore(); return render( - ) - } + ); + }; afterEach(() => { - store = null - }) + store = null; + }); it('should show board title and fields when render board component ', () => { - const { getByLabelText, getAllByText } = setup() + setup(); BOARD_FIELDS.map((field) => { - expect(getByLabelText(`${field} *`)).toBeInTheDocument() - }) - expect(getAllByText(CONFIG_TITLE.BOARD)[0]).toBeInTheDocument() - }) + expect(screen.getByLabelText(`${field} *`)).toBeInTheDocument(); + }); + expect(screen.getAllByText(CONFIG_TITLE.BOARD)[0]).toBeInTheDocument(); + }); it('should show default value jira when init board component', () => { - const { getByText, queryByText } = setup() - const boardType = getByText(BOARD_TYPES.JIRA) + setup(); + const boardType = screen.getByText(BOARD_TYPES.JIRA); - expect(boardType).toBeInTheDocument() + expect(boardType).toBeInTheDocument(); - const option = queryByText(BOARD_TYPES.CLASSIC_JIRA) - expect(option).not.toBeTruthy() - }) + const option = screen.queryByText(BOARD_TYPES.CLASSIC_JIRA); + expect(option).not.toBeTruthy(); + }); it('should show detail options when click board field', () => { - const { getByRole } = setup() - fireEvent.mouseDown(getByRole('button', { name: CONFIG_TITLE.BOARD })) - const listBox = within(getByRole('listbox')) - const options = listBox.getAllByRole('option') - const optionValue = options.map((li) => li.getAttribute('data-value')) + setup(); + fireEvent.mouseDown(screen.getByRole('button', { name: CONFIG_TITLE.BOARD })); + const listBox = within(screen.getByRole('listbox')); + const options = listBox.getAllByRole('option'); + const optionValue = options.map((li) => li.getAttribute('data-value')); - expect(optionValue).toEqual(Object.values(BOARD_TYPES)) - }) + expect(optionValue).toEqual(Object.values(BOARD_TYPES)); + }); it('should show board type when select board field value ', async () => { - const { getByRole, getByText } = setup() + setup(); - fireEvent.mouseDown(getByRole('button', { name: CONFIG_TITLE.BOARD })) - fireEvent.click(getByText(BOARD_TYPES.CLASSIC_JIRA)) + fireEvent.mouseDown(screen.getByRole('button', { name: CONFIG_TITLE.BOARD })); + fireEvent.click(screen.getByText(BOARD_TYPES.CLASSIC_JIRA)); await waitFor(() => { - expect(getByText(BOARD_TYPES.CLASSIC_JIRA)).toBeInTheDocument() - }) - }) + expect(screen.getByText(BOARD_TYPES.CLASSIC_JIRA)).toBeInTheDocument(); + }); + }); it('should show error message when input a wrong type or empty email ', async () => { - const { getByTestId, getByText } = setup() - const EMAil_INVALID_ERROR_MESSAGE = 'Email is invalid' - const emailInput = getByTestId('Email').querySelector('input') as HTMLInputElement + setup(); + const EMAil_INVALID_ERROR_MESSAGE = 'Email is invalid'; + const emailInput = screen.getByTestId('Email').querySelector('input') as HTMLInputElement; - fireEvent.change(emailInput, { target: { value: 'wrong type email' } }) + fireEvent.change(emailInput, { target: { value: 'wrong type email' } }); - expect(getByText(EMAil_INVALID_ERROR_MESSAGE)).toBeVisible() - expect(getByText(EMAil_INVALID_ERROR_MESSAGE)).toHaveStyle(ERROR_MESSAGE_COLOR) + expect(screen.getByText(EMAil_INVALID_ERROR_MESSAGE)).toBeVisible(); + expect(screen.getByText(EMAil_INVALID_ERROR_MESSAGE)).toHaveStyle(ERROR_MESSAGE_COLOR); - fireEvent.change(emailInput, { target: { value: '' } }) + fireEvent.change(emailInput, { target: { value: '' } }); - const EMAIL_REQUIRE_ERROR_MESSAGE = 'Email is required' - expect(getByText(EMAIL_REQUIRE_ERROR_MESSAGE)).toBeVisible() - }) + const EMAIL_REQUIRE_ERROR_MESSAGE = 'Email is required'; + expect(screen.getByText(EMAIL_REQUIRE_ERROR_MESSAGE)).toBeVisible(); + }); it('should clear other fields information when change board field selection', () => { - const { getByRole, getByText } = setup() - const boardIdInput = getByRole('textbox', { + setup(); + const boardIdInput = screen.getByRole('textbox', { name: 'Board Id', - }) as HTMLInputElement - const emailInput = getByRole('textbox', { + }) as HTMLInputElement; + const emailInput = screen.getByRole('textbox', { name: 'Email', - }) as HTMLInputElement + }) as HTMLInputElement; - fireEvent.change(boardIdInput, { target: { value: 2 } }) - fireEvent.change(emailInput, { target: { value: 'mockEmail@qq.com' } }) - fireEvent.mouseDown(getByRole('button', { name: CONFIG_TITLE.BOARD })) - fireEvent.click(getByText(BOARD_TYPES.CLASSIC_JIRA)) + fireEvent.change(boardIdInput, { target: { value: 2 } }); + fireEvent.change(emailInput, { target: { value: 'mockEmail@qq.com' } }); + fireEvent.mouseDown(screen.getByRole('button', { name: CONFIG_TITLE.BOARD })); + fireEvent.click(screen.getByText(BOARD_TYPES.CLASSIC_JIRA)); - expect(emailInput.value).toEqual('') - expect(boardIdInput.value).toEqual('') - }) + expect(emailInput.value).toEqual(''); + expect(boardIdInput.value).toEqual(''); + }); it('should clear all fields information when click reset button', async () => { - const { getByRole, getByText, queryByRole } = setup() + setup(); const fieldInputs = BOARD_FIELDS.slice(1, 5).map( (label) => screen.getByRole('textbox', { name: label, hidden: true, }) as HTMLInputElement - ) - fillBoardFieldsInformation() + ); + fillBoardFieldsInformation(); - fireEvent.click(getByText(VERIFY)) + fireEvent.click(screen.getByText(VERIFY)); await waitFor(() => { - fireEvent.click(getByRole('button', { name: RESET })) - }) + fireEvent.click(screen.getByRole('button', { name: RESET })); + }); fieldInputs.map((input) => { - expect(input.value).toEqual('') - }) - expect(getByText(BOARD_TYPES.JIRA)).toBeInTheDocument() - expect(queryByRole('button', { name: RESET })).not.toBeTruthy() - expect(queryByRole('button', { name: VERIFY })).toBeDisabled() - }) + expect(input.value).toEqual(''); + }); + expect(screen.getByText(BOARD_TYPES.JIRA)).toBeInTheDocument(); + expect(screen.queryByRole('button', { name: RESET })).not.toBeTruthy(); + expect(screen.queryByRole('button', { name: VERIFY })).toBeDisabled(); + }); it('should enabled verify button when all fields checked correctly given disable verify button', () => { - const { getByRole } = setup() - const verifyButton = getByRole('button', { name: VERIFY }) + setup(); + const verifyButton = screen.getByRole('button', { name: VERIFY }); - expect(verifyButton).toBeDisabled() + expect(verifyButton).toBeDisabled(); - fillBoardFieldsInformation() + fillBoardFieldsInformation(); - expect(verifyButton).toBeEnabled() - }) + expect(verifyButton).toBeEnabled(); + }); it('should show reset button and verified button when verify succeed ', async () => { - const { getByText } = setup() - fillBoardFieldsInformation() + setup(); + fillBoardFieldsInformation(); - fireEvent.click(getByText(VERIFY)) + fireEvent.click(screen.getByText(VERIFY)); await waitFor(() => { - expect(getByText(RESET)).toBeVisible() - }) + expect(screen.getByText(RESET)).toBeVisible(); + }); await waitFor(() => { - expect(getByText(VERIFIED)).toBeTruthy() - }) - }) + expect(screen.getByText(VERIFIED)).toBeTruthy(); + }); + }); it('should called verifyBoard method once when click verify button', async () => { - const { getByRole, getByText } = setup() - fillBoardFieldsInformation() - fireEvent.click(getByRole('button', { name: VERIFY })) + setup(); + fillBoardFieldsInformation(); + fireEvent.click(screen.getByRole('button', { name: VERIFY })); await waitFor(() => { - expect(getByText('Verified')).toBeInTheDocument() - }) - }) + expect(screen.getByText('Verified')).toBeInTheDocument(); + }); + }); it('should check loading animation when click verify button', async () => { - const { getByRole, container } = setup() - fillBoardFieldsInformation() - fireEvent.click(getByRole('button', { name: VERIFY })) + const { container } = setup(); + fillBoardFieldsInformation(); + fireEvent.click(screen.getByRole('button', { name: VERIFY })); await waitFor(() => { - expect(container.getElementsByTagName('span')[0].getAttribute('role')).toEqual('progressbar') - }) - }) + expect(container.getElementsByTagName('span')[0].getAttribute('role')).toEqual('progressbar'); + }); + }); it('should check noCardPop show and disappear when board verify response status is 204', async () => { - server.use(rest.post(MOCK_BOARD_URL_FOR_JIRA, (req, res, ctx) => res(ctx.status(HttpStatusCode.NoContent)))) - const { getByText, getByRole } = setup() - fillBoardFieldsInformation() + server.use(rest.post(MOCK_BOARD_URL_FOR_JIRA, (req, res, ctx) => res(ctx.status(HttpStatusCode.NoContent)))); + setup(); + fillBoardFieldsInformation(); - fireEvent.click(getByRole('button', { name: VERIFY })) + fireEvent.click(screen.getByRole('button', { name: VERIFY })); await waitFor(() => { - expect(getByText(NO_CARD_ERROR_MESSAGE)).toBeInTheDocument() - }) + expect(screen.getByText(NO_CARD_ERROR_MESSAGE)).toBeInTheDocument(); + }); - fireEvent.click(getByRole('button', { name: 'Ok' })) - expect(getByText(NO_CARD_ERROR_MESSAGE)).not.toBeVisible() - }) + fireEvent.click(screen.getByRole('button', { name: 'Ok' })); + expect(screen.getByText(NO_CARD_ERROR_MESSAGE)).not.toBeVisible(); + }); it('should check error notification show and disappear when board verify response status is 401', async () => { server.use( rest.post(MOCK_BOARD_URL_FOR_JIRA, (req, res, ctx) => res(ctx.status(HttpStatusCode.Unauthorized), ctx.json({ hintInfo: VERIFY_ERROR_MESSAGE.UNAUTHORIZED })) ) - ) - const { getByText, getByRole } = setup() - fillBoardFieldsInformation() + ); + setup(); + fillBoardFieldsInformation(); - fireEvent.click(getByRole('button', { name: VERIFY })) + fireEvent.click(screen.getByRole('button', { name: VERIFY })); await waitFor(() => { expect( - getByText(`${BOARD_TYPES.JIRA} ${VERIFY_FAILED}: ${VERIFY_ERROR_MESSAGE.UNAUTHORIZED}`) - ).toBeInTheDocument() - }) - }) -}) + screen.getByText(`${BOARD_TYPES.JIRA} ${VERIFY_FAILED}: ${VERIFY_ERROR_MESSAGE.UNAUTHORIZED}`) + ).toBeInTheDocument(); + }); + }); +}); diff --git a/frontend/__tests__/src/components/Metrics/ConfigStep/BranchSelection.test.tsx b/frontend/__tests__/src/components/Metrics/ConfigStep/BranchSelection.test.tsx index 1f9fbf4aba..83060686bb 100644 --- a/frontend/__tests__/src/components/Metrics/ConfigStep/BranchSelection.test.tsx +++ b/frontend/__tests__/src/components/Metrics/ConfigStep/BranchSelection.test.tsx @@ -1,15 +1,16 @@ -import { act, render } from '@testing-library/react' -import { BranchSelection } from '@src/components/Metrics/ConfigStep/BranchSelection' -import { ALL, BRANCH, MOCK_AUTOCOMPLETE_LIST } from '../../../fixtures' -import { setupStore } from '../../../utils/setupStoreUtil' -import { Provider } from 'react-redux' -import userEvent from '@testing-library/user-event' +import React from 'react'; +import { act, render, screen } from '@testing-library/react'; +import { BranchSelection } from '@src/components/Metrics/ConfigStep/BranchSelection'; +import { ALL, BRANCH, MOCK_AUTOCOMPLETE_LIST } from '../../../fixtures'; +import { setupStore } from '../../../utils/setupStoreUtil'; +import { Provider } from 'react-redux'; +import userEvent from '@testing-library/user-event'; describe('BranchSelection', () => { - let store = null - const onUpdatePipeline = jest.fn() + let store = null; + const onUpdatePipeline = jest.fn(); const setup = () => { - store = setupStore() + store = setupStore(); const pipelineSetting = { id: 2, @@ -17,48 +18,48 @@ describe('BranchSelection', () => { pipelineName: 'Test', step: 1, branches: MOCK_AUTOCOMPLETE_LIST, - } + }; return render( - ) - } + ); + }; it('should show Branches when render BranchSelection component', () => { - const { getByText } = setup() + const { getByText } = setup(); - expect(getByText('Branches')).toBeInTheDocument() - }) + expect(getByText('Branches')).toBeInTheDocument(); + }); it('should has Option 2 when render BranchSelection component', async () => { - const { getByRole } = setup() + setup(); - expect(getByRole('button', { name: 'Option 2' })).toBeVisible() - }) + expect(screen.getByRole('button', { name: 'Option 2' })).toBeVisible(); + }); it('should show branches selection when getSteps succeed ', async () => { - const { getByRole, getByText } = setup() + setup(); - expect(getByText(BRANCH)).toBeInTheDocument() + expect(screen.getByText(BRANCH)).toBeInTheDocument(); await act(async () => { - await userEvent.click(getByRole('combobox', { name: 'Branches' })) - }) + await userEvent.click(screen.getByRole('combobox', { name: 'Branches' })); + }); - const allOption = getByRole('option', { name: ALL }) + const allOption = screen.getByRole('option', { name: ALL }); await act(async () => { - await userEvent.click(allOption) - }) + await userEvent.click(allOption); + }); - const optionOne = getByRole('button', { name: 'Option 1' }) + const optionOne = screen.getByRole('button', { name: 'Option 1' }); - expect(optionOne).toBeVisible() + expect(optionOne).toBeVisible(); await act(async () => { - await userEvent.click(optionOne) - }) + await userEvent.click(optionOne); + }); - expect(onUpdatePipeline).toHaveBeenCalledTimes(1) - }) -}) + expect(onUpdatePipeline).toHaveBeenCalledTimes(1); + }); +}); diff --git a/frontend/__tests__/src/components/Metrics/ConfigStep/ConfigStep.test.tsx b/frontend/__tests__/src/components/Metrics/ConfigStep/ConfigStep.test.tsx index e2c9bde7a7..f776d34184 100644 --- a/frontend/__tests__/src/components/Metrics/ConfigStep/ConfigStep.test.tsx +++ b/frontend/__tests__/src/components/Metrics/ConfigStep/ConfigStep.test.tsx @@ -1,5 +1,6 @@ -import { act, fireEvent, Matcher, render, waitFor, within } from '@testing-library/react' -import ConfigStep from '@src/components/Metrics/ConfigStep' +import React from 'react'; +import { act, fireEvent, Matcher, render, waitFor, within, screen } from '@testing-library/react'; +import ConfigStep from '@src/components/Metrics/ConfigStep'; import { CHINA_CALENDAR, CONFIG_TITLE, @@ -12,176 +13,176 @@ import { TEST_PROJECT_NAME, VELOCITY, VERIFY, -} from '../../../fixtures' -import { Provider } from 'react-redux' -import { setupStore } from '../../../utils/setupStoreUtil' -import dayjs from 'dayjs' -import { fillBoardFieldsInformation } from './Board.test' +} from '../../../fixtures'; +import { Provider } from 'react-redux'; +import { setupStore } from '../../../utils/setupStoreUtil'; +import dayjs from 'dayjs'; +import { fillBoardFieldsInformation } from './Board.test'; -let store = null +let store = null; jest.mock('@src/context/config/configSlice', () => ({ ...jest.requireActual('@src/context/config/configSlice'), selectWarningMessage: jest.fn().mockReturnValue('Test warning Message'), -})) +})); describe('ConfigStep', () => { const setup = () => { - store = setupStore() + store = setupStore(); return render( - ) - } + ); + }; beforeEach(() => { - jest.useFakeTimers() - }) + jest.useFakeTimers(); + }); afterEach(() => { - store = null - jest.clearAllMocks() - jest.useRealTimers() - }) + store = null; + jest.clearAllMocks(); + jest.useRealTimers(); + }); it('should show project name when render configStep', () => { - const { getByText } = setup() + setup(); - expect(getByText(PROJECT_NAME_LABEL)).toBeInTheDocument() - }) + expect(screen.getByText(PROJECT_NAME_LABEL)).toBeInTheDocument(); + }); it('should show project name when input some letters', () => { - const { getByRole, getByDisplayValue } = setup() + setup(); const hasInputValue = (e: HTMLElement, inputValue: Matcher) => { - return getByDisplayValue(inputValue) === e - } - const input = getByRole('textbox', { name: PROJECT_NAME_LABEL }) + return screen.getByDisplayValue(inputValue) === e; + }; + const input = screen.getByRole('textbox', { name: PROJECT_NAME_LABEL }); - expect(input).toBeInTheDocument() + expect(input).toBeInTheDocument(); - fireEvent.change(input, { target: { value: TEST_PROJECT_NAME } }) + fireEvent.change(input, { target: { value: TEST_PROJECT_NAME } }); - expect(hasInputValue(input, TEST_PROJECT_NAME)).toBe(true) - }) + expect(hasInputValue(input, TEST_PROJECT_NAME)).toBe(true); + }); it('should show error message when project name is Empty', () => { - const { getByRole, getByText } = setup() - const input = getByRole('textbox', { name: PROJECT_NAME_LABEL }) + setup(); + const input = screen.getByRole('textbox', { name: PROJECT_NAME_LABEL }); - fireEvent.change(input, { target: { value: TEST_PROJECT_NAME } }) - fireEvent.change(input, { target: { value: '' } }) + fireEvent.change(input, { target: { value: TEST_PROJECT_NAME } }); + fireEvent.change(input, { target: { value: '' } }); - expect(getByText('Project name is required')).toBeInTheDocument() - }) + expect(screen.getByText('Project name is required')).toBeInTheDocument(); + }); it('should show error message when click project name input with no letter', () => { - const { getByRole, getByText } = setup() - const input = getByRole('textbox', { name: PROJECT_NAME_LABEL }) + setup(); + const input = screen.getByRole('textbox', { name: PROJECT_NAME_LABEL }); - fireEvent.focus(input) + fireEvent.focus(input); - expect(getByText('Project name is required')).toBeInTheDocument() - }) + expect(screen.getByText('Project name is required')).toBeInTheDocument(); + }); it('should select Regular calendar by default when rendering the radioGroup', () => { - const { getByRole } = setup() - const defaultValue = getByRole('radio', { name: REGULAR_CALENDAR }) - const chinaCalendar = getByRole('radio', { name: CHINA_CALENDAR }) + setup(); + const defaultValue = screen.getByRole('radio', { name: REGULAR_CALENDAR }); + const chinaCalendar = screen.getByRole('radio', { name: CHINA_CALENDAR }); - expect(defaultValue).toBeChecked() - expect(chinaCalendar).not.toBeChecked() - }) + expect(defaultValue).toBeChecked(); + expect(chinaCalendar).not.toBeChecked(); + }); it('should switch the radio when any radioLabel is selected', () => { - const { getByRole } = setup() - const chinaCalendar = getByRole('radio', { name: CHINA_CALENDAR }) - const regularCalendar = getByRole('radio', { name: REGULAR_CALENDAR }) - fireEvent.click(chinaCalendar) + setup(); + const chinaCalendar = screen.getByRole('radio', { name: CHINA_CALENDAR }); + const regularCalendar = screen.getByRole('radio', { name: REGULAR_CALENDAR }); + fireEvent.click(chinaCalendar); - expect(chinaCalendar).toBeChecked() - expect(regularCalendar).not.toBeChecked() + expect(chinaCalendar).toBeChecked(); + expect(regularCalendar).not.toBeChecked(); - fireEvent.click(regularCalendar) + fireEvent.click(regularCalendar); - expect(regularCalendar).toBeChecked() - expect(chinaCalendar).not.toBeChecked() - }) + expect(regularCalendar).toBeChecked(); + expect(chinaCalendar).not.toBeChecked(); + }); it('should not show board component when init ConfigStep component ', async () => { - const { queryByText } = setup() + setup(); await waitFor(() => { - expect(queryByText(CONFIG_TITLE.BOARD)).toBeNull() - }) - }) + expect(screen.queryByText(CONFIG_TITLE.BOARD)).toBeNull(); + }); + }); it('should show board component when MetricsTypeCheckbox select Velocity,Cycle time', () => { - const { getByRole, getAllByText } = setup() + setup(); - fireEvent.mouseDown(getByRole('button', { name: REQUIRED_DATA })) - const requireDateSelection = within(getByRole('listbox')) - fireEvent.click(requireDateSelection.getByRole('option', { name: VELOCITY })) - fireEvent.click(requireDateSelection.getByRole('option', { name: CYCLE_TIME })) + fireEvent.mouseDown(screen.getByRole('button', { name: REQUIRED_DATA })); + const requireDateSelection = within(screen.getByRole('listbox')); + fireEvent.click(requireDateSelection.getByRole('option', { name: VELOCITY })); + fireEvent.click(requireDateSelection.getByRole('option', { name: CYCLE_TIME })); - expect(getAllByText(CONFIG_TITLE.BOARD)[0]).toBeInTheDocument() - }) + expect(screen.getAllByText(CONFIG_TITLE.BOARD)[0]).toBeInTheDocument(); + }); it('should show board component when MetricsTypeCheckbox select Classification, ', () => { - const { getByRole, getAllByText } = setup() + setup(); - fireEvent.mouseDown(getByRole('button', { name: REQUIRED_DATA })) - const requireDateSelection = within(getByRole('listbox')) - fireEvent.click(requireDateSelection.getByRole('option', { name: 'Classification' })) + fireEvent.mouseDown(screen.getByRole('button', { name: REQUIRED_DATA })); + const requireDateSelection = within(screen.getByRole('listbox')); + fireEvent.click(requireDateSelection.getByRole('option', { name: 'Classification' })); - expect(getAllByText(CONFIG_TITLE.BOARD)[0]).toBeInTheDocument() - }) + expect(screen.getAllByText(CONFIG_TITLE.BOARD)[0]).toBeInTheDocument(); + }); it('should verify again when calendar type is changed given board fields are filled and verified', () => { - const { getByRole, getByText, queryByText } = setup() + setup(); - fireEvent.mouseDown(getByRole('button', { name: REQUIRED_DATA })) - const requireDateSelection = within(getByRole('listbox')) - fireEvent.click(requireDateSelection.getByRole('option', { name: VELOCITY })) - fillBoardFieldsInformation() - fireEvent.click(getByText(VERIFY)) - fireEvent.click(getByText(CHINA_CALENDAR)) + fireEvent.mouseDown(screen.getByRole('button', { name: REQUIRED_DATA })); + const requireDateSelection = within(screen.getByRole('listbox')); + fireEvent.click(requireDateSelection.getByRole('option', { name: VELOCITY })); + fillBoardFieldsInformation(); + fireEvent.click(screen.getByText(VERIFY)); + fireEvent.click(screen.getByText(CHINA_CALENDAR)); - expect(queryByText(VERIFY)).toBeVisible() - expect(queryByText('Verified')).toBeNull() - expect(queryByText(RESET)).toBeNull() - }) + expect(screen.queryByText(VERIFY)).toBeVisible(); + expect(screen.queryByText('Verified')).toBeNull(); + expect(screen.queryByText(RESET)).toBeNull(); + }); it('should verify again when date picker is changed given board fields are filled and verified', () => { - const { getByRole, getByText, queryByText, getByLabelText } = setup() - const today = dayjs().format('MM/DD/YYYY') - const startDateInput = getByLabelText('From *') - - fireEvent.mouseDown(getByRole('button', { name: REQUIRED_DATA })) - const requireDateSelection = within(getByRole('listbox')) - fireEvent.click(requireDateSelection.getByRole('option', { name: VELOCITY })) - fillBoardFieldsInformation() - fireEvent.click(getByText(VERIFY)) - fireEvent.change(startDateInput, { target: { value: today } }) - - expect(queryByText(VERIFY)).toBeVisible() - expect(queryByText('Verified')).toBeNull() - expect(queryByText(RESET)).toBeNull() - }) + setup(); + const today = dayjs().format('MM/DD/YYYY'); + const startDateInput = screen.getByLabelText('From *'); + + fireEvent.mouseDown(screen.getByRole('button', { name: REQUIRED_DATA })); + const requireDateSelection = within(screen.getByRole('listbox')); + fireEvent.click(requireDateSelection.getByRole('option', { name: VELOCITY })); + fillBoardFieldsInformation(); + fireEvent.click(screen.getByText(VERIFY)); + fireEvent.change(startDateInput, { target: { value: today } }); + + expect(screen.queryByText(VERIFY)).toBeVisible(); + expect(screen.queryByText('Verified')).toBeNull(); + expect(screen.queryByText(RESET)).toBeNull(); + }); it('should show warning message when selectWarningMessage has a value', async () => { - const { getByText } = setup() + setup(); - expect(getByText('Test warning Message')).toBeVisible() - }) + expect(screen.getByText('Test warning Message')).toBeVisible(); + }); it('should show disable warning message When selectWarningMessage has a value after two seconds', async () => { - const { queryByText } = setup() + setup(); act(() => { - jest.advanceTimersByTime(ERROR_MESSAGE_TIME_DURATION) - }) + jest.advanceTimersByTime(ERROR_MESSAGE_TIME_DURATION); + }); await waitFor(() => { - expect(queryByText('Test warning Message')).not.toBeInTheDocument() - }) - }) -}) + expect(screen.queryByText('Test warning Message')).not.toBeInTheDocument(); + }); + }); +}); diff --git a/frontend/__tests__/src/components/Metrics/ConfigStep/DateRangePicker.test.tsx b/frontend/__tests__/src/components/Metrics/ConfigStep/DateRangePicker.test.tsx index accf085bdb..490ee1d2ec 100644 --- a/frontend/__tests__/src/components/Metrics/ConfigStep/DateRangePicker.test.tsx +++ b/frontend/__tests__/src/components/Metrics/ConfigStep/DateRangePicker.test.tsx @@ -1,77 +1,78 @@ -import { fireEvent, render } from '@testing-library/react' -import { DateRangePicker } from '@src/components/Metrics/ConfigStep/DateRangePicker' -import { ERROR_DATE } from '../../../fixtures' -import dayjs from 'dayjs' -import { Provider } from 'react-redux' -import { setupStore } from '../../../utils/setupStoreUtil' - -const START_DATE_LABEL = 'From *' -const END_DATE_LABEL = 'To *' -const TODAY = dayjs() -const INPUT_DATE_VALUE = TODAY.format('MM/DD/YYYY') -let store = setupStore() +import React from 'react'; +import { fireEvent, render, screen } from '@testing-library/react'; +import { DateRangePicker } from '@src/components/Metrics/ConfigStep/DateRangePicker'; +import { ERROR_DATE } from '../../../fixtures'; +import dayjs from 'dayjs'; +import { Provider } from 'react-redux'; +import { setupStore } from '../../../utils/setupStoreUtil'; + +const START_DATE_LABEL = 'From *'; +const END_DATE_LABEL = 'To *'; +const TODAY = dayjs(); +const INPUT_DATE_VALUE = TODAY.format('MM/DD/YYYY'); +let store = setupStore(); const setup = () => { - store = setupStore() + store = setupStore(); return render( - ) -} + ); +}; describe('DateRangePicker', () => { const expectDate = (inputDate: HTMLInputElement) => { - expect(inputDate.value).toEqual(expect.stringContaining(TODAY.date().toString())) - expect(inputDate.value).toEqual(expect.stringContaining((TODAY.month() + 1).toString())) - expect(inputDate.value).toEqual(expect.stringContaining(TODAY.year().toString())) - } + expect(inputDate.value).toEqual(expect.stringContaining(TODAY.date().toString())); + expect(inputDate.value).toEqual(expect.stringContaining((TODAY.month() + 1).toString())); + expect(inputDate.value).toEqual(expect.stringContaining(TODAY.year().toString())); + }; it('should render DateRangePicker', () => { - const { queryAllByText } = setup() + setup(); - expect(queryAllByText(START_DATE_LABEL)).toHaveLength(1) - expect(queryAllByText(END_DATE_LABEL)).toHaveLength(1) - }) + expect(screen.queryAllByText(START_DATE_LABEL)).toHaveLength(1); + expect(screen.queryAllByText(END_DATE_LABEL)).toHaveLength(1); + }); it('should show right start date when input a valid date given init start date is null ', () => { - const { getByRole } = setup() - const startDateInput = getByRole('textbox', { name: START_DATE_LABEL }) as HTMLInputElement - fireEvent.change(startDateInput, { target: { value: INPUT_DATE_VALUE } }) + setup(); + const startDateInput = screen.getByRole('textbox', { name: START_DATE_LABEL }) as HTMLInputElement; + fireEvent.change(startDateInput, { target: { value: INPUT_DATE_VALUE } }); - expectDate(startDateInput) - }) + expectDate(startDateInput); + }); it('should show right end date when input a valid date given init end date is null ', () => { - const { getByRole } = setup() - const endDateInput = getByRole('textbox', { name: END_DATE_LABEL }) as HTMLInputElement + setup(); + const endDateInput = screen.getByRole('textbox', { name: END_DATE_LABEL }) as HTMLInputElement; - fireEvent.change(endDateInput, { target: { value: INPUT_DATE_VALUE } }) + fireEvent.change(endDateInput, { target: { value: INPUT_DATE_VALUE } }); - expectDate(endDateInput) - }) + expectDate(endDateInput); + }); it('should Auto-fill endDate which is after startDate 13 days when fill right startDate ', () => { - const { getByRole } = setup() - const endDate = TODAY.add(13, 'day') - const startDateInput = getByRole('textbox', { name: START_DATE_LABEL }) as HTMLInputElement - const endDateInput = getByRole('textbox', { name: END_DATE_LABEL }) as HTMLInputElement + setup(); + const endDate = TODAY.add(13, 'day'); + const startDateInput = screen.getByRole('textbox', { name: START_DATE_LABEL }) as HTMLInputElement; + const endDateInput = screen.getByRole('textbox', { name: END_DATE_LABEL }) as HTMLInputElement; - fireEvent.change(startDateInput, { target: { value: INPUT_DATE_VALUE } }) + fireEvent.change(startDateInput, { target: { value: INPUT_DATE_VALUE } }); - expect(endDateInput.value).toEqual(expect.stringContaining(endDate.date().toString())) - expect(endDateInput.value).toEqual(expect.stringContaining((endDate.month() + 1).toString())) - expect(endDateInput.value).toEqual(expect.stringContaining(endDate.year().toString())) - }) + expect(endDateInput.value).toEqual(expect.stringContaining(endDate.date().toString())); + expect(endDateInput.value).toEqual(expect.stringContaining((endDate.month() + 1).toString())); + expect(endDateInput.value).toEqual(expect.stringContaining(endDate.year().toString())); + }); it('should not Auto-fill endDate which is after startDate 14 days when fill wrong format startDate ', () => { - const { getByRole } = setup() - const startDateInput = getByRole('textbox', { name: START_DATE_LABEL }) as HTMLInputElement - const endDateInput = getByRole('textbox', { name: END_DATE_LABEL }) as HTMLInputElement + setup(); + const startDateInput = screen.getByRole('textbox', { name: START_DATE_LABEL }) as HTMLInputElement; + const endDateInput = screen.getByRole('textbox', { name: END_DATE_LABEL }) as HTMLInputElement; - fireEvent.change(startDateInput, { target: { value: ERROR_DATE } }) + fireEvent.change(startDateInput, { target: { value: ERROR_DATE } }); - expect(startDateInput.valueAsDate).toEqual(null) - expect(endDateInput.valueAsDate).toEqual(null) - }) -}) + expect(startDateInput.valueAsDate).toEqual(null); + expect(endDateInput.valueAsDate).toEqual(null); + }); +}); diff --git a/frontend/__tests__/src/components/Metrics/ConfigStep/MetricsTypeCheckbox.test.tsx b/frontend/__tests__/src/components/Metrics/ConfigStep/MetricsTypeCheckbox.test.tsx index c921be903a..eddb7a7239 100644 --- a/frontend/__tests__/src/components/Metrics/ConfigStep/MetricsTypeCheckbox.test.tsx +++ b/frontend/__tests__/src/components/Metrics/ConfigStep/MetricsTypeCheckbox.test.tsx @@ -10,106 +10,106 @@ import { REQUIRED_DATA, REQUIRED_DATA_LIST, VELOCITY, -} from '../../../fixtures' -import { act, fireEvent, render, waitFor, within, screen } from '@testing-library/react' -import { MetricsTypeCheckbox } from '@src/components/Metrics/ConfigStep/MetricsTypeCheckbox' -import { Provider } from 'react-redux' -import { setupStore } from '../../../utils/setupStoreUtil' -import userEvent from '@testing-library/user-event' -import { SELECTED_VALUE_SEPARATOR } from '@src/constants/commons' -import BasicInfo from '@src/components/Metrics/ConfigStep/BasicInfo' +} from '../../../fixtures'; +import { act, fireEvent, render, waitFor, within, screen } from '@testing-library/react'; +import { MetricsTypeCheckbox } from '@src/components/Metrics/ConfigStep/MetricsTypeCheckbox'; +import { Provider } from 'react-redux'; +import { setupStore } from '../../../utils/setupStoreUtil'; +import userEvent from '@testing-library/user-event'; +import { SELECTED_VALUE_SEPARATOR } from '@src/constants/commons'; +import BasicInfo from '@src/components/Metrics/ConfigStep/BasicInfo'; -let store = null +let store = null; describe('MetricsTypeCheckbox', () => { const setup = () => { - store = setupStore() + store = setupStore(); return render( - ) - } + ); + }; afterEach(() => { - store = null - }) + store = null; + }); it('should show require data and do not display specific options when init', () => { - const { getByText, queryByText } = setup() - const require = getByText(REQUIRED_DATA) + const { getByText, queryByText } = setup(); + const require = getByText(REQUIRED_DATA); - expect(require).toBeInTheDocument() + expect(require).toBeInTheDocument(); - const option = queryByText(VELOCITY) - expect(option).not.toBeInTheDocument() - }) + const option = queryByText(VELOCITY); + expect(option).not.toBeInTheDocument(); + }); it('should show detail options when click require data button', async () => { - const { getByRole } = setup() - await userEvent.click(screen.getByRole('button', { name: REQUIRED_DATA })) - const listBox = within(getByRole('listbox')) - const options = listBox.getAllByRole('option') - const optionValue = options.map((li) => li.getAttribute('data-value')) + const { getByRole } = setup(); + await userEvent.click(screen.getByRole('button', { name: REQUIRED_DATA })); + const listBox = within(getByRole('listbox')); + const options = listBox.getAllByRole('option'); + const optionValue = options.map((li) => li.getAttribute('data-value')); - expect(optionValue).toEqual(REQUIRED_DATA_LIST) - }) + expect(optionValue).toEqual(REQUIRED_DATA_LIST); + }); it('should show multiple selections when multiple options are selected', async () => { - const { getByRole, getByText } = setup() - await userEvent.click(screen.getByRole('button', { name: REQUIRED_DATA })) - const listBox = within(getByRole('listbox')) + const { getByRole, getByText } = setup(); + await userEvent.click(screen.getByRole('button', { name: REQUIRED_DATA })); + const listBox = within(getByRole('listbox')); await act(async () => { - await userEvent.click(listBox.getByRole('option', { name: VELOCITY })) - }) + await userEvent.click(listBox.getByRole('option', { name: VELOCITY })); + }); await act(async () => { - await userEvent.click(listBox.getByRole('option', { name: CYCLE_TIME })) - }) + await userEvent.click(listBox.getByRole('option', { name: CYCLE_TIME })); + }); - expect(getByText([VELOCITY, CYCLE_TIME].join(SELECTED_VALUE_SEPARATOR))).toBeInTheDocument() - }) + expect(getByText([VELOCITY, CYCLE_TIME].join(SELECTED_VALUE_SEPARATOR))).toBeInTheDocument(); + }); it('should show all selections when all option are select', async () => { - const { getByRole, getByText } = setup() - const displayedDataList = REQUIRED_DATA_LIST.slice(1, 8) + const { getByRole, getByText } = setup(); + const displayedDataList = REQUIRED_DATA_LIST.slice(1, 8); await act(async () => { - await userEvent.click(getByRole('button', { name: REQUIRED_DATA })) - }) - const listBox = within(getByRole('listbox')) + await userEvent.click(getByRole('button', { name: REQUIRED_DATA })); + }); + const listBox = within(getByRole('listbox')); await act(async () => { - await userEvent.click(listBox.getByRole('option', { name: ALL })) - }) + await userEvent.click(listBox.getByRole('option', { name: ALL })); + }); - expect(listBox.getByRole('option', { name: ALL })).toHaveAttribute('aria-selected', 'true') - expect(getByText(displayedDataList.join(SELECTED_VALUE_SEPARATOR))).toBeInTheDocument() - }) + expect(listBox.getByRole('option', { name: ALL })).toHaveAttribute('aria-selected', 'true'); + expect(getByText(displayedDataList.join(SELECTED_VALUE_SEPARATOR))).toBeInTheDocument(); + }); it('should show all selections when click velocity selection and then click all selection', async () => { - const { getByRole, getByText } = setup() - const displayedDataList = REQUIRED_DATA_LIST.slice(1, 8) + const { getByRole, getByText } = setup(); + const displayedDataList = REQUIRED_DATA_LIST.slice(1, 8); await act(async () => { - await userEvent.click(getByRole('button', { name: REQUIRED_DATA })) - }) + await userEvent.click(getByRole('button', { name: REQUIRED_DATA })); + }); - const listBox = within(getByRole('listbox')) + const listBox = within(getByRole('listbox')); await act(async () => { - await userEvent.click(listBox.getByRole('option', { name: VELOCITY })) - }) + await userEvent.click(listBox.getByRole('option', { name: VELOCITY })); + }); await act(async () => { - await userEvent.click(listBox.getByRole('option', { name: ALL })) - }) + await userEvent.click(listBox.getByRole('option', { name: ALL })); + }); - expect(listBox.getByRole('option', { name: ALL })).toHaveAttribute('aria-selected', 'true') - expect(getByText(displayedDataList.join(SELECTED_VALUE_SEPARATOR))).toBeInTheDocument() - }) + expect(listBox.getByRole('option', { name: ALL })).toHaveAttribute('aria-selected', 'true'); + expect(getByText(displayedDataList.join(SELECTED_VALUE_SEPARATOR))).toBeInTheDocument(); + }); it('should be checked of All selected option when click any other options', async () => { - const { getByRole } = setup() + const { getByRole } = setup(); await act(async () => { - await userEvent.click(getByRole('button', { name: REQUIRED_DATA })) - }) + await userEvent.click(getByRole('button', { name: REQUIRED_DATA })); + }); - const listBox = within(getByRole('listbox')) + const listBox = within(getByRole('listbox')); const optionsToClick = [ listBox.getByRole('option', { name: VELOCITY }), listBox.getByRole('option', { name: CYCLE_TIME }), @@ -118,94 +118,94 @@ describe('MetricsTypeCheckbox', () => { listBox.getByRole('option', { name: DEPLOYMENT_FREQUENCY }), listBox.getByRole('option', { name: CHANGE_FAILURE_RATE }), listBox.getByRole('option', { name: MEAN_TIME_TO_RECOVERY }), - ] - await Promise.all(optionsToClick.map((opt) => fireEvent.click(opt))) + ]; + await Promise.all(optionsToClick.map((opt) => fireEvent.click(opt))); await waitFor(() => { - expect(listBox.getByRole('option', { name: ALL })).toHaveAttribute('aria-selected', 'true') - }) - }) + expect(listBox.getByRole('option', { name: ALL })).toHaveAttribute('aria-selected', 'true'); + }); + }); it('should show some selections when click all option and then click velocity selection', async () => { - const { getByRole, getByText } = setup() - const displayedDataList = REQUIRED_DATA_LIST.slice(1, 7) + const { getByRole, getByText } = setup(); + const displayedDataList = REQUIRED_DATA_LIST.slice(1, 7); await act(async () => { - await userEvent.click(getByRole('button', { name: REQUIRED_DATA })) - }) + await userEvent.click(getByRole('button', { name: REQUIRED_DATA })); + }); - const listBox = within(getByRole('listbox')) + const listBox = within(getByRole('listbox')); await act(async () => { - await userEvent.click(listBox.getByRole('option', { name: ALL })) - }) + await userEvent.click(listBox.getByRole('option', { name: ALL })); + }); await act(async () => { - await userEvent.click(listBox.getByRole('option', { name: MEAN_TIME_TO_RECOVERY })) - }) + await userEvent.click(listBox.getByRole('option', { name: MEAN_TIME_TO_RECOVERY })); + }); - expect(listBox.getByRole('option', { name: MEAN_TIME_TO_RECOVERY })).toHaveAttribute('aria-selected', 'false') - expect(listBox.getByRole('option', { name: ALL })).toHaveAttribute('aria-selected', 'false') - expect(getByText(displayedDataList.join(SELECTED_VALUE_SEPARATOR))).toBeInTheDocument() - }) + expect(listBox.getByRole('option', { name: MEAN_TIME_TO_RECOVERY })).toHaveAttribute('aria-selected', 'false'); + expect(listBox.getByRole('option', { name: ALL })).toHaveAttribute('aria-selected', 'false'); + expect(getByText(displayedDataList.join(SELECTED_VALUE_SEPARATOR))).toBeInTheDocument(); + }); it('should show none selection when double click all option', async () => { - const { getByRole, getByText } = setup() - await userEvent.click(getByRole('button', { name: REQUIRED_DATA })) - const listBox = within(getByRole('listbox')) - await userEvent.dblClick(listBox.getByRole('option', { name: ALL })) - await userEvent.click(getByRole('listbox', { name: REQUIRED_DATA })) + const { getByRole, getByText } = setup(); + await userEvent.click(getByRole('button', { name: REQUIRED_DATA })); + const listBox = within(getByRole('listbox')); + await userEvent.dblClick(listBox.getByRole('option', { name: ALL })); + await userEvent.click(getByRole('listbox', { name: REQUIRED_DATA })); - const errorMessage = getByText('Metrics is required') - await waitFor(() => expect(errorMessage).toBeInTheDocument()) - }) + const errorMessage = getByText('Metrics is required'); + await waitFor(() => expect(errorMessage).toBeInTheDocument()); + }); it('should show error message when require data is null', async () => { - const { getByRole, getByText } = setup() + const { getByRole, getByText } = setup(); await act(async () => { - await userEvent.click(getByRole('button', { name: REQUIRED_DATA })) - }) - const listBox = within(getByRole('listbox')) + await userEvent.click(getByRole('button', { name: REQUIRED_DATA })); + }); + const listBox = within(getByRole('listbox')); await act(async () => { - await userEvent.click(listBox.getByRole('option', { name: VELOCITY })) - }) + await userEvent.click(listBox.getByRole('option', { name: VELOCITY })); + }); await act(async () => { - await userEvent.click(listBox.getByRole('option', { name: VELOCITY })) - }) + await userEvent.click(listBox.getByRole('option', { name: VELOCITY })); + }); await act(async () => { - await userEvent.click(getByRole('listbox', { name: REQUIRED_DATA })) - }) + await userEvent.click(getByRole('listbox', { name: REQUIRED_DATA })); + }); - const errorMessage = getByText('Metrics is required') - expect(errorMessage).toBeInTheDocument() - }) + const errorMessage = getByText('Metrics is required'); + expect(errorMessage).toBeInTheDocument(); + }); it('should show board component when click MetricsTypeCheckbox selection velocity ', async () => { - const { getByRole, getAllByText } = setup() + const { getByRole, getAllByText } = setup(); await act(async () => { - await userEvent.click(getByRole('button', { name: REQUIRED_DATA })) - }) - const listBox = within(getByRole('listbox')) + await userEvent.click(getByRole('button', { name: REQUIRED_DATA })); + }); + const listBox = within(getByRole('listbox')); await act(async () => { - await userEvent.click(listBox.getByRole('option', { name: VELOCITY })) - }) + await userEvent.click(listBox.getByRole('option', { name: VELOCITY })); + }); - expect(getAllByText(CONFIG_TITLE.BOARD)[0]).toBeInTheDocument() - }) + expect(getAllByText(CONFIG_TITLE.BOARD)[0]).toBeInTheDocument(); + }); it('should hidden board component when MetricsTypeCheckbox select is null given MetricsTypeCheckbox select is velocity ', async () => { - const { getByRole, queryByText } = setup() + const { getByRole, queryByText } = setup(); await act(async () => { - await userEvent.click(getByRole('button', { name: REQUIRED_DATA })) - }) - const requireDateSelection = within(getByRole('listbox')) + await userEvent.click(getByRole('button', { name: REQUIRED_DATA })); + }); + const requireDateSelection = within(getByRole('listbox')); await act(async () => { - await userEvent.click(requireDateSelection.getByRole('option', { name: VELOCITY })) - }) + await userEvent.click(requireDateSelection.getByRole('option', { name: VELOCITY })); + }); await act(async () => { - await userEvent.click(requireDateSelection.getByRole('option', { name: VELOCITY })) - }) + await userEvent.click(requireDateSelection.getByRole('option', { name: VELOCITY })); + }); - expect(queryByText(CONFIG_TITLE.BOARD)).not.toBeInTheDocument() - }) -}) + expect(queryByText(CONFIG_TITLE.BOARD)).not.toBeInTheDocument(); + }); +}); diff --git a/frontend/__tests__/src/components/Metrics/ConfigStep/NoCardPop.test.tsx b/frontend/__tests__/src/components/Metrics/ConfigStep/NoCardPop.test.tsx index d5afd0af1a..3817b59cda 100644 --- a/frontend/__tests__/src/components/Metrics/ConfigStep/NoCardPop.test.tsx +++ b/frontend/__tests__/src/components/Metrics/ConfigStep/NoCardPop.test.tsx @@ -1,23 +1,23 @@ -import { fireEvent, render } from '@testing-library/react' -import { NoCardPop } from '@src/components/Metrics/ConfigStep/NoDoneCardPop' -import { NO_CARD_ERROR_MESSAGE } from '../../../fixtures' +import { fireEvent, render } from '@testing-library/react'; +import { NoCardPop } from '@src/components/Metrics/ConfigStep/NoDoneCardPop'; +import { NO_CARD_ERROR_MESSAGE } from '../../../fixtures'; -const OK = 'Ok' +const OK = 'Ok'; describe('NoCardPop', () => { it('should show NoCardPop component given isOpen param is true', () => { - const { getByText, getByRole } = render() + const { getByText, getByRole } = render(); - expect(getByText(NO_CARD_ERROR_MESSAGE)).toBeInTheDocument() - expect(getByRole('button', { name: OK })).toBeInTheDocument() - }) + expect(getByText(NO_CARD_ERROR_MESSAGE)).toBeInTheDocument(); + expect(getByRole('button', { name: OK })).toBeInTheDocument(); + }); it('should call onClose function when click Ok button given isOpen param is true', () => { - const handleClose = jest.fn() - const { getByRole } = render() - const okButton = getByRole('button', { name: OK }) + const handleClose = jest.fn(); + const { getByRole } = render(); + const okButton = getByRole('button', { name: OK }); - fireEvent.click(okButton) + fireEvent.click(okButton); - expect(handleClose).toBeCalledTimes(1) - }) -}) + expect(handleClose).toBeCalledTimes(1); + }); +}); diff --git a/frontend/__tests__/src/components/Metrics/ConfigStep/PipelineTool.test.tsx b/frontend/__tests__/src/components/Metrics/ConfigStep/PipelineTool.test.tsx index 70b0344d25..ee82409bba 100644 --- a/frontend/__tests__/src/components/Metrics/ConfigStep/PipelineTool.test.tsx +++ b/frontend/__tests__/src/components/Metrics/ConfigStep/PipelineTool.test.tsx @@ -1,5 +1,5 @@ -import { fireEvent, render, screen, waitFor, within } from '@testing-library/react' -import { PipelineTool } from '@src/components/Metrics/ConfigStep/PipelineTool' +import { fireEvent, render, screen, waitFor, within } from '@testing-library/react'; +import { PipelineTool } from '@src/components/Metrics/ConfigStep/PipelineTool'; import { CONFIG_TITLE, ERROR_MESSAGE_COLOR, @@ -13,23 +13,23 @@ import { VERIFY, VERIFY_ERROR_MESSAGE, VERIFY_FAILED, -} from '../../../fixtures' -import { Provider } from 'react-redux' -import { setupStore } from '../../../utils/setupStoreUtil' -import { setupServer } from 'msw/node' -import { rest } from 'msw' -import userEvent from '@testing-library/user-event' -import { HttpStatusCode } from 'axios' +} from '../../../fixtures'; +import { Provider } from 'react-redux'; +import { setupStore } from '../../../utils/setupStoreUtil'; +import { setupServer } from 'msw/node'; +import { rest } from 'msw'; +import userEvent from '@testing-library/user-event'; +import { HttpStatusCode } from 'axios'; export const fillPipelineToolFieldsInformation = async () => { - const mockInfo = 'bkua_mockTokenMockTokenMockTokenMockToken1234' - const tokenInput = screen.getByTestId('pipelineToolTextField').querySelector('input') as HTMLInputElement - await userEvent.type(tokenInput, mockInfo) + const mockInfo = 'bkua_mockTokenMockTokenMockTokenMockToken1234'; + const tokenInput = screen.getByTestId('pipelineToolTextField').querySelector('input') as HTMLInputElement; + await userEvent.type(tokenInput, mockInfo); - expect(tokenInput.value).toEqual(mockInfo) -} + expect(tokenInput.value).toEqual(mockInfo); +}; -let store = null +let store = null; const server = setupServer( rest.post(MOCK_PIPELINE_URL, (req, res, ctx) => @@ -45,157 +45,157 @@ const server = setupServer( ctx.status(200) ) ) -) +); describe('PipelineTool', () => { - beforeAll(() => server.listen()) - afterAll(() => server.close()) - store = setupStore() + beforeAll(() => server.listen()); + afterAll(() => server.close()); + store = setupStore(); const setup = () => { - store = setupStore() + store = setupStore(); return render( - ) - } + ); + }; afterEach(() => { - store = null - }) + store = null; + }); it('should show pipelineTool title and fields when render pipelineTool component ', () => { - const { getByLabelText, getAllByText } = setup() + const { getByLabelText, getAllByText } = setup(); PIPELINE_TOOL_FIELDS.map((field) => { - expect(getByLabelText(`${field} *`)).toBeInTheDocument() - }) + expect(getByLabelText(`${field} *`)).toBeInTheDocument(); + }); - expect(getAllByText(CONFIG_TITLE.PIPELINE_TOOL)[0]).toBeInTheDocument() - }) + expect(getAllByText(CONFIG_TITLE.PIPELINE_TOOL)[0]).toBeInTheDocument(); + }); it('should show default value buildKite when init pipelineTool component', () => { - const { getByText, queryByText } = setup() - const pipelineToolType = getByText(PIPELINE_TOOL_TYPES.BUILD_KITE) + const { getByText, queryByText } = setup(); + const pipelineToolType = getByText(PIPELINE_TOOL_TYPES.BUILD_KITE); - expect(pipelineToolType).toBeInTheDocument() + expect(pipelineToolType).toBeInTheDocument(); - const option = queryByText(PIPELINE_TOOL_TYPES.GO_CD) + const option = queryByText(PIPELINE_TOOL_TYPES.GO_CD); - expect(option).not.toBeInTheDocument() - }) + expect(option).not.toBeInTheDocument(); + }); it('should clear other fields information when change pipelineTool Field selection', async () => { - setup() - const tokenInput = screen.getByTestId('pipelineToolTextField').querySelector('input') as HTMLInputElement + setup(); + const tokenInput = screen.getByTestId('pipelineToolTextField').querySelector('input') as HTMLInputElement; - await fillPipelineToolFieldsInformation() - await userEvent.click(screen.getByRole('button', { name: 'Pipeline Tool' })) + await fillPipelineToolFieldsInformation(); + await userEvent.click(screen.getByRole('button', { name: 'Pipeline Tool' })); - await userEvent.click(screen.getByText(PIPELINE_TOOL_TYPES.GO_CD)) - expect(tokenInput.value).toEqual('') - }) + await userEvent.click(screen.getByText(PIPELINE_TOOL_TYPES.GO_CD)); + expect(tokenInput.value).toEqual(''); + }); it('should clear all fields information when click reset button', async () => { - const { getByText, queryByRole } = setup() - const tokenInput = screen.getByTestId('pipelineToolTextField').querySelector('input') as HTMLInputElement - await fillPipelineToolFieldsInformation() + const { getByText, queryByRole } = setup(); + const tokenInput = screen.getByTestId('pipelineToolTextField').querySelector('input') as HTMLInputElement; + await fillPipelineToolFieldsInformation(); - await userEvent.click(screen.getByText(VERIFY)) + await userEvent.click(screen.getByText(VERIFY)); - await userEvent.click(screen.getByRole('button', { name: RESET })) + await userEvent.click(screen.getByRole('button', { name: RESET })); - expect(tokenInput.value).toEqual('') - expect(getByText(PIPELINE_TOOL_TYPES.BUILD_KITE)).toBeInTheDocument() - expect(queryByRole('button', { name: RESET })).not.toBeInTheDocument() - expect(queryByRole('button', { name: VERIFY })).toBeDisabled() - }) + expect(tokenInput.value).toEqual(''); + expect(getByText(PIPELINE_TOOL_TYPES.BUILD_KITE)).toBeInTheDocument(); + expect(queryByRole('button', { name: RESET })).not.toBeInTheDocument(); + expect(queryByRole('button', { name: VERIFY })).toBeDisabled(); + }); it('should show detail options when click pipelineTool fields', async () => { - const { getByRole } = setup() - await userEvent.click(screen.getByRole('button', { name: 'Pipeline Tool' })) - const listBox = within(getByRole('listbox')) - const options = listBox.getAllByRole('option') - const optionValue = options.map((li) => li.getAttribute('data-value')) + const { getByRole } = setup(); + await userEvent.click(screen.getByRole('button', { name: 'Pipeline Tool' })); + const listBox = within(getByRole('listbox')); + const options = listBox.getAllByRole('option'); + const optionValue = options.map((li) => li.getAttribute('data-value')); - expect(optionValue).toEqual(Object.values(PIPELINE_TOOL_TYPES)) - }) + expect(optionValue).toEqual(Object.values(PIPELINE_TOOL_TYPES)); + }); it('should enabled verify button when all fields checked correctly given disable verify button', async () => { - const { getByRole } = setup() - const verifyButton = getByRole('button', { name: VERIFY }) + const { getByRole } = setup(); + const verifyButton = getByRole('button', { name: VERIFY }); - expect(verifyButton).toBeDisabled() + expect(verifyButton).toBeDisabled(); - await fillPipelineToolFieldsInformation() + await fillPipelineToolFieldsInformation(); - expect(verifyButton).toBeEnabled() - }) + expect(verifyButton).toBeEnabled(); + }); it('should show error message and error style when token is empty', async () => { - const { getByText } = setup() - await fillPipelineToolFieldsInformation() - const mockInfo = 'mockToken' - const tokenInput = screen.getByTestId('pipelineToolTextField').querySelector('input') as HTMLInputElement - await userEvent.type(tokenInput, mockInfo) - await userEvent.clear(tokenInput) + const { getByText } = setup(); + await fillPipelineToolFieldsInformation(); + const mockInfo = 'mockToken'; + const tokenInput = screen.getByTestId('pipelineToolTextField').querySelector('input') as HTMLInputElement; + await userEvent.type(tokenInput, mockInfo); + await userEvent.clear(tokenInput); - expect(getByText(TOKEN_ERROR_MESSAGE[1])).toBeVisible() - expect(getByText(TOKEN_ERROR_MESSAGE[1])).toHaveStyle(ERROR_MESSAGE_COLOR) - }) + expect(getByText(TOKEN_ERROR_MESSAGE[1])).toBeVisible(); + expect(getByText(TOKEN_ERROR_MESSAGE[1])).toHaveStyle(ERROR_MESSAGE_COLOR); + }); it('should show error message and error style when token is invalid', async () => { - const { getByText } = setup() - const mockInfo = 'mockToken' - const tokenInput = screen.getByTestId('pipelineToolTextField').querySelector('input') as HTMLInputElement - await userEvent.type(tokenInput, mockInfo) + const { getByText } = setup(); + const mockInfo = 'mockToken'; + const tokenInput = screen.getByTestId('pipelineToolTextField').querySelector('input') as HTMLInputElement; + await userEvent.type(tokenInput, mockInfo); - expect(tokenInput.value).toEqual(mockInfo) + expect(tokenInput.value).toEqual(mockInfo); - expect(getByText(TOKEN_ERROR_MESSAGE[0])).toBeInTheDocument() - expect(getByText(TOKEN_ERROR_MESSAGE[0])).toHaveStyle(ERROR_MESSAGE_COLOR) - }) + expect(getByText(TOKEN_ERROR_MESSAGE[0])).toBeInTheDocument(); + expect(getByText(TOKEN_ERROR_MESSAGE[0])).toHaveStyle(ERROR_MESSAGE_COLOR); + }); it('should show reset button and verified button when verify succeed ', async () => { - const { getByText } = setup() - await fillPipelineToolFieldsInformation() + const { getByText } = setup(); + await fillPipelineToolFieldsInformation(); - await userEvent.click(screen.getByText(VERIFY)) - expect(screen.getByText(RESET)).toBeVisible() + await userEvent.click(screen.getByText(VERIFY)); + expect(screen.getByText(RESET)).toBeVisible(); await waitFor(() => { - expect(getByText(VERIFIED)).toBeTruthy() - }) - }) + expect(getByText(VERIFIED)).toBeTruthy(); + }); + }); it('should called verifyPipelineTool method once when click verify button', async () => { - const { getByText } = setup() - await fillPipelineToolFieldsInformation() - await userEvent.click(screen.getByRole('button', { name: VERIFY })) + const { getByText } = setup(); + await fillPipelineToolFieldsInformation(); + await userEvent.click(screen.getByRole('button', { name: VERIFY })); - expect(getByText('Verified')).toBeInTheDocument() - }) + expect(getByText('Verified')).toBeInTheDocument(); + }); it('should check loading animation when click verify button', async () => { - const { getByRole, container } = setup() - await fillPipelineToolFieldsInformation() - fireEvent.click(getByRole('button', { name: VERIFY })) + const { getByRole, container } = setup(); + await fillPipelineToolFieldsInformation(); + fireEvent.click(getByRole('button', { name: VERIFY })); await waitFor(() => { - expect(container.getElementsByTagName('span')[0].getAttribute('role')).toEqual('progressbar') - }) - }) + expect(container.getElementsByTagName('span')[0].getAttribute('role')).toEqual('progressbar'); + }); + }); it('should check error notification show when pipelineTool verify response status is 401', async () => { server.use( rest.post(MOCK_PIPELINE_URL, (req, res, ctx) => res(ctx.status(HttpStatusCode.Unauthorized), ctx.json({ hintInfo: VERIFY_ERROR_MESSAGE.UNAUTHORIZED })) ) - ) - const { getByText } = setup() - await fillPipelineToolFieldsInformation() + ); + const { getByText } = setup(); + await fillPipelineToolFieldsInformation(); - await userEvent.click(screen.getByRole('button', { name: VERIFY })) + await userEvent.click(screen.getByRole('button', { name: VERIFY })); expect( getByText(`${MOCK_PIPELINE_VERIFY_REQUEST_PARAMS.type} ${VERIFY_FAILED}: ${VERIFY_ERROR_MESSAGE.UNAUTHORIZED}`) - ).toBeInTheDocument() - }) -}) + ).toBeInTheDocument(); + }); +}); diff --git a/frontend/__tests__/src/components/Metrics/ConfigStep/SourceControl.test.tsx b/frontend/__tests__/src/components/Metrics/ConfigStep/SourceControl.test.tsx index 4928ce2109..dd20b1cd66 100644 --- a/frontend/__tests__/src/components/Metrics/ConfigStep/SourceControl.test.tsx +++ b/frontend/__tests__/src/components/Metrics/ConfigStep/SourceControl.test.tsx @@ -1,7 +1,8 @@ -import { setupStore } from '../../../utils/setupStoreUtil' -import { fireEvent, render, screen, waitFor } from '@testing-library/react' -import { Provider } from 'react-redux' -import { SourceControl } from '@src/components/Metrics/ConfigStep/SourceControl' +import React from 'react'; +import { setupStore } from '../../../utils/setupStoreUtil'; +import { fireEvent, render, screen, waitFor } from '@testing-library/react'; +import { Provider } from 'react-redux'; +import { SourceControl } from '@src/components/Metrics/ConfigStep/SourceControl'; import { CONFIG_TITLE, ERROR_MESSAGE_COLOR, @@ -14,21 +15,23 @@ import { VERIFY, VERIFY_ERROR_MESSAGE, VERIFY_FAILED, -} from '../../../fixtures' -import { setupServer } from 'msw/node' -import { rest } from 'msw' -import { HttpStatusCode } from 'axios' +} from '../../../fixtures'; +import { setupServer } from 'msw/node'; +import { rest } from 'msw'; +import { HttpStatusCode } from 'axios'; export const fillSourceControlFieldsInformation = () => { - const mockInfo = 'ghpghoghughsghr_1A2b1A2b1A2b1A2b1A2b1A2b1A2b1A2b1A2b' - const tokenInput = screen.getByTestId('sourceControlTextField').querySelector('input') as HTMLInputElement + const mockInfo = 'AAAAA_XXXXXX' + .replace('AAAAA', 'ghpghoghughsghr') + .replace('XXXXXX', '1A2b1A2b1A2b1A2b1A2b1A2b1A2b1A2b1A2b'); + const tokenInput = screen.getByTestId('sourceControlTextField').querySelector('input') as HTMLInputElement; - fireEvent.change(tokenInput, { target: { value: mockInfo } }) + fireEvent.change(tokenInput, { target: { value: mockInfo } }); - expect(tokenInput.value).toEqual(mockInfo) -} + expect(tokenInput.value).toEqual(mockInfo); +}; -let store = null +let store = null; const server = setupServer( rest.post(MOCK_SOURCE_CONTROL_URL, (req, res, ctx) => @@ -39,126 +42,126 @@ const server = setupServer( ctx.status(200) ) ) -) +); describe('SourceControl', () => { - beforeAll(() => server.listen()) - afterAll(() => server.close()) - store = setupStore() + beforeAll(() => server.listen()); + afterAll(() => server.close()); + store = setupStore(); const setup = () => { - store = setupStore() + store = setupStore(); return render( - ) - } + ); + }; afterEach(() => { - store = null - }) + store = null; + }); it('should show sourceControl title and fields when render sourceControl component', () => { - const { getByLabelText, getAllByText } = setup() + setup(); - expect(getAllByText(CONFIG_TITLE.SOURCE_CONTROL)[0]).toBeInTheDocument() + expect(screen.getAllByText(CONFIG_TITLE.SOURCE_CONTROL)[0]).toBeInTheDocument(); SOURCE_CONTROL_FIELDS.map((field) => { - expect(getByLabelText(`${field} *`)).toBeInTheDocument() - }) - }) + expect(screen.getByLabelText(`${field} *`)).toBeInTheDocument(); + }); + }); it('should show default value gitHub when init sourceControl component', () => { - const { getByText } = setup() - const sourceControlType = getByText(SOURCE_CONTROL_TYPES.GITHUB) + setup(); + const sourceControlType = screen.getByText(SOURCE_CONTROL_TYPES.GITHUB); - expect(sourceControlType).toBeInTheDocument() - }) + expect(sourceControlType).toBeInTheDocument(); + }); it('should clear all fields information when click reset button', async () => { - const { getByRole, getByText, queryByRole } = setup() - const tokenInput = screen.getByTestId('sourceControlTextField').querySelector('input') as HTMLInputElement + setup(); + const tokenInput = screen.getByTestId('sourceControlTextField').querySelector('input') as HTMLInputElement; - fillSourceControlFieldsInformation() + fillSourceControlFieldsInformation(); - fireEvent.click(getByText(VERIFY)) + fireEvent.click(screen.getByText(VERIFY)); await waitFor(() => { - expect(getByRole('button', { name: RESET })).toBeTruthy() - fireEvent.click(getByRole('button', { name: RESET })) - }) + expect(screen.getByRole('button', { name: RESET })).toBeTruthy(); + fireEvent.click(screen.getByRole('button', { name: RESET })); + }); - expect(tokenInput.value).toEqual('') - expect(getByText(SOURCE_CONTROL_TYPES.GITHUB)).toBeInTheDocument() - expect(queryByRole('button', { name: RESET })).not.toBeTruthy() - expect(getByRole('button', { name: VERIFY })).toBeDisabled() - }) + expect(tokenInput.value).toEqual(''); + expect(screen.getByText(SOURCE_CONTROL_TYPES.GITHUB)).toBeInTheDocument(); + expect(screen.queryByRole('button', { name: RESET })).not.toBeTruthy(); + expect(screen.getByRole('button', { name: VERIFY })).toBeDisabled(); + }); it('should enable verify button when all fields checked correctly given disable verify button', () => { - const { getByRole } = setup() - const verifyButton = getByRole('button', { name: VERIFY }) + setup(); + const verifyButton = screen.getByRole('button', { name: VERIFY }); - expect(verifyButton).toBeDisabled() + expect(verifyButton).toBeDisabled(); - fillSourceControlFieldsInformation() + fillSourceControlFieldsInformation(); - expect(verifyButton).toBeEnabled() - }) + expect(verifyButton).toBeEnabled(); + }); it('should show reset button and verified button when verify successfully', async () => { - const { getByText } = setup() - fillSourceControlFieldsInformation() + setup(); + fillSourceControlFieldsInformation(); - fireEvent.click(getByText(VERIFY)) + fireEvent.click(screen.getByText(VERIFY)); await waitFor(() => { - expect(getByText(RESET)).toBeTruthy() - }) + expect(screen.getByText(RESET)).toBeTruthy(); + }); await waitFor(() => { - expect(getByText(VERIFIED)).toBeTruthy() - }) - }) + expect(screen.getByText(VERIFIED)).toBeTruthy(); + }); + }); it('should show error message and error style when token is empty', () => { - const { getByText } = setup() + setup(); - fillSourceControlFieldsInformation() + fillSourceControlFieldsInformation(); - const tokenInput = screen.getByTestId('sourceControlTextField').querySelector('input') as HTMLInputElement + const tokenInput = screen.getByTestId('sourceControlTextField').querySelector('input') as HTMLInputElement; - fireEvent.change(tokenInput, { target: { value: '' } }) + fireEvent.change(tokenInput, { target: { value: '' } }); - expect(getByText(TOKEN_ERROR_MESSAGE[1])).toBeInTheDocument() - expect(getByText(TOKEN_ERROR_MESSAGE[1])).toHaveStyle(ERROR_MESSAGE_COLOR) - }) + expect(screen.getByText(TOKEN_ERROR_MESSAGE[1])).toBeInTheDocument(); + expect(screen.getByText(TOKEN_ERROR_MESSAGE[1])).toHaveStyle(ERROR_MESSAGE_COLOR); + }); it('should show error message and error style when token is invalid', () => { - const { getByText } = setup() - const mockInfo = 'mockToken' - const tokenInput = screen.getByTestId('sourceControlTextField').querySelector('input') as HTMLInputElement + setup(); + const mockInfo = 'mockToken'; + const tokenInput = screen.getByTestId('sourceControlTextField').querySelector('input') as HTMLInputElement; - fireEvent.change(tokenInput, { target: { value: mockInfo } }) + fireEvent.change(tokenInput, { target: { value: mockInfo } }); - expect(tokenInput.value).toEqual(mockInfo) - expect(getByText(TOKEN_ERROR_MESSAGE[0])).toBeInTheDocument() - expect(getByText(TOKEN_ERROR_MESSAGE[0])).toHaveStyle(ERROR_MESSAGE_COLOR) - }) + expect(tokenInput.value).toEqual(mockInfo); + expect(screen.getByText(TOKEN_ERROR_MESSAGE[0])).toBeInTheDocument(); + expect(screen.getByText(TOKEN_ERROR_MESSAGE[0])).toHaveStyle(ERROR_MESSAGE_COLOR); + }); it('should show error notification when sourceControl verify response status is 401', async () => { server.use( rest.post(MOCK_SOURCE_CONTROL_URL, (req, res, ctx) => res(ctx.status(HttpStatusCode.Unauthorized), ctx.json({ hintInfo: VERIFY_ERROR_MESSAGE.UNAUTHORIZED })) ) - ) - const { getByText, getByRole } = setup() + ); + setup(); - fillSourceControlFieldsInformation() + fillSourceControlFieldsInformation(); - fireEvent.click(getByRole('button', { name: VERIFY })) + fireEvent.click(screen.getByRole('button', { name: VERIFY })); await waitFor(() => { expect( - getByText(`${SOURCE_CONTROL_TYPES.GITHUB} ${VERIFY_FAILED}: ${VERIFY_ERROR_MESSAGE.UNAUTHORIZED}`) - ).toBeInTheDocument() - }) - }) -}) + screen.getByText(`${SOURCE_CONTROL_TYPES.GITHUB} ${VERIFY_FAILED}: ${VERIFY_ERROR_MESSAGE.UNAUTHORIZED}`) + ).toBeInTheDocument(); + }); + }); +}); diff --git a/frontend/__tests__/src/components/Metrics/MetricsStep/Classification.test.tsx b/frontend/__tests__/src/components/Metrics/MetricsStep/Classification.test.tsx index a2a0cfd2d2..537d0969cd 100644 --- a/frontend/__tests__/src/components/Metrics/MetricsStep/Classification.test.tsx +++ b/frontend/__tests__/src/components/Metrics/MetricsStep/Classification.test.tsx @@ -1,130 +1,131 @@ -import { act, render, waitFor, within } from '@testing-library/react' -import { Classification } from '@src/components/Metrics/MetricsStep/Classification' -import userEvent from '@testing-library/user-event' -import { setupStore } from '../../../utils/setupStoreUtil' -import { Provider } from 'react-redux' -import { ERROR_MESSAGE_TIME_DURATION } from '../../../fixtures' - -const mockTitle = 'Classification Setting' -const mockLabel = 'Distinguished by' +import React from 'react'; +import { act, render, waitFor, within, screen } from '@testing-library/react'; +import { Classification } from '@src/components/Metrics/MetricsStep/Classification'; +import userEvent from '@testing-library/user-event'; +import { setupStore } from '../../../utils/setupStoreUtil'; +import { Provider } from 'react-redux'; +import { ERROR_MESSAGE_TIME_DURATION } from '../../../fixtures'; + +const mockTitle = 'Classification Setting'; +const mockLabel = 'Distinguished by'; const mockTargetFields = [ { flag: true, key: 'issue', name: 'Issue' }, { flag: false, key: 'type', name: 'Type' }, -] +]; jest.mock('@src/context/config/configSlice', () => ({ ...jest.requireActual('@src/context/config/configSlice'), selectIsProjectCreated: jest.fn().mockReturnValue(false), -})) +})); jest.mock('@src/context/Metrics/metricsSlice', () => ({ ...jest.requireActual('@src/context/Metrics/metricsSlice'), selectClassificationWarningMessage: jest.fn().mockReturnValue('Test warning Message'), -})) +})); -let store = setupStore() +let store = setupStore(); const setup = () => { return render( - ) -} + ); +}; describe('Classification', () => { beforeEach(() => { - store = setupStore() - }) + store = setupStore(); + }); afterEach(() => { - jest.clearAllMocks() - }) + jest.clearAllMocks(); + }); it('should show Classification when render Classification component', () => { - const { getByText } = setup() + setup(); - expect(getByText(mockTitle)).toBeInTheDocument() - expect(getByText(mockLabel)).toBeInTheDocument() - }) + expect(screen.getByText(mockTitle)).toBeInTheDocument(); + expect(screen.getByText(mockLabel)).toBeInTheDocument(); + }); it('should show default options when initialization', () => { - const { queryByText, getByText } = setup() + setup(); - expect(getByText('Issue')).toBeInTheDocument() - expect(queryByText('Type')).not.toBeInTheDocument() - }) + expect(screen.getByText('Issue')).toBeInTheDocument(); + expect(screen.queryByText('Type')).not.toBeInTheDocument(); + }); it('should show all options when click selectBox', async () => { - const { getByRole } = setup() + setup(); await act(async () => { - await userEvent.click(getByRole('combobox', { name: mockLabel })) - }) + await userEvent.click(screen.getByRole('combobox', { name: mockLabel })); + }); - expect(getByRole('option', { name: 'Issue' })).toBeInTheDocument() - expect(getByRole('option', { name: 'Type' })).toBeInTheDocument() - }) + expect(screen.getByRole('option', { name: 'Issue' })).toBeInTheDocument(); + expect(screen.getByRole('option', { name: 'Type' })).toBeInTheDocument(); + }); it('should show all targetField when click All and show nothing when cancel click', async () => { - const { getByText, getByRole, queryByRole } = setup() + setup(); await act(async () => { - await userEvent.click(getByRole('combobox', { name: mockLabel })) - }) + await userEvent.click(screen.getByRole('combobox', { name: mockLabel })); + }); await act(async () => { - await userEvent.click(getByText('All')) - }) - const names = mockTargetFields.map((item) => item.name) + await userEvent.click(screen.getByText('All')); + }); + const names = mockTargetFields.map((item) => item.name); - expect(getByRole('button', { name: names[0] })).toBeVisible() - expect(getByRole('button', { name: names[1] })).toBeVisible() + expect(screen.getByRole('button', { name: names[0] })).toBeVisible(); + expect(screen.getByRole('button', { name: names[1] })).toBeVisible(); await act(async () => { - await userEvent.click(getByText('All')) - }) + await userEvent.click(screen.getByText('All')); + }); - expect(queryByRole('button', { name: names[0] })).not.toBeInTheDocument() - expect(queryByRole('button', { name: names[1] })).not.toBeInTheDocument() - }) + expect(screen.queryByRole('button', { name: names[0] })).not.toBeInTheDocument(); + expect(screen.queryByRole('button', { name: names[1] })).not.toBeInTheDocument(); + }); it('should show selected targetField when click selected field', async () => { - const { getByRole, getByText, queryByRole } = setup() - const names = mockTargetFields.map((item) => item.name) + setup(); + const names = mockTargetFields.map((item) => item.name); await act(async () => { - await userEvent.click(getByRole('combobox', { name: mockLabel })) - }) + await userEvent.click(screen.getByRole('combobox', { name: mockLabel })); + }); await act(async () => { - await userEvent.click(getByText('All')) - }) + await userEvent.click(screen.getByText('All')); + }); await act(async () => { - await userEvent.click(getByText('All')) - }) + await userEvent.click(screen.getByText('All')); + }); - const listBox = within(getByRole('listbox')) + const listBox = within(screen.getByRole('listbox')); await act(async () => { - await userEvent.click(listBox.getByRole('option', { name: names[0] })) - }) + await userEvent.click(listBox.getByRole('option', { name: names[0] })); + }); - expect(queryByRole('button', { name: names[0] })).toBeInTheDocument() - expect(queryByRole('button', { name: names[1] })).not.toBeInTheDocument() - }) + expect(screen.queryByRole('button', { name: names[0] })).toBeInTheDocument(); + expect(screen.queryByRole('button', { name: names[1] })).not.toBeInTheDocument(); + }); it('should show warning message when classification warning message has a value in cycleTime component', () => { - const { getByText } = setup() + setup(); - expect(getByText('Test warning Message')).toBeVisible() - }) + expect(screen.getByText('Test warning Message')).toBeVisible(); + }); it('should show disable warning message when classification warning message has a value after two seconds in cycleTime component', async () => { - jest.useFakeTimers() - const { queryByText } = setup() + jest.useFakeTimers(); + setup(); act(() => { - jest.advanceTimersByTime(ERROR_MESSAGE_TIME_DURATION) - }) + jest.advanceTimersByTime(ERROR_MESSAGE_TIME_DURATION); + }); await waitFor(() => { - expect(queryByText('Test warning Message')).not.toBeInTheDocument() - }) - }) -}) + expect(screen.queryByText('Test warning Message')).not.toBeInTheDocument(); + }); + }); +}); diff --git a/frontend/__tests__/src/components/Metrics/MetricsStep/Crews.test.tsx b/frontend/__tests__/src/components/Metrics/MetricsStep/Crews.test.tsx index b7fcf248f4..19566e5262 100644 --- a/frontend/__tests__/src/components/Metrics/MetricsStep/Crews.test.tsx +++ b/frontend/__tests__/src/components/Metrics/MetricsStep/Crews.test.tsx @@ -1,154 +1,155 @@ -import { act, render, waitFor, within } from '@testing-library/react' -import { Crews } from '@src/components/Metrics/MetricsStep/Crews' -import userEvent from '@testing-library/user-event' -import { setupStore } from '../../../utils/setupStoreUtil' -import { Provider } from 'react-redux' -import { updateAssigneeFilter } from '@src/context/Metrics/metricsSlice' - -const mockOptions = ['crew A', 'crew B'] -const mockTitle = 'Crews Setting' -const mockLabel = 'Included Crews' -const assigneeFilterLabels = ['Last assignee', 'Historical assignee'] -const assigneeFilterValues = ['lastAssignee', 'historicalAssignee'] +import React from 'react'; +import { act, render, screen, waitFor, within } from '@testing-library/react'; +import { Crews } from '@src/components/Metrics/MetricsStep/Crews'; +import userEvent from '@testing-library/user-event'; +import { setupStore } from '../../../utils/setupStoreUtil'; +import { Provider } from 'react-redux'; +import { updateAssigneeFilter } from '@src/context/Metrics/metricsSlice'; + +const mockOptions = ['crew A', 'crew B']; +const mockTitle = 'Crews Setting'; +const mockLabel = 'Included Crews'; +const assigneeFilterLabels = ['Last assignee', 'Historical assignee']; +const assigneeFilterValues = ['lastAssignee', 'historicalAssignee']; jest.mock('@src/context/Metrics/metricsSlice', () => ({ ...jest.requireActual('@src/context/Metrics/metricsSlice'), selectMetricsContent: jest.fn().mockReturnValue({ users: ['crew A', 'crew B'] }), -})) +})); -const mockedUseAppDispatch = jest.fn() +const mockedUseAppDispatch = jest.fn(); jest.mock('@src/hooks/useAppDispatch', () => ({ useAppDispatch: () => mockedUseAppDispatch, -})) +})); -let store = setupStore() +let store = setupStore(); const setup = () => { return render( - ) -} + ); +}; describe('Crew', () => { beforeEach(() => { - store = setupStore() - }) + store = setupStore(); + }); afterEach(() => { - jest.clearAllMocks() - }) + jest.clearAllMocks(); + }); it('should show Crews when render Crews component', () => { - const { getByText } = setup() + setup(); - expect(getByText(mockTitle)).toBeInTheDocument() - }) + expect(screen.getByText(mockTitle)).toBeInTheDocument(); + }); it('should selected all options by default when initializing', () => { - const { getByRole } = setup() + setup(); - expect(getByRole('button', { name: 'crew A' })).toBeInTheDocument() - expect(getByRole('button', { name: 'crew B' })).toBeInTheDocument() - }) + expect(screen.getByRole('button', { name: 'crew A' })).toBeInTheDocument(); + expect(screen.getByRole('button', { name: 'crew B' })).toBeInTheDocument(); + }); it('should show detail options when click Included crews button', async () => { - const { getByRole } = setup() + setup(); await act(async () => { - await userEvent.click(getByRole('combobox', { name: mockLabel })) - }) - const listBox = within(getByRole('listbox')) + await userEvent.click(screen.getByRole('combobox', { name: mockLabel })); + }); + const listBox = within(screen.getByRole('listbox')); - expect(listBox.getByRole('option', { name: 'All' })).toBeVisible() - expect(listBox.getByRole('option', { name: 'crew A' })).toBeVisible() - expect(listBox.getByRole('option', { name: 'crew B' })).toBeVisible() - }) + expect(listBox.getByRole('option', { name: 'All' })).toBeVisible(); + expect(listBox.getByRole('option', { name: 'crew A' })).toBeVisible(); + expect(listBox.getByRole('option', { name: 'crew B' })).toBeVisible(); + }); it('should show error message when crews is null', async () => { - const { getByRole, getByText } = setup() + setup(); await act(async () => { - await userEvent.click(getByRole('combobox', { name: mockLabel })) - }) + await userEvent.click(screen.getByRole('combobox', { name: mockLabel })); + }); await act(async () => { - await userEvent.click(getByText('All')) - }) + await userEvent.click(screen.getByText('All')); + }); - const requiredText = getByText('required') - expect(requiredText.tagName).toBe('STRONG') - }) + const requiredText = screen.getByText('required'); + expect(requiredText.tagName).toBe('STRONG'); + }); it('should show other selections when cancel one option given default all selections in crews', async () => { - const { getByRole, queryByRole } = setup() + setup(); await act(async () => { - await userEvent.click(getByRole('combobox', { name: mockLabel })) - }) + await userEvent.click(screen.getByRole('combobox', { name: mockLabel })); + }); - const listBox = within(getByRole('listbox')) + const listBox = within(screen.getByRole('listbox')); await act(async () => { - await userEvent.click(listBox.getByRole('option', { name: mockOptions[0] })) - }) + await userEvent.click(listBox.getByRole('option', { name: mockOptions[0] })); + }); - expect(queryByRole('button', { name: mockOptions[0] })).not.toBeInTheDocument() - expect(queryByRole('button', { name: mockOptions[1] })).toBeInTheDocument() - }) + expect(screen.queryByRole('button', { name: mockOptions[0] })).not.toBeInTheDocument(); + expect(screen.queryByRole('button', { name: mockOptions[1] })).toBeInTheDocument(); + }); it('should clear crews data when check all option', async () => { - const { getByRole, queryByRole } = setup() + setup(); await act(async () => { - await userEvent.click(getByRole('combobox', { name: mockLabel })) - }) + await userEvent.click(screen.getByRole('combobox', { name: mockLabel })); + }); - const listBox = within(getByRole('listbox')) - const allOption = listBox.getByRole('option', { name: 'All' }) + const listBox = within(screen.getByRole('listbox')); + const allOption = listBox.getByRole('option', { name: 'All' }); await act(async () => { - await userEvent.click(allOption) - }) + await userEvent.click(allOption); + }); - expect(queryByRole('button', { name: mockOptions[0] })).not.toBeInTheDocument() - expect(queryByRole('button', { name: mockOptions[1] })).not.toBeInTheDocument() + expect(screen.queryByRole('button', { name: mockOptions[0] })).not.toBeInTheDocument(); + expect(screen.queryByRole('button', { name: mockOptions[1] })).not.toBeInTheDocument(); await act(async () => { - await userEvent.click(allOption) - }) + await userEvent.click(allOption); + }); - expect(queryByRole('button', { name: mockOptions[0] })).toBeInTheDocument() - expect(queryByRole('button', { name: mockOptions[1] })).toBeInTheDocument() - }, 50000) + expect(screen.queryByRole('button', { name: mockOptions[0] })).toBeInTheDocument(); + expect(screen.queryByRole('button', { name: mockOptions[1] })).toBeInTheDocument(); + }); it('should show radio group when render Crews component', async () => { - const { getByRole, getByText } = setup() + setup(); - expect(getByText(assigneeFilterLabels[0])).toBeInTheDocument() - expect(getByText(assigneeFilterLabels[1])).toBeInTheDocument() - expect(getByRole('radiogroup', { name: 'assigneeFilter' })).toBeVisible() - }) + expect(screen.getByText(assigneeFilterLabels[0])).toBeInTheDocument(); + expect(screen.getByText(assigneeFilterLabels[1])).toBeInTheDocument(); + expect(screen.getByRole('radiogroup', { name: 'assigneeFilter' })).toBeVisible(); + }); it('should show radio group with init value when render Crews component', async () => { - const { getAllByRole } = setup() + setup(); - const radioGroups = getAllByRole('radio') - const optionValues = radioGroups.map((option) => option.getAttribute('value')) - const checkedValues = radioGroups.map((option) => option.getAttribute('checked')) + const radioGroups = screen.getAllByRole('radio'); + const optionValues = radioGroups.map((option) => option.getAttribute('value')); + const checkedValues = radioGroups.map((option) => option.getAttribute('checked')); - const expectedCheckedValues = ['', null] - expect(optionValues).toEqual(assigneeFilterValues) - expect(checkedValues).toEqual(expectedCheckedValues) - }) + const expectedCheckedValues = ['', null]; + expect(optionValues).toEqual(assigneeFilterValues); + expect(checkedValues).toEqual(expectedCheckedValues); + }); it('should call update function when change radio option', async () => { - const { getByRole } = setup() + setup(); await act(async () => { - await userEvent.click(getByRole('radio', { name: assigneeFilterLabels[1] })) - }) + await userEvent.click(screen.getByRole('radio', { name: assigneeFilterLabels[1] })); + }); await waitFor(() => { - expect(mockedUseAppDispatch).toHaveBeenCalledTimes(2) - expect(mockedUseAppDispatch).toHaveBeenCalledWith(updateAssigneeFilter(assigneeFilterValues[1])) - }) - }) -}) + expect(mockedUseAppDispatch).toHaveBeenCalledTimes(2); + expect(mockedUseAppDispatch).toHaveBeenCalledWith(updateAssigneeFilter(assigneeFilterValues[1])); + }); + }); +}); diff --git a/frontend/__tests__/src/components/Metrics/MetricsStep/CycleTime.test.tsx b/frontend/__tests__/src/components/Metrics/MetricsStep/CycleTime.test.tsx index a336abc3b1..cc97a25b0b 100644 --- a/frontend/__tests__/src/components/Metrics/MetricsStep/CycleTime.test.tsx +++ b/frontend/__tests__/src/components/Metrics/MetricsStep/CycleTime.test.tsx @@ -1,14 +1,15 @@ -import { act, render, waitFor, within } from '@testing-library/react' -import { CycleTime } from '@src/components/Metrics/MetricsStep/CycleTime' -import userEvent from '@testing-library/user-event' -import { Provider } from 'react-redux' -import { setupStore } from '../../../utils/setupStoreUtil' -import { CYCLE_TIME_SETTINGS, ERROR_MESSAGE_TIME_DURATION, LIST_OPEN, NO_RESULT_DASH } from '../../../fixtures' -import { saveDoneColumn, updateTreatFlagCardAsBlock } from '@src/context/Metrics/metricsSlice' - -const FlagAsBlock = 'Consider the "Flag" as "Block"' - -let store = setupStore() +import React from 'react'; +import { act, render, waitFor, within, screen } from '@testing-library/react'; +import { CycleTime } from '@src/components/Metrics/MetricsStep/CycleTime'; +import userEvent from '@testing-library/user-event'; +import { Provider } from 'react-redux'; +import { setupStore } from '../../../utils/setupStoreUtil'; +import { CYCLE_TIME_SETTINGS, ERROR_MESSAGE_TIME_DURATION, LIST_OPEN, NO_RESULT_DASH } from '../../../fixtures'; +import { saveDoneColumn, updateTreatFlagCardAsBlock } from '@src/context/Metrics/metricsSlice'; + +const FlagAsBlock = 'Consider the "Flag" as "Block"'; + +let store = setupStore(); jest.mock('@src/context/Metrics/metricsSlice', () => ({ ...jest.requireActual('@src/context/Metrics/metricsSlice'), selectMetricsContent: jest.fn().mockReturnValue({ @@ -29,7 +30,7 @@ jest.mock('@src/context/Metrics/metricsSlice', () => ({ }), selectTreatFlagCardAsBlock: jest.fn().mockReturnValue(true), selectCycleTimeWarningMessage: jest.fn().mockReturnValue('Test warning Message'), -})) +})); jest.mock('@src/context/config/configSlice', () => ({ ...jest.requireActual('@src/context/config/configSlice'), selectJiraColumns: jest.fn().mockReturnValue([ @@ -37,77 +38,77 @@ jest.mock('@src/context/config/configSlice', () => ({ { key: 'Testing', value: { name: 'Testing', statuses: ['Test'] } }, { key: 'TODO', value: { name: 'TODO', statuses: ['To do'] } }, ]), -})) +})); -const mockedUseAppDispatch = jest.fn() +const mockedUseAppDispatch = jest.fn(); jest.mock('@src/hooks/useAppDispatch', () => ({ useAppDispatch: () => mockedUseAppDispatch, -})) +})); const setup = () => render( - ) + ); describe('CycleTime', () => { beforeEach(() => { - store = setupStore() - }) + store = setupStore(); + }); afterEach(() => { - jest.clearAllMocks() - }) + jest.clearAllMocks(); + }); describe('CycleTime Title', () => { it('should show Cycle Time title when render Crews component', () => { - const { getByText } = setup() - expect(getByText(CYCLE_TIME_SETTINGS)).toBeInTheDocument() - }) + setup(); + expect(screen.getByText(CYCLE_TIME_SETTINGS)).toBeInTheDocument(); + }); it('should show Cycle Time tooltip when render Crews component', () => { - const { getByTestId } = setup() - expect(getByTestId('InfoOutlinedIcon')).toBeInTheDocument() - }) - }) + setup(); + expect(screen.getByTestId('InfoOutlinedIcon')).toBeInTheDocument(); + }); + }); describe('CycleTime Selector List', () => { it('should show selectors title when render Crews component', () => { - const { getByText } = setup() + setup(); - expect(getByText('Analysis, In Dev, doing')).toBeInTheDocument() - expect(getByText('Test')).toBeInTheDocument() - expect(getByText('To do')).toBeInTheDocument() - }) + expect(screen.getByText('Analysis, In Dev, doing')).toBeInTheDocument(); + expect(screen.getByText('Test')).toBeInTheDocument(); + expect(screen.getByText('To do')).toBeInTheDocument(); + }); it('should always show board status column tooltip', async () => { - const { getByText, getByRole } = setup() - userEvent.hover(getByText('Analysis, In Dev, doing')) + setup(); + userEvent.hover(screen.getByText('Analysis, In Dev, doing')); await waitFor(() => { - expect(getByRole('tooltip', { name: 'Analysis, In Dev, doing' })).toBeVisible() - }) - }) + expect(screen.getByRole('tooltip', { name: 'Analysis, In Dev, doing' })).toBeVisible(); + }); + }); it('should show right input value when initializing', async () => { - const { getAllByRole } = setup() - const inputElements = getAllByRole('combobox') - const selectedInputValues = inputElements.map((input) => input.getAttribute('value')) + setup(); + const inputElements = screen.getAllByRole('combobox'); + const selectedInputValues = inputElements.map((input) => input.getAttribute('value')); - const expectedInputValues = ['Analysis', 'Review', NO_RESULT_DASH] + const expectedInputValues = ['Analysis', 'Review', NO_RESULT_DASH]; - expect(selectedInputValues).toEqual(expectedInputValues) - }) + expect(selectedInputValues).toEqual(expectedInputValues); + }); it('should show detail options when click included button', async () => { - const { getAllByRole, getByRole } = setup() - const columnsArray = getAllByRole('button', { name: LIST_OPEN }) + setup(); + const columnsArray = screen.getAllByRole('button', { name: LIST_OPEN }); await act(async () => { - await userEvent.click(columnsArray[0]) - }) - const listBox = within(getByRole('listbox')) - const options = listBox.getAllByRole('option') - const optionText = options.map((option) => option.textContent) + await userEvent.click(columnsArray[0]); + }); + const listBox = within(screen.getByRole('listbox')); + const options = listBox.getAllByRole('option'); + const optionText = options.map((option) => option.textContent); const expectedOptions = [ NO_RESULT_DASH, @@ -119,162 +120,162 @@ describe('CycleTime', () => { 'Testing', 'Review', 'Done', - ] + ]; expectedOptions.forEach((expectedOption) => { - expect(optionText).toContain(expectedOption) - }) - }) + expect(optionText).toContain(expectedOption); + }); + }); it('should show the right options when input the keyword to search', async () => { - const { getAllByRole, getByRole } = setup() - const columnsArray = getAllByRole('button', { name: LIST_OPEN }) + setup(); + const columnsArray = screen.getAllByRole('button', { name: LIST_OPEN }); await act(async () => { - await userEvent.type(columnsArray[0], 'Done') - }) - const listBox = within(getByRole('listbox')) - const options = listBox.getAllByRole('option') - const optionTexts = options.map((option) => option.textContent) + await userEvent.type(columnsArray[0], 'Done'); + }); + const listBox = within(screen.getByRole('listbox')); + const options = listBox.getAllByRole('option'); + const optionTexts = options.map((option) => option.textContent); - const expectedOptions = ['Done'] + const expectedOptions = ['Done']; - expect(optionTexts).toEqual(expectedOptions) - }) + expect(optionTexts).toEqual(expectedOptions); + }); it('should show no options when enter the wrong keyword', async () => { - const { getAllByRole, getByText } = setup() - const columnsArray = getAllByRole('button', { name: LIST_OPEN }) + setup(); + const columnsArray = screen.getAllByRole('button', { name: LIST_OPEN }); await act(async () => { - await userEvent.type(columnsArray[0], 'wrong keyword') - }) + await userEvent.type(columnsArray[0], 'wrong keyword'); + }); - expect(getByText('No options')).toBeInTheDocument() - }) + expect(screen.getByText('No options')).toBeInTheDocument(); + }); it('should show selected option when click the dropDown button ', async () => { - const { getAllByRole, getByRole } = setup() - const columnsArray = getAllByRole('button', { name: LIST_OPEN }) + setup(); + const columnsArray = screen.getAllByRole('button', { name: LIST_OPEN }); await act(async () => { - await userEvent.click(columnsArray[2]) - }) + await userEvent.click(columnsArray[2]); + }); - const listBox = within(getByRole('listbox')) - const options = listBox.getAllByRole('option') - const selectedOption = options.find((option) => option.getAttribute('aria-selected') === 'true') + const listBox = within(screen.getByRole('listbox')); + const options = listBox.getAllByRole('option'); + const selectedOption = options.find((option) => option.getAttribute('aria-selected') === 'true'); - const selectedOptionText = selectedOption?.textContent + const selectedOptionText = selectedOption?.textContent; - expect(selectedOptionText).toBe(NO_RESULT_DASH) - }) + expect(selectedOptionText).toBe(NO_RESULT_DASH); + }); it('should show other selections when change option and will not affect Real done', async () => { - const { getAllByRole, getByRole } = setup() - const columnsArray = getAllByRole('button', { name: LIST_OPEN }) + setup(); + const columnsArray = screen.getAllByRole('button', { name: LIST_OPEN }); await act(async () => { - await userEvent.click(columnsArray[2]) - }) + await userEvent.click(columnsArray[2]); + }); - const listBox = within(getByRole('listbox')) - const mockOptions = listBox.getAllByRole('option') + const listBox = within(screen.getByRole('listbox')); + const mockOptions = listBox.getAllByRole('option'); await act(async () => { - await userEvent.click(mockOptions[1]) - }) + await userEvent.click(mockOptions[1]); + }); - const inputElements = getAllByRole('combobox') - const selectedInputValue = inputElements.map((option) => option.getAttribute('value'))[2] + const inputElements = screen.getAllByRole('combobox'); + const selectedInputValue = inputElements.map((option) => option.getAttribute('value'))[2]; - expect(selectedInputValue).toBe('To do') - await waitFor(() => expect(mockedUseAppDispatch).not.toHaveBeenCalledWith(saveDoneColumn([]))) - }) + expect(selectedInputValue).toBe('To do'); + await waitFor(() => expect(mockedUseAppDispatch).not.toHaveBeenCalledWith(saveDoneColumn([]))); + }); it('should reset Real done when marked as done from other options', async () => { - const { getAllByRole, getByRole } = setup() - const columnsArray = getAllByRole('button', { name: LIST_OPEN }) + setup(); + const columnsArray = screen.getAllByRole('button', { name: LIST_OPEN }); await act(async () => { - await userEvent.click(columnsArray[0]) - }) + await userEvent.click(columnsArray[0]); + }); - const listBox = within(getByRole('listbox')) + const listBox = within(screen.getByRole('listbox')); await act(async () => { - await userEvent.click(listBox.getAllByRole('option')[8]) - }) + await userEvent.click(listBox.getAllByRole('option')[8]); + }); - const inputElements = getAllByRole('combobox') + const inputElements = screen.getAllByRole('combobox'); - const selectedInputValue = inputElements.map((option) => option.getAttribute('value'))[0] + const selectedInputValue = inputElements.map((option) => option.getAttribute('value'))[0]; - expect(selectedInputValue).toBe('Done') - await waitFor(() => expect(mockedUseAppDispatch).toHaveBeenCalledWith(saveDoneColumn([]))) - }) + expect(selectedInputValue).toBe('Done'); + await waitFor(() => expect(mockedUseAppDispatch).toHaveBeenCalledWith(saveDoneColumn([]))); + }); it('should show the right selected value when cancel the done', async () => { - const { getAllByRole, getByRole } = setup() - const columnsArray = getAllByRole('button', { name: LIST_OPEN }) + setup(); + const columnsArray = screen.getAllByRole('button', { name: LIST_OPEN }); await act(async () => { - await userEvent.click(columnsArray[0]) - }) + await userEvent.click(columnsArray[0]); + }); - const listBox = within(getByRole('listbox')) + const listBox = within(screen.getByRole('listbox')); await act(async () => { - await userEvent.click(listBox.getAllByRole('option')[8]) - }) + await userEvent.click(listBox.getAllByRole('option')[8]); + }); await act(async () => { - await userEvent.click(columnsArray[0]) - }) + await userEvent.click(columnsArray[0]); + }); - const newListBox = within(getByRole('listbox')) + const newListBox = within(screen.getByRole('listbox')); await act(async () => { - await userEvent.click(newListBox.getAllByRole('option')[7]) - }) + await userEvent.click(newListBox.getAllByRole('option')[7]); + }); - const inputElements = getAllByRole('combobox') - const selectedInputValue = inputElements.map((option) => option.getAttribute('value'))[0] + const inputElements = screen.getAllByRole('combobox'); + const selectedInputValue = inputElements.map((option) => option.getAttribute('value'))[0]; - expect(selectedInputValue).toBe('Review') - await waitFor(() => expect(mockedUseAppDispatch).toHaveBeenCalledWith(saveDoneColumn([]))) - }) - }) + expect(selectedInputValue).toBe('Review'); + await waitFor(() => expect(mockedUseAppDispatch).toHaveBeenCalledWith(saveDoneColumn([]))); + }); + }); describe('CycleTime Flag as Block', () => { it('should show FlagAsBlock when render Crews component', () => { - const { getByText } = setup() - expect(getByText(FlagAsBlock)).toBeInTheDocument() - }) + setup(); + expect(screen.getByText(FlagAsBlock)).toBeInTheDocument(); + }); it('should be checked by default when initializing', () => { - const { getByRole } = setup() - expect(getByRole('checkbox')).toHaveProperty('checked', true) - }) + setup(); + expect(screen.getByRole('checkbox')).toHaveProperty('checked', true); + }); it('should change checked when click', async () => { - const { getByRole } = setup() + setup(); await act(async () => { - await userEvent.click(getByRole('checkbox')) - }) + await userEvent.click(screen.getByRole('checkbox')); + }); await waitFor(() => { - expect(mockedUseAppDispatch).toHaveBeenCalledWith(updateTreatFlagCardAsBlock(false)) - }) - }) - }) + expect(mockedUseAppDispatch).toHaveBeenCalledWith(updateTreatFlagCardAsBlock(false)); + }); + }); + }); it('should show warning message when selectWarningMessage has a value in cycleTime component', () => { - const { getByText } = setup() + setup(); - expect(getByText('Test warning Message')).toBeVisible() - }) + expect(screen.getByText('Test warning Message')).toBeVisible(); + }); it('should show disable warning message when selectWarningMessage has a value after two seconds in cycleTime component', async () => { - jest.useFakeTimers() - const { queryByText } = setup() + jest.useFakeTimers(); + setup(); act(() => { - jest.advanceTimersByTime(ERROR_MESSAGE_TIME_DURATION) - }) + jest.advanceTimersByTime(ERROR_MESSAGE_TIME_DURATION); + }); await waitFor(() => { - expect(queryByText('Test warning Message')).not.toBeInTheDocument() - }) - }) -}) + expect(screen.queryByText('Test warning Message')).not.toBeInTheDocument(); + }); + }); +}); diff --git a/frontend/__tests__/src/components/Metrics/MetricsStep/DeploymentFrequencySettings/DeploymentFrequencySettings.test.tsx b/frontend/__tests__/src/components/Metrics/MetricsStep/DeploymentFrequencySettings/DeploymentFrequencySettings.test.tsx index 69ef47dad3..98dd1d32a0 100644 --- a/frontend/__tests__/src/components/Metrics/MetricsStep/DeploymentFrequencySettings/DeploymentFrequencySettings.test.tsx +++ b/frontend/__tests__/src/components/Metrics/MetricsStep/DeploymentFrequencySettings/DeploymentFrequencySettings.test.tsx @@ -1,19 +1,19 @@ -import { render, within, screen } from '@testing-library/react' -import { Provider } from 'react-redux' -import { store } from '@src/store' -import userEvent from '@testing-library/user-event' -import { DeploymentFrequencySettings } from '@src/components/Metrics/MetricsStep/DeploymentFrequencySettings' +import { render, within, screen } from '@testing-library/react'; +import { Provider } from 'react-redux'; +import { store } from '@src/store'; +import userEvent from '@testing-library/user-event'; +import { DeploymentFrequencySettings } from '@src/components/Metrics/MetricsStep/DeploymentFrequencySettings'; import { addADeploymentFrequencySetting, deleteADeploymentFrequencySetting, updateDeploymentFrequencySettings, -} from '@src/context/Metrics/metricsSlice' -import { DEPLOYMENT_FREQUENCY_SETTINGS, LIST_OPEN, ORGANIZATION, REMOVE_BUTTON } from '../../../../fixtures' +} from '@src/context/Metrics/metricsSlice'; +import { DEPLOYMENT_FREQUENCY_SETTINGS, LIST_OPEN, ORGANIZATION, REMOVE_BUTTON } from '../../../../fixtures'; jest.mock('@src/hooks', () => ({ ...jest.requireActual('@src/hooks'), useAppDispatch: () => jest.fn(), -})) +})); jest.mock('@src/context/Metrics/metricsSlice', () => ({ ...jest.requireActual('@src/context/Metrics/metricsSlice'), @@ -28,7 +28,7 @@ jest.mock('@src/context/Metrics/metricsSlice', () => ({ selectPipelineNameWarningMessage: jest.fn().mockReturnValue(null), selectStepWarningMessage: jest.fn().mockReturnValue(null), selectMetricsContent: jest.fn().mockReturnValue({ pipelineCrews: [], users: [] }), -})) +})); jest.mock('@src/context/config/configSlice', () => ({ ...jest.requireActual('@src/context/config/configSlice'), @@ -37,16 +37,16 @@ jest.mock('@src/context/config/configSlice', () => ({ selectSteps: jest.fn().mockReturnValue(['']), selectBranches: jest.fn().mockReturnValue(['']), selectPipelineCrews: jest.fn().mockReturnValue(['']), -})) +})); const mockValidationCheckContext = { isPipelineValid: jest.fn().mockReturnValue(true), getDuplicatedPipeLineIds: jest.fn().mockReturnValue([]), -} +}; jest.mock('@src/hooks/useMetricsStepValidationCheckContext', () => ({ useMetricsStepValidationCheckContext: () => mockValidationCheckContext, -})) +})); describe('DeploymentFrequencySettings', () => { const setup = () => @@ -54,38 +54,38 @@ describe('DeploymentFrequencySettings', () => { - ) + ); afterEach(() => { - jest.clearAllMocks() - }) + jest.clearAllMocks(); + }); it('should render DeploymentFrequencySettings component', () => { - const { getByText, getAllByText } = setup() + const { getByText, getAllByText } = setup(); - expect(getByText(DEPLOYMENT_FREQUENCY_SETTINGS)).toBeInTheDocument() - expect(getAllByText(ORGANIZATION).length).toBe(2) - }) + expect(getByText(DEPLOYMENT_FREQUENCY_SETTINGS)).toBeInTheDocument(); + expect(getAllByText(ORGANIZATION).length).toBe(2); + }); it('should call addADeploymentFrequencySetting function when click add another pipeline button', async () => { - setup() - await userEvent.click(screen.getByTestId('AddIcon')) + setup(); + await userEvent.click(screen.getByTestId('AddIcon')); - expect(addADeploymentFrequencySetting).toHaveBeenCalledTimes(1) - }) + expect(addADeploymentFrequencySetting).toHaveBeenCalledTimes(1); + }); it('should call deleteADeploymentFrequencySetting function when click remove pipeline button', async () => { - setup() - await userEvent.click(screen.getAllByRole('button', { name: REMOVE_BUTTON })[0]) + setup(); + await userEvent.click(screen.getAllByRole('button', { name: REMOVE_BUTTON })[0]); - expect(deleteADeploymentFrequencySetting).toHaveBeenCalledTimes(1) - }) + expect(deleteADeploymentFrequencySetting).toHaveBeenCalledTimes(1); + }); it('should call updateDeploymentFrequencySetting function and clearErrorMessages function when select organization', async () => { - const { getByRole } = setup() - await userEvent.click(screen.getAllByRole('button', { name: LIST_OPEN })[0]) - const listBox = within(getByRole('listbox')) - await userEvent.click(listBox.getByText('mockOrgName')) + const { getByRole } = setup(); + await userEvent.click(screen.getAllByRole('button', { name: LIST_OPEN })[0]); + const listBox = within(getByRole('listbox')); + await userEvent.click(listBox.getByText('mockOrgName')); - expect(updateDeploymentFrequencySettings).toHaveBeenCalledTimes(1) - }) -}) + expect(updateDeploymentFrequencySettings).toHaveBeenCalledTimes(1); + }); +}); diff --git a/frontend/__tests__/src/components/Metrics/MetricsStep/DeploymentFrequencySettings/PipelineMetricSelection.test.tsx b/frontend/__tests__/src/components/Metrics/MetricsStep/DeploymentFrequencySettings/PipelineMetricSelection.test.tsx index ce6851b943..f8e4d7ee68 100644 --- a/frontend/__tests__/src/components/Metrics/MetricsStep/DeploymentFrequencySettings/PipelineMetricSelection.test.tsx +++ b/frontend/__tests__/src/components/Metrics/MetricsStep/DeploymentFrequencySettings/PipelineMetricSelection.test.tsx @@ -1,10 +1,10 @@ -import { act, render, waitFor, within } from '@testing-library/react' -import userEvent from '@testing-library/user-event' -import { Provider } from 'react-redux' -import { setupStore } from '../../../../utils/setupStoreUtil' -import { PipelineMetricSelection } from '@src/components/Metrics/MetricsStep/DeploymentFrequencySettings/PipelineMetricSelection' -import { metricsClient } from '@src/clients/MetricsClient' -import { updatePipelineToolVerifyResponseSteps } from '@src/context/config/configSlice' +import { act, render, waitFor, within, screen } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; +import { Provider } from 'react-redux'; +import { setupStore } from '../../../../utils/setupStoreUtil'; +import { PipelineMetricSelection } from '@src/components/Metrics/MetricsStep/DeploymentFrequencySettings/PipelineMetricSelection'; +import { metricsClient } from '@src/clients/MetricsClient'; +import { updatePipelineToolVerifyResponseSteps } from '@src/context/config/configSlice'; import { BRANCH, ERROR_MESSAGE_TIME_DURATION, @@ -14,8 +14,8 @@ import { PIPELINE_SETTING_TYPES, REMOVE_BUTTON, STEP, -} from '../../../../fixtures' -import { PipelineSetting } from '@src/context/interface' +} from '../../../../fixtures'; +import { PipelineSetting } from '@src/context/interface'; jest.mock('@src/context/Metrics/metricsSlice', () => ({ ...jest.requireActual('@src/context/Metrics/metricsSlice'), @@ -23,7 +23,7 @@ jest.mock('@src/context/Metrics/metricsSlice', () => ({ selectOrganizationWarningMessage: jest.fn().mockReturnValue('Test organization warning message'), selectPipelineNameWarningMessage: jest.fn().mockReturnValue('Test pipelineName warning message'), selectStepWarningMessage: jest.fn().mockReturnValue('Test step warning message'), -})) +})); jest.mock('@src/context/config/configSlice', () => ({ ...jest.requireActual('@src/context/config/configSlice'), @@ -47,26 +47,26 @@ jest.mock('@src/context/config/configSlice', () => ({ updatePipelineToolVerifyResponseSteps: jest .fn() .mockReturnValue({ type: 'UPDATE_PIPELINE_TOOL_VERIFY_RESPONSE_STEPS' }), -})) +})); describe('PipelineMetricSelection', () => { - const mockId = 0 + const mockId = 0; const deploymentFrequencySetting = { id: 0, organization: '', pipelineName: '', step: '', branches: [], - } - const mockHandleClickRemoveButton = jest.fn() - const mockUpdatePipeline = jest.fn() + }; + const mockHandleClickRemoveButton = jest.fn(); + const mockUpdatePipeline = jest.fn(); const setup = async ( deploymentFrequencySetting: PipelineSetting, isShowRemoveButton: boolean, isDuplicated: boolean ) => { - const store = setupStore() + const store = setupStore(); return render( { isDuplicated={isDuplicated} /> - ) - } + ); + }; beforeEach(() => { - jest.clearAllMocks() - }) + jest.clearAllMocks(); + }); it('should render PipelineMetricSelection when isShowRemoveButton is true', async () => { - const { getByText } = await setup(deploymentFrequencySetting, true, false) + await setup(deploymentFrequencySetting, true, false); - expect(getByText(REMOVE_BUTTON)).toBeInTheDocument() - expect(getByText(ORGANIZATION)).toBeInTheDocument() - }) + expect(screen.getByText(REMOVE_BUTTON)).toBeInTheDocument(); + expect(screen.getByText(ORGANIZATION)).toBeInTheDocument(); + }); it('should render PipelineMetricSelection when isShowRemoveButton is false', async () => { - const { getByText, queryByText } = await setup(deploymentFrequencySetting, false, false) + const { getByText, queryByText } = await setup(deploymentFrequencySetting, false, false); - expect(queryByText(REMOVE_BUTTON)).not.toBeInTheDocument() - expect(getByText(ORGANIZATION)).toBeInTheDocument() - }) + expect(queryByText(REMOVE_BUTTON)).not.toBeInTheDocument(); + expect(getByText(ORGANIZATION)).toBeInTheDocument(); + }); it('should call deleteADeploymentFrequencySetting function when click remove this pipeline button', async () => { - const { getByRole } = await setup(deploymentFrequencySetting, true, false) + const { getByRole } = await setup(deploymentFrequencySetting, true, false); await act(async () => { - await userEvent.click(getByRole('button', { name: REMOVE_BUTTON })) - }) + await userEvent.click(getByRole('button', { name: REMOVE_BUTTON })); + }); - expect(mockHandleClickRemoveButton).toHaveBeenCalledTimes(1) - expect(mockHandleClickRemoveButton).toHaveBeenCalledWith(mockId) - }) + expect(mockHandleClickRemoveButton).toHaveBeenCalledTimes(1); + expect(mockHandleClickRemoveButton).toHaveBeenCalledWith(mockId); + }); it('should show pipelineName selection when select organization', async () => { - const { getByText } = await setup({ ...deploymentFrequencySetting, organization: 'mockOrgName' }, false, false) + const { getByText } = await setup({ ...deploymentFrequencySetting, organization: 'mockOrgName' }, false, false); - expect(getByText(ORGANIZATION)).toBeInTheDocument() - expect(getByText(PIPELINE_NAME)).toBeInTheDocument() - }) + expect(getByText(ORGANIZATION)).toBeInTheDocument(); + expect(getByText(PIPELINE_NAME)).toBeInTheDocument(); + }); it('should show step selection when select organization and pipelineName', async () => { - metricsClient.getSteps = jest.fn().mockImplementation(() => ['steps1', 'steps2']) + metricsClient.getSteps = jest.fn().mockImplementation(() => ['steps1', 'steps2']); const { getByText } = await setup( { ...deploymentFrequencySetting, organization: 'mockOrgName', pipelineName: 'mockName' }, false, false - ) + ); - expect(getByText(ORGANIZATION)).toBeInTheDocument() - expect(getByText(PIPELINE_NAME)).toBeInTheDocument() - expect(getByText(BRANCH)).toBeInTheDocument() - expect(getByText(STEP)).toBeInTheDocument() - }) + expect(getByText(ORGANIZATION)).toBeInTheDocument(); + expect(getByText(PIPELINE_NAME)).toBeInTheDocument(); + expect(getByText(BRANCH)).toBeInTheDocument(); + expect(getByText(STEP)).toBeInTheDocument(); + }); it('should show error message pop when getSteps failed', async () => { metricsClient.getSteps = jest.fn().mockImplementation(() => { - throw new Error('error message') - }) + throw new Error('error message'); + }); const { getByText, getByRole, getAllByRole } = await setup( { id: 0, organization: 'mockOrgName', pipelineName: 'mockName', step: '', branches: [] }, false, false - ) + ); await act(async () => { - await userEvent.click(getAllByRole('button', { name: LIST_OPEN })[1]) - }) + await userEvent.click(getAllByRole('button', { name: LIST_OPEN })[1]); + }); - const listBox = within(getByRole('listbox')) + const listBox = within(getByRole('listbox')); await act(async () => { - await userEvent.click(listBox.getByText('mockName2')) - }) + await userEvent.click(listBox.getByText('mockName2')); + }); await waitFor(() => { - expect(getByText('BuildKite get steps failed: error message')).toBeInTheDocument() - }) - expect(mockUpdatePipeline).toHaveBeenCalledTimes(2) - }) + expect(getByText('BuildKite get steps failed: error message')).toBeInTheDocument(); + }); + expect(mockUpdatePipeline).toHaveBeenCalledTimes(2); + }); it('should show no steps warning message when getSteps succeed but get no steps', async () => { - metricsClient.getSteps = jest.fn().mockReturnValue({ response: [], haveStep: false }) + metricsClient.getSteps = jest.fn().mockReturnValue({ response: [], haveStep: false }); const { getByText, getByRole, getAllByRole } = await setup( { id: 0, organization: 'mockOrgName', pipelineName: 'mockName', step: '', branches: [] }, false, false - ) + ); await act(async () => { - await userEvent.click(getAllByRole('button', { name: LIST_OPEN })[1]) - }) + await userEvent.click(getAllByRole('button', { name: LIST_OPEN })[1]); + }); - const listBox = within(getByRole('listbox')) + const listBox = within(getByRole('listbox')); await act(async () => { - await userEvent.click(listBox.getByText('mockName2')) - }) + await userEvent.click(listBox.getByText('mockName2')); + }); await waitFor(() => { expect( getByText( 'There is no step during this period for this pipeline! Please change the search time in the Config page!' ) - ).toBeInTheDocument() - }) - }) + ).toBeInTheDocument(); + }); + }); it('should show no steps warning message when getSteps succeed but get no steps and isShowRemoveButton is true', async () => { - metricsClient.getSteps = jest.fn().mockReturnValue({ response: [], haveStep: false }) + metricsClient.getSteps = jest.fn().mockReturnValue({ response: [], haveStep: false }); const { getByRole, getAllByRole } = await setup( { id: 0, organization: 'mockOrgName', pipelineName: 'mockName', step: '', branches: [] }, true, false - ) + ); await act(async () => { - await userEvent.click(getAllByRole('button', { name: LIST_OPEN })[1]) - }) + await userEvent.click(getAllByRole('button', { name: LIST_OPEN })[1]); + }); - const listBox = within(getByRole('listbox')) + const listBox = within(getByRole('listbox')); await act(async () => { - await userEvent.click(listBox.getByText('mockName2')) - }) + await userEvent.click(listBox.getByText('mockName2')); + }); await waitFor(() => { - expect(mockHandleClickRemoveButton).toHaveBeenCalledTimes(2) - }) - }) + expect(mockHandleClickRemoveButton).toHaveBeenCalledTimes(2); + }); + }); it('should show steps selection when getSteps succeed ', async () => { - metricsClient.getSteps = jest.fn().mockReturnValue({ response: ['steps'], haveStep: true }) + metricsClient.getSteps = jest.fn().mockReturnValue({ response: ['steps'], haveStep: true }); const { getByRole, getByText, getAllByRole } = await setup( { id: 0, organization: 'mockOrgName', pipelineName: 'mockName', step: '', branches: [] }, false, false - ) + ); await waitFor(() => { - expect(updatePipelineToolVerifyResponseSteps).toHaveBeenCalledTimes(1) - expect(getByText(STEP)).toBeInTheDocument() - }) + expect(updatePipelineToolVerifyResponseSteps).toHaveBeenCalledTimes(1); + expect(getByText(STEP)).toBeInTheDocument(); + }); await act(async () => { - await userEvent.click(getAllByRole('button', { name: LIST_OPEN })[2]) - }) + await userEvent.click(getAllByRole('button', { name: LIST_OPEN })[2]); + }); - const stepsListBox = within(getByRole('listbox')) + const stepsListBox = within(getByRole('listbox')); await act(async () => { - await userEvent.click(stepsListBox.getByText('step2')) - }) + await userEvent.click(stepsListBox.getByText('step2')); + }); - expect(mockUpdatePipeline).toHaveBeenCalledTimes(1) - }) + expect(mockUpdatePipeline).toHaveBeenCalledTimes(1); + }); it('should show branches selection when getSteps succeed ', async () => { metricsClient.getSteps = jest .fn() - .mockReturnValue({ response: ['steps'], haveStep: true, branches: ['branch1', 'branch2'] }) + .mockReturnValue({ response: ['steps'], haveStep: true, branches: ['branch1', 'branch2'] }); const { getByRole, getByText } = await setup( { id: 0, organization: 'mockOrgName', pipelineName: 'mockName', step: '', branches: ['branch1', 'branch2'] }, false, false - ) + ); await waitFor(() => { - expect(updatePipelineToolVerifyResponseSteps).toHaveBeenCalledTimes(1) - expect(getByText(BRANCH)).toBeInTheDocument() - }) + expect(updatePipelineToolVerifyResponseSteps).toHaveBeenCalledTimes(1); + expect(getByText(BRANCH)).toBeInTheDocument(); + }); await act(async () => { - await userEvent.click(getByRole('combobox', { name: 'Branches' })) - }) + await userEvent.click(getByRole('combobox', { name: 'Branches' })); + }); - const branchesListBox = within(getByRole('listbox')) - const allOption = branchesListBox.getByRole('option', { name: 'All' }) + const branchesListBox = within(getByRole('listbox')); + const allOption = branchesListBox.getByRole('option', { name: 'All' }); await act(async () => { - await userEvent.click(allOption) - }) + await userEvent.click(allOption); + }); - expect(getByRole('button', { name: 'branch1' })).toBeInTheDocument() - expect(getByRole('button', { name: 'branch2' })).toBeInTheDocument() + expect(getByRole('button', { name: 'branch1' })).toBeInTheDocument(); + expect(getByRole('button', { name: 'branch2' })).toBeInTheDocument(); await act(async () => { - await userEvent.click(allOption) - }) + await userEvent.click(allOption); + }); - expect(getByRole('button', { name: 'branch1' })).toBeInTheDocument() - expect(getByRole('button', { name: 'branch2' })).toBeInTheDocument() + expect(getByRole('button', { name: 'branch1' })).toBeInTheDocument(); + expect(getByRole('button', { name: 'branch2' })).toBeInTheDocument(); - expect(mockUpdatePipeline).toHaveBeenCalledTimes(2) - }) + expect(mockUpdatePipeline).toHaveBeenCalledTimes(2); + }); it('should show duplicated message given duplicated id', async () => { - metricsClient.getSteps = jest.fn().mockReturnValue({ response: ['steps'], haveStep: true }) + metricsClient.getSteps = jest.fn().mockReturnValue({ response: ['steps'], haveStep: true }); const { getByText } = await setup( { id: 0, organization: 'mockOrgName', pipelineName: 'mockName', step: 'step1', branches: [] }, false, true - ) + ); - expect(getByText('This pipeline is the same as another one!')).toBeInTheDocument() - }) + expect(getByText('This pipeline is the same as another one!')).toBeInTheDocument(); + }); it('should show warning message when organization and pipelineName warning messages have value', async () => { - const { getByText } = await setup(deploymentFrequencySetting, false, false) + const { getByText } = await setup(deploymentFrequencySetting, false, false); - expect(getByText('Test organization warning message')).toBeInTheDocument() - expect(getByText('Test pipelineName warning message')).toBeInTheDocument() - expect(getByText('Test step warning message')).toBeInTheDocument() - }) + expect(getByText('Test organization warning message')).toBeInTheDocument(); + expect(getByText('Test pipelineName warning message')).toBeInTheDocument(); + expect(getByText('Test step warning message')).toBeInTheDocument(); + }); it('should clear warning message when organization and pipelineName warning messages have value after four seconds', async () => { - jest.useFakeTimers() - const { queryByText } = await setup(deploymentFrequencySetting, false, false) + jest.useFakeTimers(); + const { queryByText } = await setup(deploymentFrequencySetting, false, false); act(() => { - jest.advanceTimersByTime(ERROR_MESSAGE_TIME_DURATION) - }) + jest.advanceTimersByTime(ERROR_MESSAGE_TIME_DURATION); + }); await waitFor(() => { - expect(queryByText('Test organization warning message')).not.toBeInTheDocument() - expect(queryByText('Test pipelineName warning message')).not.toBeInTheDocument() - expect(queryByText('Test step warning message')).not.toBeInTheDocument() - }) - }) -}) + expect(queryByText('Test organization warning message')).not.toBeInTheDocument(); + expect(queryByText('Test pipelineName warning message')).not.toBeInTheDocument(); + expect(queryByText('Test step warning message')).not.toBeInTheDocument(); + }); + }); +}); diff --git a/frontend/__tests__/src/components/Metrics/MetricsStep/DeploymentFrequencySettings/SingleSelection.test.tsx b/frontend/__tests__/src/components/Metrics/MetricsStep/DeploymentFrequencySettings/SingleSelection.test.tsx index 41b4a5e736..144c8d4d7b 100644 --- a/frontend/__tests__/src/components/Metrics/MetricsStep/DeploymentFrequencySettings/SingleSelection.test.tsx +++ b/frontend/__tests__/src/components/Metrics/MetricsStep/DeploymentFrequencySettings/SingleSelection.test.tsx @@ -1,38 +1,38 @@ -import { act, render, within } from '@testing-library/react' -import { SingleSelection } from '@src/components/Metrics/MetricsStep/DeploymentFrequencySettings/SingleSelection' -import userEvent from '@testing-library/user-event' -import { Provider } from 'react-redux' -import { setupStore } from '../../../../utils/setupStoreUtil' -import { LIST_OPEN } from '../../../../fixtures' +import { act, render, within } from '@testing-library/react'; +import { SingleSelection } from '@src/components/Metrics/MetricsStep/DeploymentFrequencySettings/SingleSelection'; +import userEvent from '@testing-library/user-event'; +import { Provider } from 'react-redux'; +import { setupStore } from '../../../../utils/setupStoreUtil'; +import { LIST_OPEN } from '../../../../fixtures'; const mockValidationCheckContext = { checkDuplicatedPipeLine: jest.fn(), checkPipelineValidation: jest.fn(), -} +}; jest.mock('@src/hooks/useMetricsStepValidationCheckContext', () => ({ useMetricsStepValidationCheckContext: () => mockValidationCheckContext, -})) +})); jest.mock('react', () => ({ ...jest.requireActual('react'), useEffect: jest.fn(), -})) -let store = setupStore() +})); +let store = setupStore(); describe('SingleSelection', () => { - const mockOptions = ['mockOptions 1', 'mockOptions 2', 'mockOptions 3'] - const mockLabel = 'mockLabel' - const mockValue = 'mockOptions 1' - const mockOnGetSteps = jest.fn() - const mockUpdatePipeline = jest.fn() + const mockOptions = ['mockOptions 1', 'mockOptions 2', 'mockOptions 3']; + const mockLabel = 'mockLabel'; + const mockValue = 'mockOptions 1'; + const mockOnGetSteps = jest.fn(); + const mockUpdatePipeline = jest.fn(); beforeEach(() => { - store = setupStore() - }) + store = setupStore(); + }); afterEach(() => { - jest.clearAllMocks() - }) + jest.clearAllMocks(); + }); const setup = () => render( @@ -46,71 +46,71 @@ describe('SingleSelection', () => { onUpDatePipeline={mockUpdatePipeline} /> - ) + ); it('should show selected label and value when render a SingleSelection', () => { - const { getByText, getAllByRole } = setup() - const inputElements = getAllByRole('combobox') + const { getByText, getAllByRole } = setup(); + const inputElements = getAllByRole('combobox'); - const selectedInputValues = inputElements.map((input) => input.getAttribute('value')) + const selectedInputValues = inputElements.map((input) => input.getAttribute('value')); - expect(getByText(mockLabel)).toBeInTheDocument() - expect(selectedInputValues).toEqual([mockValue]) - }) + expect(getByText(mockLabel)).toBeInTheDocument(); + expect(selectedInputValues).toEqual([mockValue]); + }); it('should show detail options when click the dropdown button', async () => { - const { getAllByRole, getByRole } = setup() - const buttonElements = getAllByRole('button', { name: LIST_OPEN }) + const { getAllByRole, getByRole } = setup(); + const buttonElements = getAllByRole('button', { name: LIST_OPEN }); await act(async () => { - await userEvent.click(buttonElements[0]) - }) - const listBox = within(getByRole('listbox')) - const options = listBox.getAllByRole('option') - const optionText = options.map((option) => option.textContent) + await userEvent.click(buttonElements[0]); + }); + const listBox = within(getByRole('listbox')); + const options = listBox.getAllByRole('option'); + const optionText = options.map((option) => option.textContent); - expect(optionText).toEqual(mockOptions) - }) + expect(optionText).toEqual(mockOptions); + }); it('should show the right options when search the keyword', async () => { - const { getAllByRole, getByRole } = setup() - const buttonElements = getAllByRole('button', { name: LIST_OPEN }) + const { getAllByRole, getByRole } = setup(); + const buttonElements = getAllByRole('button', { name: LIST_OPEN }); await act(async () => { - await userEvent.type(buttonElements[0], '1') - }) + await userEvent.type(buttonElements[0], '1'); + }); - const listBox = within(getByRole('listbox')) - const options = listBox.getAllByRole('option') - const optionTexts = options.map((option) => option.textContent) + const listBox = within(getByRole('listbox')); + const options = listBox.getAllByRole('option'); + const optionTexts = options.map((option) => option.textContent); - const expectedOptions = ['mockOptions 1'] + const expectedOptions = ['mockOptions 1']; - expect(optionTexts).toEqual(expectedOptions) - }) + expect(optionTexts).toEqual(expectedOptions); + }); it('should show no options when search the wrong keyword', async () => { - const { getAllByRole, getByText } = setup() - const buttonElements = getAllByRole('button', { name: LIST_OPEN }) + const { getAllByRole, getByText } = setup(); + const buttonElements = getAllByRole('button', { name: LIST_OPEN }); await act(async () => { - await userEvent.type(buttonElements[0], 'wrong keyword') - }) + await userEvent.type(buttonElements[0], 'wrong keyword'); + }); - expect(getByText('No options')).toBeInTheDocument() - }) + expect(getByText('No options')).toBeInTheDocument(); + }); it('should call update option function and OnGetSteps function when change option given mockValue as default', async () => { - const { getByText, getByRole } = setup() + const { getByText, getByRole } = setup(); await act(async () => { - await userEvent.click(getByRole('button', { name: LIST_OPEN })) - }) + await userEvent.click(getByRole('button', { name: LIST_OPEN })); + }); await act(async () => { - await userEvent.click(getByText(mockOptions[1])) - }) + await userEvent.click(getByText(mockOptions[1])); + }); - expect(mockOnGetSteps).toHaveBeenCalledTimes(1) - expect(mockUpdatePipeline).toHaveBeenCalledTimes(2) - }) -}) + expect(mockOnGetSteps).toHaveBeenCalledTimes(1); + expect(mockUpdatePipeline).toHaveBeenCalledTimes(2); + }); +}); diff --git a/frontend/__tests__/src/components/Metrics/MetricsStep/MetricsStep.test.tsx b/frontend/__tests__/src/components/Metrics/MetricsStep/MetricsStep.test.tsx index abb74a2fac..11e5f13ebf 100644 --- a/frontend/__tests__/src/components/Metrics/MetricsStep/MetricsStep.test.tsx +++ b/frontend/__tests__/src/components/Metrics/MetricsStep/MetricsStep.test.tsx @@ -1,9 +1,9 @@ -import { act, render, renderHook, waitFor, within } from '@testing-library/react' -import MetricsStep from '@src/components/Metrics/MetricsStep' -import { Provider } from 'react-redux' -import { setupStore } from '../../../utils/setupStoreUtil' +import { act, render, renderHook, waitFor, within } from '@testing-library/react'; +import MetricsStep from '@src/components/Metrics/MetricsStep'; +import { Provider } from 'react-redux'; +import { setupStore } from '../../../utils/setupStoreUtil'; -import { updateJiraVerifyResponse, updateMetrics } from '@src/context/config/configSlice' +import { updateJiraVerifyResponse, updateMetrics } from '@src/context/config/configSlice'; import { CLASSIFICATION_SETTING, CREWS_SETTING, @@ -16,74 +16,74 @@ import { REAL_DONE_SETTING_SECTION, REQUIRED_DATA_LIST, SELECT_CONSIDER_AS_DONE_MESSAGE, -} from '../../../fixtures' -import { saveCycleTimeSettings, saveDoneColumn } from '@src/context/Metrics/metricsSlice' -import { useNotificationLayoutEffect } from '@src/hooks/useNotificationLayoutEffect' -import userEvent from '@testing-library/user-event' +} from '../../../fixtures'; +import { saveCycleTimeSettings, saveDoneColumn } from '@src/context/Metrics/metricsSlice'; +import { useNotificationLayoutEffect } from '@src/hooks/useNotificationLayoutEffect'; +import userEvent from '@testing-library/user-event'; -let store = setupStore() +let store = setupStore(); const setup = () => render( - ) + ); describe('MetricsStep', () => { beforeEach(() => { - store = setupStore() - }) + store = setupStore(); + }); - const { result } = renderHook(() => useNotificationLayoutEffect()) + const { result } = renderHook(() => useNotificationLayoutEffect()); it('should render Crews when select velocity, and show Real done when have done column in Cycle time', async () => { - store.dispatch(updateMetrics([REQUIRED_DATA_LIST[1]])) - const { getByText, queryByText } = setup() + store.dispatch(updateMetrics([REQUIRED_DATA_LIST[1]])); + const { getByText, queryByText } = setup(); - expect(getByText(CREWS_SETTING)).toBeInTheDocument() - expect(queryByText(CYCLE_TIME_SETTINGS)).not.toBeInTheDocument() - expect(queryByText(CLASSIFICATION_SETTING)).not.toBeInTheDocument() + expect(getByText(CREWS_SETTING)).toBeInTheDocument(); + expect(queryByText(CYCLE_TIME_SETTINGS)).not.toBeInTheDocument(); + expect(queryByText(CLASSIFICATION_SETTING)).not.toBeInTheDocument(); act(() => { - store.dispatch(saveCycleTimeSettings([{ name: 'Testing', value: 'Done' }])) - }) + store.dispatch(saveCycleTimeSettings([{ name: 'Testing', value: 'Done' }])); + }); - expect(getByText(REAL_DONE)).toBeInTheDocument() - }) + expect(getByText(REAL_DONE)).toBeInTheDocument(); + }); it('should show Cycle Time Settings when select cycle time in config page', async () => { - await store.dispatch(updateMetrics([REQUIRED_DATA_LIST[2]])) - const { getByText } = setup() + await store.dispatch(updateMetrics([REQUIRED_DATA_LIST[2]])); + const { getByText } = setup(); - expect(getByText(CYCLE_TIME_SETTINGS)).toBeInTheDocument() - }) + expect(getByText(CYCLE_TIME_SETTINGS)).toBeInTheDocument(); + }); it('should hide Real Done when no done column in cycleTime settings', async () => { - await store.dispatch(saveCycleTimeSettings([{ name: 'Testing', value: 'Block' }])) - const { queryByText } = setup() + await store.dispatch(saveCycleTimeSettings([{ name: 'Testing', value: 'Block' }])); + const { queryByText } = setup(); - expect(queryByText(REAL_DONE)).not.toBeInTheDocument() - }) + expect(queryByText(REAL_DONE)).not.toBeInTheDocument(); + }); it('should show Classification Setting when select classification in config page', async () => { - await store.dispatch(updateMetrics([REQUIRED_DATA_LIST[3]])) - const { getByText } = setup() + await store.dispatch(updateMetrics([REQUIRED_DATA_LIST[3]])); + const { getByText } = setup(); - expect(getByText(CLASSIFICATION_SETTING)).toBeInTheDocument() - }) + expect(getByText(CLASSIFICATION_SETTING)).toBeInTheDocument(); + }); it('should show DeploymentFrequencySettings component when select deployment frequency in config page', async () => { - await store.dispatch(updateMetrics([REQUIRED_DATA_LIST[5]])) - const { getByText } = setup() + await store.dispatch(updateMetrics([REQUIRED_DATA_LIST[5]])); + const { getByText } = setup(); - expect(getByText(DEPLOYMENT_FREQUENCY_SETTINGS)).toBeInTheDocument() - }) + expect(getByText(DEPLOYMENT_FREQUENCY_SETTINGS)).toBeInTheDocument(); + }); it('should call resetProps when resetProps is not undefined', async () => { act(() => { - result.current.resetProps = jest.fn() - }) + result.current.resetProps = jest.fn(); + }); await waitFor(() => render( @@ -91,10 +91,10 @@ describe('MetricsStep', () => { ) - ) + ); - expect(result.current.resetProps).toBeCalled() - }) + expect(result.current.resetProps).toBeCalled(); + }); describe('with pre-filled cycle time data', () => { beforeEach(() => { @@ -119,77 +119,77 @@ describe('MetricsStep', () => { name: 'Done', value: 'Done', }, - ] - const doneColumn = ['IN PROGRESS', 'IN DEV', 'PRE-DONE', 'DONE', 'CANCLE'] + ]; + const doneColumn = ['IN PROGRESS', 'IN DEV', 'PRE-DONE', 'DONE', 'CANCLE']; const jiraColumns = [ { key: 'indeterminate', value: { name: 'To Do', statuses: ['BACKLOG', 'TO DO', 'GOING TO DO'] } }, { key: 'indeterminate', value: { name: 'In Progress', statuses: ['IN PROGRESS', 'IN DEV'] } }, { key: 'indeterminate', value: { name: 'Block', statuses: ['BLOCK'] } }, { key: 'indeterminate', value: { name: 'Test', statuses: ['TESTING', 'TO BE TESTED'] } }, { key: 'done', value: { name: 'Done', statuses: ['PRE-DONE,', 'DONE', 'CANCLE'] } }, - ] + ]; - store.dispatch(updateMetrics(REQUIRED_DATA_LIST)) - store.dispatch(saveCycleTimeSettings(cycleTimeSettingsWithTwoDoneValue)) - store.dispatch(saveDoneColumn(doneColumn)) + store.dispatch(updateMetrics(REQUIRED_DATA_LIST)); + store.dispatch(saveCycleTimeSettings(cycleTimeSettingsWithTwoDoneValue)); + store.dispatch(saveDoneColumn(doneColumn)); store.dispatch( updateJiraVerifyResponse({ jiraColumns, users: MOCK_JIRA_VERIFY_RESPONSE.users, }) - ) - }) + ); + }); it('should reset real done when change Cycle time settings DONE to other status', async () => { - const { getByLabelText, getByRole } = setup() - const cycleTimeSettingsSection = getByLabelText(CYCLE_TIME_SETTINGS_SECTION) - const realDoneSettingSection = getByLabelText(REAL_DONE_SETTING_SECTION) + const { getByLabelText, getByRole } = setup(); + const cycleTimeSettingsSection = getByLabelText(CYCLE_TIME_SETTINGS_SECTION); + const realDoneSettingSection = getByLabelText(REAL_DONE_SETTING_SECTION); - expect(realDoneSettingSection).not.toHaveTextContent(SELECT_CONSIDER_AS_DONE_MESSAGE) - const columnsArray = within(cycleTimeSettingsSection).getAllByRole('button', { name: LIST_OPEN }) + expect(realDoneSettingSection).not.toHaveTextContent(SELECT_CONSIDER_AS_DONE_MESSAGE); + const columnsArray = within(cycleTimeSettingsSection).getAllByRole('button', { name: LIST_OPEN }); - await userEvent.click(columnsArray[1]) + await userEvent.click(columnsArray[1]); - const options = within(getByRole('listbox')).getAllByRole('option') - await userEvent.click(options[1]) + const options = within(getByRole('listbox')).getAllByRole('option'); + await userEvent.click(options[1]); - await waitFor(() => expect(realDoneSettingSection).toHaveTextContent(SELECT_CONSIDER_AS_DONE_MESSAGE)) - }) + await waitFor(() => expect(realDoneSettingSection).toHaveTextContent(SELECT_CONSIDER_AS_DONE_MESSAGE)); + }); it('should reset real done when change Cycle time settings other status to DONE', async () => { - const { getByLabelText, getByRole } = setup() - const cycleTimeSettingsSection = getByLabelText(CYCLE_TIME_SETTINGS_SECTION) - const realDoneSettingSection = getByLabelText(REAL_DONE_SETTING_SECTION) + const { getByLabelText, getByRole } = setup(); + const cycleTimeSettingsSection = getByLabelText(CYCLE_TIME_SETTINGS_SECTION); + const realDoneSettingSection = getByLabelText(REAL_DONE_SETTING_SECTION); - expect(realDoneSettingSection).not.toHaveTextContent(SELECT_CONSIDER_AS_DONE_MESSAGE) - const columnsArray = within(cycleTimeSettingsSection).getAllByRole('button', { name: LIST_OPEN }) - await userEvent.click(columnsArray[2]) + expect(realDoneSettingSection).not.toHaveTextContent(SELECT_CONSIDER_AS_DONE_MESSAGE); + const columnsArray = within(cycleTimeSettingsSection).getAllByRole('button', { name: LIST_OPEN }); + await userEvent.click(columnsArray[2]); - const options = within(getByRole('listbox')).getAllByRole('option') - await userEvent.click(options[options.length - 1]) + const options = within(getByRole('listbox')).getAllByRole('option'); + await userEvent.click(options[options.length - 1]); - await waitFor(() => expect(realDoneSettingSection).toHaveTextContent(SELECT_CONSIDER_AS_DONE_MESSAGE)) - }) + await waitFor(() => expect(realDoneSettingSection).toHaveTextContent(SELECT_CONSIDER_AS_DONE_MESSAGE)); + }); it('should hide real done when change all Cycle time settings to other status', async () => { - const { getByLabelText, getByRole } = setup() - const cycleTimeSettingsSection = getByLabelText(CYCLE_TIME_SETTINGS_SECTION) - const realDoneSettingSection = getByLabelText(REAL_DONE_SETTING_SECTION) + const { getByLabelText, getByRole } = setup(); + const cycleTimeSettingsSection = getByLabelText(CYCLE_TIME_SETTINGS_SECTION); + const realDoneSettingSection = getByLabelText(REAL_DONE_SETTING_SECTION); - expect(realDoneSettingSection).not.toHaveTextContent(SELECT_CONSIDER_AS_DONE_MESSAGE) - const columnsArray = within(cycleTimeSettingsSection).getAllByRole('button', { name: LIST_OPEN }) + expect(realDoneSettingSection).not.toHaveTextContent(SELECT_CONSIDER_AS_DONE_MESSAGE); + const columnsArray = within(cycleTimeSettingsSection).getAllByRole('button', { name: LIST_OPEN }); - await userEvent.click(columnsArray[1]) + await userEvent.click(columnsArray[1]); - const options1 = within(getByRole('listbox')).getAllByRole('option') - await userEvent.click(options1[1]) + const options1 = within(getByRole('listbox')).getAllByRole('option'); + await userEvent.click(options1[1]); - await userEvent.click(columnsArray[4]) + await userEvent.click(columnsArray[4]); - const options2 = within(getByRole('listbox')).getAllByRole('option') - await userEvent.click(options2[1]) + const options2 = within(getByRole('listbox')).getAllByRole('option'); + await userEvent.click(options2[1]); - await waitFor(() => expect(realDoneSettingSection).not.toBeInTheDocument()) - }) - }) -}) + await waitFor(() => expect(realDoneSettingSection).not.toBeInTheDocument()); + }); + }); +}); diff --git a/frontend/__tests__/src/components/Metrics/MetricsStep/MultiAutoComplete.test.tsx b/frontend/__tests__/src/components/Metrics/MetricsStep/MultiAutoComplete.test.tsx index 997ff09234..86d62fc7ec 100644 --- a/frontend/__tests__/src/components/Metrics/MetricsStep/MultiAutoComplete.test.tsx +++ b/frontend/__tests__/src/components/Metrics/MetricsStep/MultiAutoComplete.test.tsx @@ -1,18 +1,18 @@ -import React from 'react' -import { render } from '@testing-library/react' -import userEvent from '@testing-library/user-event' -import MultiAutoComplete from '@src/components/Common/MultiAutoComplete' -import { act } from 'react-dom/test-utils' -import { ALL, AUTOCOMPLETE_SELECT_ACTION, MOCK_AUTOCOMPLETE_LIST } from '../../../fixtures' +import React from 'react'; +import { render, screen } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; +import MultiAutoComplete from '@src/components/Common/MultiAutoComplete'; +import { act } from 'react-dom/test-utils'; +import { ALL, AUTOCOMPLETE_SELECT_ACTION, MOCK_AUTOCOMPLETE_LIST } from '../../../fixtures'; describe('MultiAutoComplete', () => { - const optionList = ['Option 1', 'Option 2', 'Option 3'] - const selectedOption = ['Option 1'] - const onChangeHandler = jest.fn() - const isSelectAll = false - const textFieldLabel = 'Select Options' - const isError = false - const testId = 'multi-auto-complete' + const optionList = ['Option 1', 'Option 2', 'Option 3']; + const selectedOption = ['Option 1']; + const onChangeHandler = jest.fn(); + const isSelectAll = false; + const textFieldLabel = 'Select Options'; + const isError = false; + const testId = 'multi-auto-complete'; const setup = () => render( { isError={isError} testId={testId} /> - ) + ); it('renders the component', () => { - const { getByTestId } = setup() + setup(); - expect(getByTestId(testId)).toBeInTheDocument() - }) + expect(screen.getByTestId(testId)).toBeInTheDocument(); + }); it('When passed selectedoption changed, the correct option would be displayed', async () => { - const { getByRole } = setup() + setup(); - expect(getByRole('button', { name: 'Option 1' })).toBeVisible() - }) + expect(screen.getByRole('button', { name: 'Option 1' })).toBeVisible(); + }); it('When user select All option, all options in drop box would be selected', async () => { - const { getByRole } = setup() + setup(); - const inputField = getByRole('combobox') - await userEvent.click(inputField) - const allOption = getByRole('option', { name: 'All' }) + const inputField = screen.getByRole('combobox'); + await userEvent.click(inputField); + const allOption = screen.getByRole('option', { name: 'All' }); await act(async () => { - await userEvent.click(allOption) - }) + await userEvent.click(allOption); + }); expect(onChangeHandler).toHaveBeenCalledWith( expect.anything(), @@ -55,6 +55,6 @@ describe('MultiAutoComplete', () => { { option: ALL, } - ) - }) -}) + ); + }); +}); diff --git a/frontend/__tests__/src/components/Metrics/MetricsStep/RealDone.test.tsx b/frontend/__tests__/src/components/Metrics/MetricsStep/RealDone.test.tsx index 71ebf0002f..9e91715176 100644 --- a/frontend/__tests__/src/components/Metrics/MetricsStep/RealDone.test.tsx +++ b/frontend/__tests__/src/components/Metrics/MetricsStep/RealDone.test.tsx @@ -1,19 +1,19 @@ -import { act, render, waitFor, within } from '@testing-library/react' -import { RealDone } from '@src/components/Metrics/MetricsStep/RealDone' -import userEvent from '@testing-library/user-event' -import { setupStore } from '../../../utils/setupStoreUtil' -import { Provider } from 'react-redux' -import { saveCycleTimeSettings } from '@src/context/Metrics/metricsSlice' -import { ERROR_MESSAGE_TIME_DURATION } from '../../../fixtures' +import { act, render, waitFor, within } from '@testing-library/react'; +import { RealDone } from '@src/components/Metrics/MetricsStep/RealDone'; +import userEvent from '@testing-library/user-event'; +import { setupStore } from '../../../utils/setupStoreUtil'; +import { Provider } from 'react-redux'; +import { saveCycleTimeSettings } from '@src/context/Metrics/metricsSlice'; +import { ERROR_MESSAGE_TIME_DURATION } from '../../../fixtures'; jest.mock('@src/context/Metrics/metricsSlice', () => ({ ...jest.requireActual('@src/context/Metrics/metricsSlice'), selectRealDoneWarningMessage: jest.fn().mockReturnValue('Test warning Message'), -})) +})); -const mockTitle = 'RealDone' -const mockLabel = 'Consider as Done' -let store = setupStore() +const mockTitle = 'RealDone'; +const mockLabel = 'Consider as Done'; +let store = setupStore(); describe('RealDone', () => { describe('when done column with more than one statuses', () => { @@ -25,125 +25,125 @@ describe('RealDone', () => { statuses: ['DONE', 'CANCELLED'], }, }, - ] + ]; const setup = () => render( - ) + ); beforeEach(() => { - store = setupStore() - }) + store = setupStore(); + }); afterEach(() => { - jest.clearAllMocks() - }) + jest.clearAllMocks(); + }); it('should show RealDone when render RealDone component', () => { - const { getByText } = setup() + const { getByText } = setup(); - expect(getByText(mockTitle)).toBeInTheDocument() - }) + expect(getByText(mockTitle)).toBeInTheDocument(); + }); it('should show consider as done when initializing', () => { - const { getByText } = setup() - const label = getByText(mockLabel) - const helperText = getByText('consider as Done') + const { getByText } = setup(); + const label = getByText(mockLabel); + const helperText = getByText('consider as Done'); - expect(label).toBeInTheDocument() - expect(helperText.tagName).toBe('STRONG') - }) + expect(label).toBeInTheDocument(); + expect(helperText.tagName).toBe('STRONG'); + }); it('should show detail options when click Consider as Done button', async () => { - const { getByRole } = setup() + const { getByRole } = setup(); await act(async () => { - await userEvent.click(getByRole('combobox', { name: mockLabel })) - }) + await userEvent.click(getByRole('combobox', { name: mockLabel })); + }); - const listBox = within(getByRole('listbox')) - expect(listBox.getByRole('option', { name: 'All' })).toBeInTheDocument() - expect(listBox.getByRole('option', { name: 'DONE' })).toBeInTheDocument() - expect(listBox.getByRole('option', { name: 'CANCELLED' })).toBeInTheDocument() - }) + const listBox = within(getByRole('listbox')); + expect(listBox.getByRole('option', { name: 'All' })).toBeInTheDocument(); + expect(listBox.getByRole('option', { name: 'DONE' })).toBeInTheDocument(); + expect(listBox.getByRole('option', { name: 'CANCELLED' })).toBeInTheDocument(); + }); it('should show other selections when cancel one option given default all selections in RealDone', async () => { - const { getByRole, queryByRole } = setup() + const { getByRole, queryByRole } = setup(); await act(async () => { - await userEvent.click(getByRole('combobox', { name: mockLabel })) - }) + await userEvent.click(getByRole('combobox', { name: mockLabel })); + }); - const listBox = within(getByRole('listbox')) + const listBox = within(getByRole('listbox')); await act(async () => { - await userEvent.click(listBox.getByRole('option', { name: mockColumnsList[0].value.statuses[0] })) - }) + await userEvent.click(listBox.getByRole('option', { name: mockColumnsList[0].value.statuses[0] })); + }); - expect(queryByRole('button', { name: mockColumnsList[0].value.statuses[0] })).toBeInTheDocument() - expect(queryByRole('button', { name: mockColumnsList[0].value.statuses[1] })).not.toBeInTheDocument() - }) + expect(queryByRole('button', { name: mockColumnsList[0].value.statuses[0] })).toBeInTheDocument(); + expect(queryByRole('button', { name: mockColumnsList[0].value.statuses[1] })).not.toBeInTheDocument(); + }); it('should clear RealDone data when check all option', async () => { - const { getByRole, queryByRole } = setup() + const { getByRole, queryByRole } = setup(); await act(async () => { - await userEvent.click(getByRole('combobox', { name: mockLabel })) - }) + await userEvent.click(getByRole('combobox', { name: mockLabel })); + }); - const listBox = within(getByRole('listbox')) - const allOption = listBox.getByRole('option', { name: 'All' }) + const listBox = within(getByRole('listbox')); + const allOption = listBox.getByRole('option', { name: 'All' }); await act(async () => { - await userEvent.click(allOption) - }) + await userEvent.click(allOption); + }); - expect(getByRole('button', { name: mockColumnsList[0].value.statuses[0] })).toBeInTheDocument() - expect(getByRole('button', { name: mockColumnsList[0].value.statuses[1] })).toBeInTheDocument() + expect(getByRole('button', { name: mockColumnsList[0].value.statuses[0] })).toBeInTheDocument(); + expect(getByRole('button', { name: mockColumnsList[0].value.statuses[1] })).toBeInTheDocument(); await act(async () => { - await userEvent.click(allOption) - }) + await userEvent.click(allOption); + }); - expect(queryByRole('button', { name: mockColumnsList[0].value.statuses[0] })).not.toBeInTheDocument() - expect(queryByRole('button', { name: mockColumnsList[0].value.statuses[1] })).not.toBeInTheDocument() - }) + expect(queryByRole('button', { name: mockColumnsList[0].value.statuses[0] })).not.toBeInTheDocument(); + expect(queryByRole('button', { name: mockColumnsList[0].value.statuses[1] })).not.toBeInTheDocument(); + }); it('should show doing when choose Testing column is Done', async () => { - await store.dispatch(saveCycleTimeSettings([{ name: 'Done', value: 'Done' }])) - const { getByRole } = setup() + await store.dispatch(saveCycleTimeSettings([{ name: 'Done', value: 'Done' }])); + const { getByRole } = setup(); await act(async () => { - await userEvent.click(getByRole('combobox', { name: mockLabel })) - }) - const listBox = within(getByRole('listbox')) - const allOption = listBox.getByRole('option', { name: 'All' }) + await userEvent.click(getByRole('combobox', { name: mockLabel })); + }); + const listBox = within(getByRole('listbox')); + const allOption = listBox.getByRole('option', { name: 'All' }); await act(async () => { - await userEvent.click(allOption) - }) + await userEvent.click(allOption); + }); - expect(getByRole('button', { name: 'DONE' })).toBeInTheDocument() - expect(getByRole('button', { name: 'CANCELLED' })).toBeInTheDocument() - }) + expect(getByRole('button', { name: 'DONE' })).toBeInTheDocument(); + expect(getByRole('button', { name: 'CANCELLED' })).toBeInTheDocument(); + }); it('should show warning message when realDone warning message has a value in realDone component', () => { - const { getByText } = setup() + const { getByText } = setup(); - expect(getByText('Test warning Message')).toBeInTheDocument() - }) + expect(getByText('Test warning Message')).toBeInTheDocument(); + }); it('should clear warning message when realDone warning message has a value after four seconds in realDone component', async () => { - jest.useFakeTimers() - const { queryByText } = setup() + jest.useFakeTimers(); + const { queryByText } = setup(); act(() => { - jest.advanceTimersByTime(ERROR_MESSAGE_TIME_DURATION) - }) + jest.advanceTimersByTime(ERROR_MESSAGE_TIME_DURATION); + }); await waitFor(() => { - expect(queryByText('Test warning Message')).not.toBeInTheDocument() - }) - }) - }) + expect(queryByText('Test warning Message')).not.toBeInTheDocument(); + }); + }); + }); describe('when done column with only one status', () => { it('should not show read done box', async () => { @@ -155,16 +155,16 @@ describe('RealDone', () => { statuses: ['DONE'], }, }, - ] + ]; const { queryByText } = render( - ) + ); - expect(queryByText(mockTitle)).not.toBeInTheDocument() - expect(queryByText(mockLabel)).not.toBeInTheDocument() - }) - }) -}) + expect(queryByText(mockTitle)).not.toBeInTheDocument(); + expect(queryByText(mockLabel)).not.toBeInTheDocument(); + }); + }); +}); diff --git a/frontend/__tests__/src/components/Metrics/MetricsStepper/ConfirmDialog.test.tsx b/frontend/__tests__/src/components/Metrics/MetricsStepper/ConfirmDialog.test.tsx index 8cf653eac8..5138411b48 100644 --- a/frontend/__tests__/src/components/Metrics/MetricsStepper/ConfirmDialog.test.tsx +++ b/frontend/__tests__/src/components/Metrics/MetricsStepper/ConfirmDialog.test.tsx @@ -1,14 +1,14 @@ -import { render } from '@testing-library/react' -import { ConfirmDialog } from '@src/components/Metrics/MetricsStepper/ConfirmDialog' -import { CONFIRM_DIALOG_DESCRIPTION } from '../../../fixtures' +import { render } from '@testing-library/react'; +import { ConfirmDialog } from '@src/components/Metrics/MetricsStepper/ConfirmDialog'; +import { CONFIRM_DIALOG_DESCRIPTION } from '../../../fixtures'; -const onClose = jest.fn() -const onConfirm = jest.fn() +const onClose = jest.fn(); +const onConfirm = jest.fn(); describe('confirm dialog', () => { it('should show confirm dialog', () => { - const { getByText } = render() + const { getByText } = render(); - expect(getByText(CONFIRM_DIALOG_DESCRIPTION)).toBeInTheDocument() - }) -}) + expect(getByText(CONFIRM_DIALOG_DESCRIPTION)).toBeInTheDocument(); + }); +}); diff --git a/frontend/__tests__/src/components/Metrics/MetricsStepper/MetricsStepper.test.tsx b/frontend/__tests__/src/components/Metrics/MetricsStepper/MetricsStepper.test.tsx index fe97f358da..9b7fae3ba7 100644 --- a/frontend/__tests__/src/components/Metrics/MetricsStepper/MetricsStepper.test.tsx +++ b/frontend/__tests__/src/components/Metrics/MetricsStepper/MetricsStepper.test.tsx @@ -1,23 +1,23 @@ -import { act, fireEvent, render, screen, waitFor } from '@testing-library/react' -import MetricsStepper from '@src/components/Metrics/MetricsStepper' -import { Provider } from 'react-redux' -import { setupStore } from '../../../utils/setupStoreUtil' +import { act, fireEvent, render, screen, waitFor } from '@testing-library/react'; +import MetricsStepper from '@src/components/Metrics/MetricsStepper'; +import { Provider } from 'react-redux'; +import { setupStore } from '../../../utils/setupStoreUtil'; import { - BACK, + BASE_PAGE_ROUTE, BOARD_TYPES, CONFIRM_DIALOG_DESCRIPTION, - BASE_PAGE_ROUTE, MOCK_REPORT_URL, NEXT, PIPELINE_TOOL_TYPES, + PREVIOUS, PROJECT_NAME_LABEL, SAVE, SOURCE_CONTROL_TYPES, STEPPER, TEST_PROJECT_NAME, VELOCITY, -} from '../../../fixtures' -import userEvent from '@testing-library/user-event' +} from '../../../fixtures'; +import userEvent from '@testing-library/user-event'; import { updateBoard, updateBoardVerifyState, @@ -26,12 +26,12 @@ import { updatePipelineToolVerifyState, updateSourceControl, updateSourceControlVerifyState, -} from '@src/context/config/configSlice' -import dayjs from 'dayjs' -import { navigateMock } from '../../../../setupTests' -import { setupServer } from 'msw/node' -import { rest } from 'msw' -import { HttpStatusCode } from 'axios' +} from '@src/context/config/configSlice'; +import dayjs from 'dayjs'; +import { navigateMock } from '../../../../setupTests'; +import { setupServer } from 'msw/node'; +import { rest } from 'msw'; +import { HttpStatusCode } from 'axios'; import { saveCycleTimeSettings, saveDoneColumn, @@ -39,19 +39,19 @@ import { saveUsers, updateDeploymentFrequencySettings, updateTreatFlagCardAsBlock, -} from '@src/context/Metrics/metricsSlice' -import { exportToJsonFile } from '@src/utils/util' -import { ASSIGNEE_FILTER_TYPES } from '@src/constants/resources' - -const START_DATE_LABEL = 'From *' -const TODAY = dayjs() -const INPUT_DATE_VALUE = TODAY.format('MM/DD/YYYY') -const END_DATE_LABEL = 'To *' -const YES = 'Yes' -const CANCEL = 'Cancel' -const METRICS = 'Metrics' -const REPORT = 'Report' -const stepperColor = 'rgba(0, 0, 0, 0.87)' +} from '@src/context/Metrics/metricsSlice'; +import { exportToJsonFile } from '@src/utils/util'; +import { ASSIGNEE_FILTER_TYPES } from '@src/constants/resources'; + +const START_DATE_LABEL = 'From *'; +const TODAY = dayjs(); +const INPUT_DATE_VALUE = TODAY.format('MM/DD/YYYY'); +const END_DATE_LABEL = 'To *'; +const YES = 'Yes'; +const CANCEL = 'Cancel'; +const METRICS = 'Metrics'; +const REPORT = 'Report'; +const stepperColor = 'rgba(0, 0, 0, 0.87)'; const mockValidationCheckContext = { deploymentFrequencySettingsErrorMessages: [], @@ -60,11 +60,11 @@ const mockValidationCheckContext = { checkDuplicatedPipeline: jest.fn(), isPipelineValid: jest.fn().mockReturnValue(true), getDuplicatedPipeLineIds: jest.fn().mockReturnValue([]), -} +}; jest.mock('@src/hooks/useMetricsStepValidationCheckContext', () => ({ useMetricsStepValidationCheckContext: () => mockValidationCheckContext, -})) +})); jest.mock('@src/context/config/configSlice', () => ({ ...jest.requireActual('@src/context/config/configSlice'), @@ -75,12 +75,12 @@ jest.mock('@src/context/config/configSlice', () => ({ updateSourceControl: jest.fn().mockReturnValue({ type: 'UPDATE_SOURCE_CONTROL' }), updatePipelineTool: jest.fn().mockReturnValue({ type: 'UPDATE_PIPELINE_TOOL' }), updateBoard: jest.fn().mockReturnValue({ type: 'UPDATE_BOARD' }), -})) +})); jest.mock('@src/emojis/emoji', () => ({ getEmojiUrls: jest.fn().mockReturnValue(['https://buildkiteassets.com/emojis/img-buildkite-64/charger64.png']), removeExtraEmojiName: jest.fn(), -})) +})); jest.mock('@src/utils/util', () => ({ exportToJsonFile: jest.fn(), @@ -89,7 +89,7 @@ jest.mock('@src/utils/util', () => ({ findCaseInsensitiveType: jest.fn(), filterAndMapCycleTimeSettings: jest.fn(), formatDate: jest.fn(), -})) +})); jest.mock('@src/hooks/useGenerateReportEffect', () => ({ useGenerateReportEffect: jest.fn().mockReturnValue({ @@ -99,33 +99,33 @@ jest.mock('@src/hooks/useGenerateReportEffect', () => ({ isServerError: false, errorMessage: '', }), -})) +})); -const server = setupServer(rest.post(MOCK_REPORT_URL, (_, res, ctx) => res(ctx.status(HttpStatusCode.Ok)))) +const server = setupServer(rest.post(MOCK_REPORT_URL, (_, res, ctx) => res(ctx.status(HttpStatusCode.Ok)))); -const mockLocation = { reload: jest.fn() } -Object.defineProperty(window, 'location', { value: mockLocation }) +const mockLocation = { reload: jest.fn() }; +Object.defineProperty(window, 'location', { value: mockLocation }); -let store = setupStore() +let store = setupStore(); const fillConfigPageData = async () => { - const projectNameInput = await screen.findByRole('textbox', { name: PROJECT_NAME_LABEL }) - fireEvent.change(projectNameInput, { target: { value: TEST_PROJECT_NAME } }) - const startDateInput = (await screen.findByRole('textbox', { name: START_DATE_LABEL })) as HTMLInputElement - fireEvent.change(startDateInput, { target: { value: INPUT_DATE_VALUE } }) + const projectNameInput = await screen.findByRole('textbox', { name: PROJECT_NAME_LABEL }); + fireEvent.change(projectNameInput, { target: { value: TEST_PROJECT_NAME } }); + const startDateInput = (await screen.findByRole('textbox', { name: START_DATE_LABEL })) as HTMLInputElement; + fireEvent.change(startDateInput, { target: { value: INPUT_DATE_VALUE } }); act(() => { - store.dispatch(updateMetrics([VELOCITY])) - store.dispatch(updateBoardVerifyState(true)) - store.dispatch(updatePipelineToolVerifyState(true)) - store.dispatch(updateSourceControlVerifyState(true)) - }) -} + store.dispatch(updateMetrics([VELOCITY])); + store.dispatch(updateBoardVerifyState(true)); + store.dispatch(updatePipelineToolVerifyState(true)); + store.dispatch(updateSourceControlVerifyState(true)); + }); +}; const fillMetricsData = () => { act(() => { - store.dispatch(updateMetrics([VELOCITY])) - }) -} + store.dispatch(updateMetrics([VELOCITY])); + }); +}; const fillMetricsPageDate = async () => { await act(async () => { @@ -142,148 +142,148 @@ const fillMetricsPageDate = async () => { updateDeploymentFrequencySettings({ updateId: 0, label: 'pipelineName', value: 'mock new pipelineName' }) ), store.dispatch(updateDeploymentFrequencySettings({ updateId: 0, label: 'step', value: 'mock new step' })), - ]) - }) -} + ]); + }); +}; describe('MetricsStepper', () => { - beforeAll(() => server.listen()) - afterAll(() => server.close()) + beforeAll(() => server.listen()); + afterAll(() => server.close()); beforeEach(() => { - store = setupStore() - }) + store = setupStore(); + }); afterEach(() => { - navigateMock.mockClear() - }) + navigateMock.mockClear(); + }); const setup = () => render( - ) + ); it('should show metrics stepper', () => { - const { getByText } = setup() + setup(); STEPPER.map((label) => { - expect(getByText(label)).toBeInTheDocument() - }) + expect(screen.getByText(label)).toBeInTheDocument(); + }); - expect(getByText(NEXT)).toBeInTheDocument() - expect(getByText(BACK)).toBeInTheDocument() - }) + expect(screen.getByText(NEXT)).toBeInTheDocument(); + expect(screen.getByText(PREVIOUS)).toBeInTheDocument(); + }); it('should show metrics config step when click back button given config step ', async () => { - const { getByText } = setup() + setup(); - await userEvent.click(getByText(BACK)) + await userEvent.click(screen.getByText(PREVIOUS)); - expect(getByText(PROJECT_NAME_LABEL)).toBeInTheDocument() - }) + expect(screen.getByText(PROJECT_NAME_LABEL)).toBeInTheDocument(); + }); it('should show confirm dialog when click back button in config page', async () => { - const { getByText } = setup() + setup(); - await userEvent.click(getByText(BACK)) + await userEvent.click(screen.getByText(PREVIOUS)); - expect(getByText(CONFIRM_DIALOG_DESCRIPTION)).toBeInTheDocument() - }) + expect(screen.getByText(CONFIRM_DIALOG_DESCRIPTION)).toBeInTheDocument(); + }); it('should close confirm dialog when click cancel button', async () => { - const { getByText, queryByText } = setup() + setup(); - await userEvent.click(getByText(BACK)) - await userEvent.click(getByText(CANCEL)) + await userEvent.click(screen.getByText(PREVIOUS)); + await userEvent.click(screen.getByText(CANCEL)); - expect(queryByText(CONFIRM_DIALOG_DESCRIPTION)).not.toBeInTheDocument() - }) + expect(screen.queryByText(CONFIRM_DIALOG_DESCRIPTION)).not.toBeInTheDocument(); + }); it('should go to home page when click Yes button', async () => { - const { getByText } = setup() + setup(); - await userEvent.click(getByText(BACK)) + await userEvent.click(screen.getByText(PREVIOUS)); - expect(getByText(YES)).toBeVisible() + expect(screen.getByText(YES)).toBeVisible(); - await userEvent.click(getByText(YES)) + await userEvent.click(screen.getByText(YES)); - expect(navigateMock).toHaveBeenCalledTimes(1) - expect(navigateMock).toHaveBeenCalledWith(BASE_PAGE_ROUTE) - }) + expect(navigateMock).toHaveBeenCalledTimes(1); + expect(navigateMock).toHaveBeenCalledWith(BASE_PAGE_ROUTE); + }); it('should disable next when required data is empty ', async () => { - const { getByText } = setup() + setup(); act(() => { - store.dispatch(updateMetrics([])) - }) + store.dispatch(updateMetrics([])); + }); - expect(getByText(NEXT)).toBeDisabled() - }) + expect(screen.getByText(NEXT)).toBeDisabled(); + }); it('should disable next when dataRange is empty ', async () => { - const { getByText, getByRole } = setup() - await fillConfigPageData() + setup(); + await fillConfigPageData(); - const startDateInput = getByRole('textbox', { name: START_DATE_LABEL }) as HTMLInputElement - const endDateInput = getByRole('textbox', { name: END_DATE_LABEL }) as HTMLInputElement + const startDateInput = screen.getByRole('textbox', { name: START_DATE_LABEL }) as HTMLInputElement; + const endDateInput = screen.getByRole('textbox', { name: END_DATE_LABEL }) as HTMLInputElement; - await userEvent.clear(startDateInput) - await userEvent.clear(endDateInput) + await userEvent.clear(startDateInput); + await userEvent.clear(endDateInput); - expect(getByText(NEXT)).toBeDisabled() - }, 50000) + expect(screen.getByText(NEXT)).toBeDisabled(); + }, 50000); it('should disable next when endDate is empty ', async () => { - const { getByText, getByRole } = setup() - await fillConfigPageData() + setup(); + await fillConfigPageData(); - const endDateInput = getByRole('textbox', { name: END_DATE_LABEL }) as HTMLInputElement + const endDateInput = screen.getByRole('textbox', { name: END_DATE_LABEL }) as HTMLInputElement; - await userEvent.clear(endDateInput) + await userEvent.clear(endDateInput); - expect(getByText(NEXT)).toBeDisabled() - }) + expect(screen.getByText(NEXT)).toBeDisabled(); + }); it('should enable next when every selected component is show and verified', async () => { - const { getByText } = setup() - await fillConfigPageData() + setup(); + await fillConfigPageData(); - expect(getByText(NEXT)).toBeEnabled() - }) + expect(screen.getByText(NEXT)).toBeEnabled(); + }); it('should disable next when board component is exist but not verified successfully', async () => { - const { getByText } = setup() + setup(); act(() => { - store.dispatch(updateMetrics([VELOCITY])) - store.dispatch(updateBoardVerifyState(false)) - }) + store.dispatch(updateMetrics([VELOCITY])); + store.dispatch(updateBoardVerifyState(false)); + }); - expect(getByText(NEXT)).toBeDisabled() - }) + expect(screen.getByText(NEXT)).toBeDisabled(); + }); it('should go metrics page when click next button given next button enabled', async () => { - const { getByText } = setup() + setup(); - await fillConfigPageData() - fireEvent.click(getByText(NEXT)) + await fillConfigPageData(); + fireEvent.click(screen.getByText(NEXT)); - expect(getByText(METRICS)).toHaveStyle(`color:${stepperColor}`) - }) + expect(screen.getByText(METRICS)).toHaveStyle(`color:${stepperColor}`); + }); it('should show metrics export step when click next button given export step', async () => { - const { getByText } = setup() - await fillConfigPageData() - await userEvent.click(getByText(NEXT)) - await fillMetricsPageDate() + setup(); + await fillConfigPageData(); + await userEvent.click(screen.getByText(NEXT)); + await fillMetricsPageDate(); waitFor(() => { - expect(getByText(NEXT)).toBeInTheDocument() - }) - await userEvent.click(getByText(NEXT)) + expect(screen.getByText(NEXT)).toBeInTheDocument(); + }); + await userEvent.click(screen.getByText(NEXT)); - expect(getByText(REPORT)).toHaveStyle(`color:${stepperColor}`) - }) + expect(screen.getByText(REPORT)).toHaveStyle(`color:${stepperColor}`); + }); it('should export json when click save button', async () => { - const expectedFileName = 'config' + const expectedFileName = 'config'; const expectedJson = { board: undefined, calendarType: 'Regular Calendar(Weekend Considered)', @@ -295,16 +295,16 @@ describe('MetricsStepper', () => { pipelineTool: undefined, projectName: '', sourceControl: undefined, - } - const { getByText } = setup() + }; + setup(); - await userEvent.click(getByText(SAVE)) + await userEvent.click(screen.getByText(SAVE)); - expect(exportToJsonFile).toHaveBeenCalledWith(expectedFileName, expectedJson) - }) + expect(exportToJsonFile).toHaveBeenCalledWith(expectedFileName, expectedJson); + }); it('should export json when click save button when pipelineTool, sourceControl, and board is not empty', async () => { - const expectedFileName = 'config' + const expectedFileName = 'config'; const expectedJson = { board: { boardId: '', email: '', projectKey: '', site: '', token: '', type: 'Jira' }, calendarType: 'Regular Calendar(Weekend Considered)', @@ -316,18 +316,18 @@ describe('MetricsStepper', () => { pipelineTool: undefined, projectName: '', sourceControl: undefined, - } + }; - const { getByText } = setup() - await fillMetricsData() + setup(); + await fillMetricsData(); - await userEvent.click(getByText(SAVE)) + await userEvent.click(screen.getByText(SAVE)); - expect(exportToJsonFile).toHaveBeenCalledWith(expectedFileName, expectedJson) - }) + expect(exportToJsonFile).toHaveBeenCalledWith(expectedFileName, expectedJson); + }); it('should export json file when click save button in metrics page given all content is empty', async () => { - const expectedFileName = 'config' + const expectedFileName = 'config'; const expectedJson = { assigneeFilter: ASSIGNEE_FILTER_TYPES.LAST_ASSIGNEE, board: { boardId: '', email: '', projectKey: '', site: '', token: '', type: 'Jira' }, @@ -346,18 +346,18 @@ describe('MetricsStepper', () => { deployment: undefined, doneStatus: undefined, leadTime: undefined, - } - const { getByText } = setup() + }; + setup(); - await fillConfigPageData() - await userEvent.click(getByText(NEXT)) - await userEvent.click(getByText(SAVE)) + await fillConfigPageData(); + await userEvent.click(screen.getByText(NEXT)); + await userEvent.click(screen.getByText(SAVE)); - expect(exportToJsonFile).toHaveBeenCalledWith(expectedFileName, expectedJson) - }, 50000) + expect(exportToJsonFile).toHaveBeenCalledWith(expectedFileName, expectedJson); + }, 50000); it('should export json file when click save button in report page given all content is empty', async () => { - const expectedFileName = 'config' + const expectedFileName = 'config'; const expectedJson = { assigneeFilter: ASSIGNEE_FILTER_TYPES.LAST_ASSIGNEE, board: { boardId: '', email: '', projectKey: '', site: '', token: '', type: 'Jira' }, @@ -376,26 +376,26 @@ describe('MetricsStepper', () => { deployment: undefined, doneStatus: undefined, leadTime: undefined, - } + }; - const { getByText } = setup() - await fillConfigPageData() - await userEvent.click(getByText(NEXT)) - await fillMetricsPageDate() + setup(); + await fillConfigPageData(); + await userEvent.click(screen.getByText(NEXT)); + await fillMetricsPageDate(); waitFor(() => { - expect(getByText(NEXT)).toBeInTheDocument() - }) - await userEvent.click(getByText(NEXT)) - await userEvent.click(getByText(SAVE)) + expect(screen.getByText(NEXT)).toBeInTheDocument(); + }); + await userEvent.click(screen.getByText(NEXT)); + await userEvent.click(screen.getByText(SAVE)); - expect(exportToJsonFile).toHaveBeenCalledWith(expectedFileName, expectedJson) - }, 50000) + expect(exportToJsonFile).toHaveBeenCalledWith(expectedFileName, expectedJson); + }, 50000); it('should clean the config information that is hidden when click next button', async () => { - const { getByText } = setup() + setup(); - await fillConfigPageData() - await userEvent.click(getByText(NEXT)) + await fillConfigPageData(); + await userEvent.click(screen.getByText(NEXT)); expect(updateBoard).not.toHaveBeenCalledWith({ type: BOARD_TYPES.JIRA, @@ -404,8 +404,8 @@ describe('MetricsStepper', () => { projectKey: '', site: '', token: '', - }) - expect(updateSourceControl).toHaveBeenCalledWith({ type: SOURCE_CONTROL_TYPES.GITHUB, token: '' }) - expect(updatePipelineTool).toHaveBeenCalledWith({ type: PIPELINE_TOOL_TYPES.BUILD_KITE, token: '' }) - }, 50000) -}) + }); + expect(updateSourceControl).toHaveBeenCalledWith({ type: SOURCE_CONTROL_TYPES.GITHUB, token: '' }); + expect(updatePipelineTool).toHaveBeenCalledWith({ type: PIPELINE_TOOL_TYPES.BUILD_KITE, token: '' }); + }, 50000); +}); diff --git a/frontend/__tests__/src/components/Metrics/ReportStep/ExpiredDialog.test.tsx b/frontend/__tests__/src/components/Metrics/ReportStep/ExpiredDialog.test.tsx index db0d7709e5..32f3fceed3 100644 --- a/frontend/__tests__/src/components/Metrics/ReportStep/ExpiredDialog.test.tsx +++ b/frontend/__tests__/src/components/Metrics/ReportStep/ExpiredDialog.test.tsx @@ -1,52 +1,52 @@ -import { render, screen, waitFor } from '@testing-library/react' -import { setupStore } from '../../../utils/setupStoreUtil' -import { ExpiredDialog } from '@src/components/Metrics/ReportStep/ExpiredDialog' -import { Provider } from 'react-redux' -import userEvent from '@testing-library/user-event' -import { EXPORT_EXPIRED_CSV_MESSAGE } from '../../../fixtures' +import { render, screen, waitFor } from '@testing-library/react'; +import { setupStore } from '../../../utils/setupStoreUtil'; +import { ExpiredDialog } from '@src/components/Metrics/ReportStep/ExpiredDialog'; +import { Provider } from 'react-redux'; +import userEvent from '@testing-library/user-event'; +import { EXPORT_EXPIRED_CSV_MESSAGE } from '../../../fixtures'; describe('ExpiredDialog', () => { it('should show expired dialog when csv file expired and close expired dialog when click No button', async () => { - const handleOkFn = jest.fn() + const handleOkFn = jest.fn(); const { getByText, queryByText } = render( - ) + ); - expect(getByText(EXPORT_EXPIRED_CSV_MESSAGE)).toBeInTheDocument() + expect(getByText(EXPORT_EXPIRED_CSV_MESSAGE)).toBeInTheDocument(); - await userEvent.click(screen.getByText('No')) + await userEvent.click(screen.getByText('No')); await waitFor(() => { - expect(queryByText(EXPORT_EXPIRED_CSV_MESSAGE)).not.toBeInTheDocument() - }) - }) + expect(queryByText(EXPORT_EXPIRED_CSV_MESSAGE)).not.toBeInTheDocument(); + }); + }); it('should not show expired dialog when isExpired is false ', async () => { - const handleOkFn = jest.fn() + const handleOkFn = jest.fn(); const { queryByText } = render( - ) + ); - expect(queryByText(EXPORT_EXPIRED_CSV_MESSAGE)).not.toBeInTheDocument() - }) + expect(queryByText(EXPORT_EXPIRED_CSV_MESSAGE)).not.toBeInTheDocument(); + }); it('should close expired dialog given an expired dialog when click the Ok button', async () => { - const handleOkFn = jest.fn() + const handleOkFn = jest.fn(); const { getByText } = render( - ) + ); - expect(getByText(EXPORT_EXPIRED_CSV_MESSAGE)).toBeInTheDocument() + expect(getByText(EXPORT_EXPIRED_CSV_MESSAGE)).toBeInTheDocument(); - await userEvent.click(screen.getByText('Yes')) - expect(handleOkFn).toBeCalledTimes(1) - }) -}) + await userEvent.click(screen.getByText('Yes')); + expect(handleOkFn).toBeCalledTimes(1); + }); +}); diff --git a/frontend/__tests__/src/components/Metrics/ReportStep/ReportDetail/board.test.tsx b/frontend/__tests__/src/components/Metrics/ReportStep/ReportDetail/board.test.tsx new file mode 100644 index 0000000000..34b4189379 --- /dev/null +++ b/frontend/__tests__/src/components/Metrics/ReportStep/ReportDetail/board.test.tsx @@ -0,0 +1,125 @@ +import { ReportResponseDTO } from '@src/clients/report/dto/response'; +import { render, screen, within } from '@testing-library/react'; +import { BoardDetail } from '@src/components/Metrics/ReportStep/ReportDetail'; +import { reportMapper } from '@src/hooks/reportMapper/report'; +import React from 'react'; + +jest.mock('@src/hooks/reportMapper/report'); + +describe('board', () => { + const data: ReportResponseDTO = {} as ReportResponseDTO; + + afterEach(jest.clearAllMocks); + + it('should render a back link', () => { + (reportMapper as jest.Mock).mockReturnValue({}); + render(); + expect(screen.getByTestId('ArrowBackIcon')).toBeInTheDocument(); + expect(screen.getByText('Back')).toBeInTheDocument(); + }); + + describe('Velocity', () => { + it('should show velocity when velocity data is existing', () => { + (reportMapper as jest.Mock).mockReturnValue({ + velocityList: [ + { id: 0, name: 'name1', valueList: [{ value: 1 }] }, + { id: 1, name: 'name2', valueList: [{ value: 2 }] }, + ], + }); + render(); + const velocityTable = screen.getByTestId('Velocity'); + expect(screen.getByText('Velocity')).toBeInTheDocument(); + expect(velocityTable).toBeInTheDocument(); + expect(within(velocityTable).queryAllByTestId('tr').length).toBe(2); + }); + + it('should not show velocity when velocity data is not existing', () => { + (reportMapper as jest.Mock).mockReturnValue({ + velocityList: null, + }); + render(); + expect(screen.queryAllByText('Velocity').length).toEqual(0); + }); + }); + + describe('Cycle Time', () => { + it('should show cycle time when cycle time data is existing', () => { + (reportMapper as jest.Mock).mockReturnValue({ + cycleTimeList: [ + { id: 0, name: 'name1', valueList: [{ value: 1 }] }, + { id: 1, name: 'name2', valueList: [{ value: 2 }] }, + { id: 2, name: 'name3', valueList: [{ value: 3 }] }, + ], + }); + render(); + const cycleTimeTable = screen.getByTestId('Cycle Time'); + expect(screen.getByText('Cycle Time')).toBeInTheDocument(); + expect(cycleTimeTable).toBeInTheDocument(); + expect(within(cycleTimeTable).queryAllByTestId('tr').length).toBe(3); + }); + + it('should not show cycle time when cycle time data is not existing', () => { + (reportMapper as jest.Mock).mockReturnValue({ + cycleTimeList: null, + }); + render(); + expect(screen.queryAllByText('Cycle Time').length).toEqual(0); + }); + }); + + describe('Classification', () => { + it('should show classifications when classifications data is existing', () => { + (reportMapper as jest.Mock).mockReturnValue({ + classification: [ + { id: 0, name: 'name1', valuesList: [{ name: 'test1', value: 1 }] }, + { id: 1, name: 'name2', valuesList: [{ name: 'test2', value: 2 }] }, + { id: 2, name: 'name3', valuesList: [{ name: 'test3', value: 3 }] }, + { id: 3, name: 'name4', valuesList: [{ name: 'test4', value: 4 }] }, + ], + }); + + render(); + const classificationTable = screen.getByTestId('Classification'); + expect(screen.getByText('Classification')).toBeInTheDocument(); + expect(classificationTable).toBeInTheDocument(); + expect(within(classificationTable).queryAllByTestId('tr').length).toBe(8); + }); + + it('should not show classifications when classifications data is not existing', () => { + (reportMapper as jest.Mock).mockReturnValue({ + classification: null, + }); + render(); + expect(screen.queryAllByText('Classification').length).toEqual(0); + }); + }); + + it('should show all data when all data is existing', () => { + (reportMapper as jest.Mock).mockReturnValue({ + velocityList: [{ id: 0, name: 'name1', valueList: [{ value: 1 }] }], + cycleTimeList: [ + { id: 0, name: 'name1', valueList: [{ value: 1 }] }, + { id: 1, name: 'name2', valueList: [{ value: 2 }] }, + ], + classification: [ + { id: 0, name: 'name1', valuesList: [{ name: 'test1', value: 1 }] }, + { id: 1, name: 'name2', valuesList: [{ name: 'test2', value: 2 }] }, + { id: 2, name: 'name3', valuesList: [{ name: 'test3', value: 3 }] }, + ], + }); + render(); + const velocityTable = screen.getByTestId('Velocity'); + const cycleTimeTable = screen.getByTestId('Cycle Time'); + const classificationTable = screen.getByTestId('Classification'); + expect(screen.getByText('Velocity')).toBeInTheDocument(); + expect(velocityTable).toBeInTheDocument(); + expect(screen.getByText('Cycle Time')).toBeInTheDocument(); + expect(cycleTimeTable).toBeInTheDocument(); + expect(screen.getByText('Classification')).toBeInTheDocument(); + expect(classificationTable).toBeInTheDocument(); + + expect(within(velocityTable).queryAllByTestId('tr').length).toBe(1); + expect(within(cycleTimeTable).queryAllByTestId('tr').length).toBe(2); + expect(within(classificationTable).queryAllByTestId('tr').length).toBe(6); + }); +}); diff --git a/frontend/__tests__/src/components/Metrics/ReportStep/ReportDetail/dora.test.tsx b/frontend/__tests__/src/components/Metrics/ReportStep/ReportDetail/dora.test.tsx new file mode 100644 index 0000000000..2142ba238c --- /dev/null +++ b/frontend/__tests__/src/components/Metrics/ReportStep/ReportDetail/dora.test.tsx @@ -0,0 +1,103 @@ +import { ReportResponseDTO } from '@src/clients/report/dto/response'; +import { render, screen, within } from '@testing-library/react'; +import { reportMapper } from '@src/hooks/reportMapper/report'; +import { DoraDetail } from '@src/components/Metrics/ReportStep/ReportDetail'; +import React from 'react'; + +jest.mock('@src/hooks/reportMapper/report'); +describe('DoraDetail', () => { + const data: ReportResponseDTO = {} as ReportResponseDTO; + + afterEach(jest.clearAllMocks); + + it('should render a back link', () => { + (reportMapper as jest.Mock).mockReturnValue({}); + render(); + expect(screen.getByTestId('ArrowBackIcon')).toBeInTheDocument(); + expect(screen.getByText('Back')).toBeInTheDocument(); + }); + + describe('Deployment Frequency', () => { + it('should show deploymentFrequencyList when deploymentFrequencyList data is existing', () => { + (reportMapper as jest.Mock).mockReturnValue({ + deploymentFrequencyList: [{ id: 0, name: 'name1', valuesList: [{ name: 'test1', value: 1 }] }], + }); + render(); + const deploymentFrequencyTable = screen.getByTestId('Deployment Frequency'); + expect(screen.getByText('Deployment Frequency')).toBeInTheDocument(); + expect(deploymentFrequencyTable).toBeInTheDocument(); + expect(within(deploymentFrequencyTable).queryAllByTestId('tr').length).toBe(2); + }); + + it('should not show deploymentFrequencyList when deploymentFrequencyList data is not existing', () => { + (reportMapper as jest.Mock).mockReturnValue({ + deploymentFrequencyList: null, + }); + render(); + expect(screen.queryAllByText('Deployment Frequency').length).toEqual(0); + }); + }); + + describe('Lead Time For Changes', () => { + it('should show leadTimeForChangesList when leadTimeForChangesList data is existing', () => { + (reportMapper as jest.Mock).mockReturnValue({ + leadTimeForChangesList: [{ id: 0, name: 'name1', valuesList: [{ name: 'test1', value: 1 }] }], + }); + render(); + const leadTimeForChangesTable = screen.getByTestId('Lead Time For Changes'); + expect(screen.getByText('Lead Time For Changes')).toBeInTheDocument(); + expect(leadTimeForChangesTable).toBeInTheDocument(); + expect(within(leadTimeForChangesTable).queryAllByTestId('tr').length).toBe(2); + }); + + it('should not show leadTimeForChangesList when leadTimeForChangesList data is not existing', () => { + (reportMapper as jest.Mock).mockReturnValue({ + leadTimeForChangesList: null, + }); + render(); + expect(screen.queryAllByText('Lead Time For Changes').length).toEqual(0); + }); + }); + + describe('Change Failure Rate', () => { + it('should show changeFailureRateList when changeFailureRateList data is existing', () => { + (reportMapper as jest.Mock).mockReturnValue({ + changeFailureRateList: [{ id: 0, name: 'name1', valuesList: [{ name: 'test1', value: 1 }] }], + }); + render(); + const changeFailureRateTable = screen.getByTestId('Change Failure Rate'); + expect(screen.getByText('Change Failure Rate')).toBeInTheDocument(); + expect(changeFailureRateTable).toBeInTheDocument(); + expect(within(changeFailureRateTable).queryAllByTestId('tr').length).toBe(2); + }); + + it('should not show changeFailureRateList when changeFailureRateList data is not existing', () => { + (reportMapper as jest.Mock).mockReturnValue({ + changeFailureRateList: null, + }); + render(); + expect(screen.queryAllByText('Change Failure Rate').length).toEqual(0); + }); + }); + + describe('Mean Time To Recovery', () => { + it('should show meanTimeToRecoveryList when meanTimeToRecoveryList data is existing', () => { + (reportMapper as jest.Mock).mockReturnValue({ + meanTimeToRecoveryList: [{ id: 0, name: 'name1', valuesList: [{ name: 'test1', value: 1 }] }], + }); + render(); + const meanTimeToRecoveryTable = screen.getByTestId('Mean Time To Recovery'); + expect(screen.getByText('Mean Time To Recovery')).toBeInTheDocument(); + expect(meanTimeToRecoveryTable).toBeInTheDocument(); + expect(within(meanTimeToRecoveryTable).queryAllByTestId('tr').length).toBe(2); + }); + + it('should not show meanTimeToRecoveryList when meanTimeToRecoveryList data is not existing', () => { + (reportMapper as jest.Mock).mockReturnValue({ + meanTimeToRecoveryList: null, + }); + render(); + expect(screen.queryAllByText('Mean Time To Recovery').length).toEqual(0); + }); + }); +}); diff --git a/frontend/__tests__/src/components/Metrics/ReportStep/ReportDetail/withBack.test.tsx b/frontend/__tests__/src/components/Metrics/ReportStep/ReportDetail/withBack.test.tsx new file mode 100644 index 0000000000..143e7a6fa2 --- /dev/null +++ b/frontend/__tests__/src/components/Metrics/ReportStep/ReportDetail/withBack.test.tsx @@ -0,0 +1,28 @@ +import { withGoBack } from '@src/components/Metrics/ReportStep/ReportDetail/withBack'; +import { render, fireEvent, screen } from '@testing-library/react'; +import React from 'react'; + +describe('withGoBack', () => { + const onBack = jest.fn(); + + afterEach(jest.clearAllMocks); + + it('should render a link with back', () => { + const Component = withGoBack(() =>
{'test1'}
); + render(); + expect(screen.getByText('Back')).toBeInTheDocument(); + }); + + it('should render the icon', () => { + const Component = withGoBack(() =>
{'test2'}
); + render(); + expect(screen.getByTestId('ArrowBackIcon')).toBeInTheDocument(); + }); + + it('should call onBack when the back is clicked', () => { + const Component = withGoBack(() =>
{'test3'}
); + render(); + fireEvent.click(screen.getByText('Back')); + expect(onBack).toBeCalled(); + }); +}); diff --git a/frontend/__tests__/src/components/Metrics/ReportStep/ReportStep.test.tsx b/frontend/__tests__/src/components/Metrics/ReportStep/ReportStep.test.tsx index d64c552035..c01aa3f2c6 100644 --- a/frontend/__tests__/src/components/Metrics/ReportStep/ReportStep.test.tsx +++ b/frontend/__tests__/src/components/Metrics/ReportStep/ReportStep.test.tsx @@ -1,5 +1,5 @@ -import { render, renderHook, screen } from '@testing-library/react' -import ReportStep from '@src/components/Metrics/ReportStep' +import { render, renderHook, screen, waitFor } from '@testing-library/react'; +import ReportStep from '@src/components/Metrics/ReportStep'; import { BACK, EMPTY_REPORT_VALUES, @@ -9,31 +9,32 @@ import { EXPORT_PIPELINE_DATA, MOCK_JIRA_VERIFY_RESPONSE, MOCK_REPORT_RESPONSE, + PREVIOUS, REQUIRED_DATA_LIST, SAVE, -} from '../../../fixtures' -import { setupStore } from '../../../utils/setupStoreUtil' -import { Provider } from 'react-redux' -import { updateDeploymentFrequencySettings } from '@src/context/Metrics/metricsSlice' + SHOW_MORE, +} from '../../../fixtures'; +import { setupStore } from '../../../utils/setupStoreUtil'; +import { Provider } from 'react-redux'; +import { updateDeploymentFrequencySettings } from '@src/context/Metrics/metricsSlice'; import { updateJiraVerifyResponse, updateMetrics, updatePipelineToolVerifyResponse, -} from '@src/context/config/configSlice' -import userEvent from '@testing-library/user-event' -import { backStep } from '@src/context/stepper/StepperSlice' -import { navigateMock } from '../../../../setupTests' -import { useGenerateReportEffect } from '@src/hooks/useGenerateReportEffect' -import { useNotificationLayoutEffect } from '@src/hooks/useNotificationLayoutEffect' -import React from 'react' -import { useExportCsvEffect } from '@src/hooks/useExportCsvEffect' -import { MESSAGE } from '@src/constants/resources' -import { act } from 'react-dom/test-utils' +} from '@src/context/config/configSlice'; +import userEvent from '@testing-library/user-event'; +import { backStep } from '@src/context/stepper/StepperSlice'; +import { navigateMock } from '../../../../setupTests'; +import { useGenerateReportEffect } from '@src/hooks/useGenerateReportEffect'; +import { useNotificationLayoutEffect } from '@src/hooks/useNotificationLayoutEffect'; +import React from 'react'; +import { useExportCsvEffect } from '@src/hooks/useExportCsvEffect'; +import { MESSAGE } from '@src/constants/resources'; jest.mock('@src/context/stepper/StepperSlice', () => ({ ...jest.requireActual('@src/context/stepper/StepperSlice'), backStep: jest.fn().mockReturnValue({ type: 'BACK_STEP' }), -})) +})); jest.mock('@src/hooks/useExportCsvEffect', () => ({ useExportCsvEffect: jest.fn().mockReturnValue({ @@ -41,7 +42,7 @@ jest.mock('@src/hooks/useExportCsvEffect', () => ({ errorMessage: 'failed export csv', isExpired: false, }), -})) +})); jest.mock('@src/hooks/useGenerateReportEffect', () => ({ useGenerateReportEffect: jest.fn().mockReturnValue({ @@ -51,12 +52,12 @@ jest.mock('@src/hooks/useGenerateReportEffect', () => ({ isServerError: false, errorMessage: '', }), -})) +})); jest.mock('@src/emojis/emoji', () => ({ getEmojiUrls: jest.fn().mockReturnValue(['']), removeExtraEmojiName: jest.fn(), -})) +})); jest.mock('@src/utils/util', () => ({ transformToCleanedBuildKiteEmoji: jest.fn(), @@ -64,44 +65,44 @@ jest.mock('@src/utils/util', () => ({ filterAndMapCycleTimeSettings: jest.fn(), formatMinToHours: jest.fn().mockImplementation((time) => time / 60), formatMillisecondsToHours: jest.fn().mockImplementation((time) => time / 60 / 60 / 1000), -})) +})); -let store = null +let store = null; describe('Report Step', () => { - const { result: notificationHook } = renderHook(() => useNotificationLayoutEffect()) - const { result: reportHook } = renderHook(() => useGenerateReportEffect()) + const { result: notificationHook } = renderHook(() => useNotificationLayoutEffect()); + const { result: reportHook } = renderHook(() => useGenerateReportEffect()); beforeEach(() => { - resetReportHook() - }) + resetReportHook(); + }); afterAll(() => { - jest.clearAllMocks() - }) + jest.clearAllMocks(); + }); const resetReportHook = async () => { - reportHook.current.startToRequestBoardData = jest.fn() - reportHook.current.startToRequestDoraData = jest.fn() - reportHook.current.stopPollingReports = jest.fn() - reportHook.current.isServerError = false - reportHook.current.errorMessage = '' - reportHook.current.reportData = MOCK_REPORT_RESPONSE - } - const handleSaveMock = jest.fn() + reportHook.current.startToRequestBoardData = jest.fn(); + reportHook.current.startToRequestDoraData = jest.fn(); + reportHook.current.stopPollingReports = jest.fn(); + reportHook.current.isServerError = false; + reportHook.current.errorMessage = ''; + reportHook.current.reportData = { ...MOCK_REPORT_RESPONSE, exportValidityTime: 30 }; + }; + const handleSaveMock = jest.fn(); const setup = (params: string[]) => { - store = setupStore() + store = setupStore(); store.dispatch( updateJiraVerifyResponse({ jiraColumns: MOCK_JIRA_VERIFY_RESPONSE.jiraColumns, targetFields: MOCK_JIRA_VERIFY_RESPONSE.targetFields, users: MOCK_JIRA_VERIFY_RESPONSE.users, }) - ) - store.dispatch(updateMetrics(params)) + ); + store.dispatch(updateMetrics(params)); store.dispatch( updateDeploymentFrequencySettings({ updateId: 0, label: 'organization', value: 'mock organization' }) - ) + ); store.dispatch( updateDeploymentFrequencySettings({ updateId: 0, label: 'pipelineName', value: 'mock pipeline name' }) - ) - store.dispatch(updateDeploymentFrequencySettings({ updateId: 0, label: 'step', value: 'mock step1' })) + ); + store.dispatch(updateDeploymentFrequencySettings({ updateId: 0, label: 'step', value: 'mock step1' })); store.dispatch( updatePipelineToolVerifyResponse({ pipelineList: [ @@ -115,262 +116,313 @@ describe('Report Step', () => { }, ], }) - ) + ); return render( - ) - } + ); + }; afterEach(() => { - store = null - jest.clearAllMocks() - }) + store = null; + jest.clearAllMocks(); + }); describe('render correctly', () => { it('should render report page', () => { - const { getByText } = setup(REQUIRED_DATA_LIST) - - expect(getByText('Board Metrics')).toBeInTheDocument() - expect(getByText('Velocity')).toBeInTheDocument() - expect(getByText('Cycle Time')).toBeInTheDocument() - expect(getByText('DORA Metrics')).toBeInTheDocument() - expect(getByText('Lead Time For Changes')).toBeInTheDocument() - expect(getByText('Deployment Frequency')).toBeInTheDocument() - expect(getByText('Change Failure Rate')).toBeInTheDocument() - expect(getByText('Mean Time To Recovery')).toBeInTheDocument() - }) + setup(REQUIRED_DATA_LIST); + + expect(screen.getByText('Board Metrics')).toBeInTheDocument(); + expect(screen.getByText('Velocity')).toBeInTheDocument(); + expect(screen.getByText('Cycle Time')).toBeInTheDocument(); + expect(screen.getByText('DORA Metrics')).toBeInTheDocument(); + expect(screen.getByText('Lead Time For Changes')).toBeInTheDocument(); + expect(screen.getByText('Deployment Frequency')).toBeInTheDocument(); + expect(screen.getByText('Change Failure Rate')).toBeInTheDocument(); + expect(screen.getByText('Mean Time To Recovery')).toBeInTheDocument(); + }); it('should render loading page when report data is empty', () => { - reportHook.current.reportData = EMPTY_REPORT_VALUES + reportHook.current.reportData = EMPTY_REPORT_VALUES; - const { getAllByTestId } = setup(REQUIRED_DATA_LIST) + setup(REQUIRED_DATA_LIST); - expect(getAllByTestId('loading-page')).toHaveLength(6) - }) + expect(screen.getAllByTestId('loading-page')).toHaveLength(6); + }); it('should render the velocity component with correct props', async () => { - const { getByText } = setup([REQUIRED_DATA_LIST[1]]) + setup([REQUIRED_DATA_LIST[1]]); - expect(getByText('20')).toBeInTheDocument() - expect(getByText('14')).toBeInTheDocument() - }) + expect(screen.getByText('20')).toBeInTheDocument(); + expect(screen.getByText('14')).toBeInTheDocument(); + }); it('should render the CycleTime component with correct props', () => { - const { getByText } = setup([REQUIRED_DATA_LIST[2]]) + setup([REQUIRED_DATA_LIST[2]]); - expect(getByText('30.26')).toBeInTheDocument() - expect(getByText('21.18')).toBeInTheDocument() - }) + expect(screen.getByText('30.26')).toBeInTheDocument(); + expect(screen.getByText('21.18')).toBeInTheDocument(); + }); it('should render the Lead Time For Change component with correct props', () => { - const { getByText } = setup([REQUIRED_DATA_LIST[4]]) + setup([REQUIRED_DATA_LIST[4]]); - expect(getByText('60.79')).toBeInTheDocument() - expect(getByText('39.03')).toBeInTheDocument() - expect(getByText('99.82')).toBeInTheDocument() - }) + expect(screen.getByText('60.79')).toBeInTheDocument(); + expect(screen.getByText('39.03')).toBeInTheDocument(); + expect(screen.getByText('99.82')).toBeInTheDocument(); + }); it('should render the Deployment frequency component with correct props', () => { - const { getByText } = setup([REQUIRED_DATA_LIST[5]]) + setup([REQUIRED_DATA_LIST[5]]); - expect(getByText('0.40')).toBeInTheDocument() - }) + expect(screen.getByText('0.40')).toBeInTheDocument(); + }); it('should render the Change failure rate component with correct props', () => { - const { getByText } = setup([REQUIRED_DATA_LIST[6]]) + setup([REQUIRED_DATA_LIST[6]]); - expect(getByText('0.00')).toBeInTheDocument() - expect(getByText('% (0/6)')).toBeInTheDocument() - }) + expect(screen.getByText('0.00')).toBeInTheDocument(); + expect(screen.getByText('% (0/6)')).toBeInTheDocument(); + }); it('should render the Mean time to recovery component with correct props', () => { - const { getByText } = setup([REQUIRED_DATA_LIST[7]]) + setup([REQUIRED_DATA_LIST[7]]); - expect(getByText('4.00')).toBeInTheDocument() - }) + expect(screen.getByText('4.00')).toBeInTheDocument(); + }); it('should show errorMessage when generating report has error message', () => { - reportHook.current.errorMessage = 'error message' + reportHook.current.errorMessage = 'error message'; - const { getByText } = setup(['']) + setup(['']); - expect(getByText('error message')).toBeInTheDocument() - }) - }) + expect(screen.getByText('error message')).toBeInTheDocument(); + }); + }); describe('behavior', () => { it('should call handleBack method when clicking back button given back button enabled', async () => { - const { getByText } = setup(['']) + setup(['']); - const back = getByText(BACK) - await userEvent.click(back) - expect(backStep).toHaveBeenCalledTimes(1) - }) + const back = screen.getByText(PREVIOUS); + await userEvent.click(back); + + expect(backStep).toHaveBeenCalledTimes(1); + }); it('should call handleSaveMock method when clicking save button', async () => { - const { getByText } = setup(['']) + setup(['']); + + const save = screen.getByText(SAVE); + await userEvent.click(save); - const save = getByText(SAVE) - await userEvent.click(save) - expect(handleSaveMock).toHaveBeenCalledTimes(1) - }) + expect(handleSaveMock).toHaveBeenCalledTimes(1); + }); it('should check error page show when isReportError is true', () => { - reportHook.current.isServerError = true - reportHook.current.errorMessage = 'error message' - - setup([REQUIRED_DATA_LIST[1]]) - - expect(navigateMock).toHaveBeenCalledWith(ERROR_PAGE_ROUTE) - }) - - it('should call resetProps and updateProps when remaining time is less than or equal to 5 minutes', async () => { - const initExportValidityTimeMin = 30 - React.useState = jest.fn().mockReturnValue([ - initExportValidityTimeMin, - () => { - jest.fn() - }, - ]) - const resetProps = jest.fn() - const updateProps = jest.fn() - notificationHook.current.resetProps = resetProps - notificationHook.current.updateProps = updateProps - jest.useFakeTimers() - - setup(['']) - - expect(resetProps).not.toBeCalled() + reportHook.current.isServerError = true; + reportHook.current.errorMessage = 'error message'; + + setup([REQUIRED_DATA_LIST[1]]); + + expect(navigateMock).toHaveBeenCalledWith(ERROR_PAGE_ROUTE); + }); + + it('should call resetProps and updateProps when remaining time is less than or equal to 5 minutes', () => { + const resetProps = jest.fn(); + const updateProps = jest.fn(); + notificationHook.current.resetProps = resetProps; + notificationHook.current.updateProps = updateProps; + jest.useFakeTimers(); + + setup(['']); + + expect(resetProps).not.toBeCalled(); expect(updateProps).not.toBeCalledWith({ open: true, title: MESSAGE.EXPIRE_IN_FIVE_MINUTES, closeAutomatically: true, - }) - await act(async () => { - return jest.advanceTimersByTime(500000) - }) + }); + + jest.advanceTimersByTime(500000); + expect(updateProps).not.toBeCalledWith({ open: true, title: MESSAGE.EXPIRE_IN_FIVE_MINUTES, closeAutomatically: true, - }) - await act(async () => { - return jest.advanceTimersByTime(1000000) - }) + }); + + jest.advanceTimersByTime(1000000); expect(updateProps).toBeCalledWith({ open: true, title: MESSAGE.EXPIRE_IN_FIVE_MINUTES, closeAutomatically: true, - }) + }); + + jest.useRealTimers(); + }); + + it.each([[REQUIRED_DATA_LIST[1]], [REQUIRED_DATA_LIST[4]]])( + 'should render detail page when clicking show more button given metric %s', + async (requiredData) => { + setup([requiredData]); + + await userEvent.click(screen.getByText(SHOW_MORE)); - jest.useRealTimers() - }) - }) + await waitFor(() => { + expect(screen.queryByText(SHOW_MORE)).not.toBeInTheDocument(); + }); + expect(screen.getByText(BACK)).toBeInTheDocument(); + } + ); + + it.each([[REQUIRED_DATA_LIST[1]], [REQUIRED_DATA_LIST[4]]])( + 'should return report page when clicking back button in Breadcrumb in detail page given metric %s', + async (requiredData) => { + setup([requiredData]); + + await userEvent.click(screen.getByText(SHOW_MORE)); + + await waitFor(() => { + expect(screen.queryByText(SHOW_MORE)).not.toBeInTheDocument(); + }); + + await userEvent.click(screen.getByText(BACK)); + + await waitFor(() => { + expect(screen.queryByText(BACK)).not.toBeInTheDocument(); + }); + expect(screen.getByText(SHOW_MORE)).toBeInTheDocument(); + } + ); + + it.each([[REQUIRED_DATA_LIST[1]], [REQUIRED_DATA_LIST[4]]])( + 'should return report page when clicking previous button in detail page given metric %s', + async (requiredData) => { + setup([requiredData]); + + const showMore = screen.getByText(SHOW_MORE); + + await userEvent.click(showMore); + + await waitFor(() => { + expect(screen.queryByText(SHOW_MORE)).not.toBeInTheDocument(); + }); + const previous = screen.getByText(PREVIOUS); + + await userEvent.click(previous); + + await waitFor(() => { + expect(screen.getByText(SHOW_MORE)).toBeInTheDocument(); + }); + } + ); + }); describe('export pipeline data', () => { it('should not show export pipeline button when not selecting deployment frequency', () => { - const { queryByText } = setup([REQUIRED_DATA_LIST[1]]) + const { queryByText } = setup([REQUIRED_DATA_LIST[1]]); - const exportPipelineButton = queryByText(EXPORT_PIPELINE_DATA) + const exportPipelineButton = queryByText(EXPORT_PIPELINE_DATA); - expect(exportPipelineButton).not.toBeInTheDocument() - }) + expect(exportPipelineButton).not.toBeInTheDocument(); + }); it.each([[REQUIRED_DATA_LIST[4]], [REQUIRED_DATA_LIST[5]], [REQUIRED_DATA_LIST[6]], [REQUIRED_DATA_LIST[7]]])( 'should show export pipeline button when selecting %s', (requiredData) => { - const { getByText } = setup([requiredData]) + setup([requiredData]); - const exportPipelineButton = getByText(EXPORT_PIPELINE_DATA) + const exportPipelineButton = screen.getByText(EXPORT_PIPELINE_DATA); - expect(exportPipelineButton).toBeInTheDocument() + expect(exportPipelineButton).toBeInTheDocument(); } - ) + ); it('should call fetchExportData when clicking "Export pipeline data"', async () => { - const { result } = renderHook(() => useExportCsvEffect()) - const { getByText } = setup([REQUIRED_DATA_LIST[6]]) + const { result } = renderHook(() => useExportCsvEffect()); + setup([REQUIRED_DATA_LIST[6]]); + + const exportButton = screen.getByText(EXPORT_PIPELINE_DATA); + expect(exportButton).toBeInTheDocument(); + await userEvent.click(exportButton); - const exportButton = getByText(EXPORT_PIPELINE_DATA) - expect(exportButton).toBeInTheDocument() - await userEvent.click(exportButton) expect(result.current.fetchExportData).toBeCalledWith({ csvTimeStamp: 0, dataType: 'pipeline', endDate: '', startDate: '', - }) - }) - }) + }); + }); + }); describe('export board data', () => { it('should not show export board button when not selecting board metrics', () => { - const { queryByText } = setup([REQUIRED_DATA_LIST[4]]) + const { queryByText } = setup([REQUIRED_DATA_LIST[4]]); - const exportPipelineButton = queryByText(EXPORT_BOARD_DATA) + const exportPipelineButton = queryByText(EXPORT_BOARD_DATA); - expect(exportPipelineButton).not.toBeInTheDocument() - }) + expect(exportPipelineButton).not.toBeInTheDocument(); + }); it.each([[REQUIRED_DATA_LIST[1]], [REQUIRED_DATA_LIST[2]]])( 'should show export board button when selecting %s', (requiredData) => { - const { getByText } = setup([requiredData]) + setup([requiredData]); - const exportPipelineButton = getByText(EXPORT_BOARD_DATA) + const exportPipelineButton = screen.getByText(EXPORT_BOARD_DATA); - expect(exportPipelineButton).toBeInTheDocument() + expect(exportPipelineButton).toBeInTheDocument(); } - ) + ); it('should call fetchExportData when clicking "Export board data"', async () => { - const { result } = renderHook(() => useExportCsvEffect()) - const { getByText } = setup([REQUIRED_DATA_LIST[2]]) + const { result } = renderHook(() => useExportCsvEffect()); + setup([REQUIRED_DATA_LIST[2]]); - const exportButton = getByText(EXPORT_BOARD_DATA) - expect(exportButton).toBeInTheDocument() - await userEvent.click(exportButton) + const exportButton = screen.getByText(EXPORT_BOARD_DATA); + expect(exportButton).toBeInTheDocument(); + await userEvent.click(exportButton); expect(result.current.fetchExportData).toBeCalledWith({ csvTimeStamp: 0, dataType: 'board', endDate: '', startDate: '', - }) - }) - }) + }); + }); + }); describe('export metric data', () => { it('should show export metric button when visiting this page', () => { - const { getByText } = setup(['']) + setup(['']); - const exportMetricButton = getByText(EXPORT_METRIC_DATA) + const exportMetricButton = screen.getByText(EXPORT_METRIC_DATA); - expect(exportMetricButton).toBeInTheDocument() - }) + expect(exportMetricButton).toBeInTheDocument(); + }); it('should call fetchExportData when clicking "Export metric data"', async () => { - const { result } = renderHook(() => useExportCsvEffect()) - setup(['']) + const { result } = renderHook(() => useExportCsvEffect()); + setup(['']); - const exportButton = screen.getByText(EXPORT_METRIC_DATA) - expect(exportButton).toBeInTheDocument() - await userEvent.click(exportButton) + const exportButton = screen.getByText(EXPORT_METRIC_DATA); + expect(exportButton).toBeInTheDocument(); + await userEvent.click(exportButton); expect(result.current.fetchExportData).toBeCalledWith({ csvTimeStamp: 0, dataType: 'metric', endDate: '', startDate: '', - }) - }) + }); + }); it('should show errorMessage when clicking export metric button given csv not exist', async () => { - setup(['']) - await userEvent.click(screen.getByText(EXPORT_METRIC_DATA)) - expect(screen.getByText('Export metric data')).toBeInTheDocument() - }) - }) -}) + setup(['']); + await userEvent.click(screen.getByText(EXPORT_METRIC_DATA)); + expect(screen.getByText('Export metric data')).toBeInTheDocument(); + }); + }); +}); diff --git a/frontend/__tests__/src/components/ProjectDescripution.test.tsx b/frontend/__tests__/src/components/ProjectDescripution.test.tsx index c90533f0c0..6da87ac6b6 100644 --- a/frontend/__tests__/src/components/ProjectDescripution.test.tsx +++ b/frontend/__tests__/src/components/ProjectDescripution.test.tsx @@ -1,11 +1,11 @@ -import { render } from '@testing-library/react' -import { ProjectDescription } from '@src/components/ProjectDescription' -import { PROJECT_DESCRIPTION } from '../fixtures' +import { render } from '@testing-library/react'; +import { ProjectDescription } from '@src/components/ProjectDescription'; +import { PROJECT_DESCRIPTION } from '../fixtures'; describe('ProjectDescription', () => { it('should show project description', () => { - const { getByRole } = render() + const { getByRole } = render(); - expect(getByRole('description').textContent).toContain(PROJECT_DESCRIPTION) - }) -}) + expect(getByRole('description').textContent).toContain(PROJECT_DESCRIPTION); + }); +}); diff --git a/frontend/__tests__/src/context/boardSlice.test.ts b/frontend/__tests__/src/context/boardSlice.test.ts index f3b752bb97..0b1b5e5c69 100644 --- a/frontend/__tests__/src/context/boardSlice.test.ts +++ b/frontend/__tests__/src/context/boardSlice.test.ts @@ -2,37 +2,37 @@ import boardReducer, { updateBoard, updateBoardVerifyState, updateJiraVerifyResponse, -} from '@src/context/config/configSlice' -import { MOCK_JIRA_VERIFY_RESPONSE } from '../fixtures' -import initialConfigState from '../initialConfigState' +} from '@src/context/config/configSlice'; +import { MOCK_JIRA_VERIFY_RESPONSE } from '../fixtures'; +import initialConfigState from '../initialConfigState'; describe('board reducer', () => { it('should return false when handle initial state', () => { - const result = boardReducer(undefined, { type: 'unknown' }) + const result = boardReducer(undefined, { type: 'unknown' }); - expect(result.board.isVerified).toEqual(false) - }) + expect(result.board.isVerified).toEqual(false); + }); it('should return true when handle changeBoardVerifyState given isBoardVerified is true', () => { - const result = boardReducer(initialConfigState, updateBoardVerifyState(true)) + const result = boardReducer(initialConfigState, updateBoardVerifyState(true)); - expect(result.board.isVerified).toEqual(true) - }) + expect(result.board.isVerified).toEqual(true); + }); it('should update board fields when change board fields input', () => { - const board = boardReducer(initialConfigState, updateBoard({ boardId: '1' })) + const board = boardReducer(initialConfigState, updateBoard({ boardId: '1' })); - expect(board.board.config.boardId).toEqual('1') - }) + expect(board.board.config.boardId).toEqual('1'); + }); describe('boardVerifyResponse reducer', () => { it('should show empty array when handle initial state', () => { - const boardVerifyResponse = boardReducer(undefined, { type: 'unknown' }) + const boardVerifyResponse = boardReducer(undefined, { type: 'unknown' }); - expect(boardVerifyResponse.board.verifiedResponse.jiraColumns).toEqual([]) - expect(boardVerifyResponse.board.verifiedResponse.targetFields).toEqual([]) - expect(boardVerifyResponse.board.verifiedResponse.users).toEqual([]) - }) + expect(boardVerifyResponse.board.verifiedResponse.jiraColumns).toEqual([]); + expect(boardVerifyResponse.board.verifiedResponse.targetFields).toEqual([]); + expect(boardVerifyResponse.board.verifiedResponse.users).toEqual([]); + }); it('should store jiraColumns,targetFields,users data when get network jira verify response', () => { const boardVerifyResponse = boardReducer( @@ -42,11 +42,11 @@ describe('board reducer', () => { targetFields: MOCK_JIRA_VERIFY_RESPONSE.targetFields, users: MOCK_JIRA_VERIFY_RESPONSE.users, }) - ) - - expect(boardVerifyResponse.board.verifiedResponse.jiraColumns).toEqual(MOCK_JIRA_VERIFY_RESPONSE.jiraColumns) - expect(boardVerifyResponse.board.verifiedResponse.targetFields).toEqual(MOCK_JIRA_VERIFY_RESPONSE.targetFields) - expect(boardVerifyResponse.board.verifiedResponse.users).toEqual(MOCK_JIRA_VERIFY_RESPONSE.users) - }) - }) -}) + ); + + expect(boardVerifyResponse.board.verifiedResponse.jiraColumns).toEqual(MOCK_JIRA_VERIFY_RESPONSE.jiraColumns); + expect(boardVerifyResponse.board.verifiedResponse.targetFields).toEqual(MOCK_JIRA_VERIFY_RESPONSE.targetFields); + expect(boardVerifyResponse.board.verifiedResponse.users).toEqual(MOCK_JIRA_VERIFY_RESPONSE.users); + }); + }); +}); diff --git a/frontend/__tests__/src/context/configSlice.test.ts b/frontend/__tests__/src/context/configSlice.test.ts index 6726232a51..3797fb6b38 100644 --- a/frontend/__tests__/src/context/configSlice.test.ts +++ b/frontend/__tests__/src/context/configSlice.test.ts @@ -5,9 +5,9 @@ import configReducer, { updateMetrics, updateProjectCreatedState, updateProjectName, -} from '@src/context/config/configSlice' -import { CHINA_CALENDAR, CONFIG_PAGE_VERIFY_IMPORT_ERROR_MESSAGE, REGULAR_CALENDAR, VELOCITY } from '../fixtures' -import initialConfigState from '../initialConfigState' +} from '@src/context/config/configSlice'; +import { CHINA_CALENDAR, CONFIG_PAGE_VERIFY_IMPORT_ERROR_MESSAGE, REGULAR_CALENDAR, VELOCITY } from '../fixtures'; +import initialConfigState from '../initialConfigState'; const MockBasicState = { projectName: 'Test Project', @@ -16,70 +16,70 @@ const MockBasicState = { endDate: new Date(), }, metrics: ['Metric 1', 'Metric 2'], -} +}; describe('config reducer', () => { it('should be default value when init render config page', () => { - const config = configReducer(undefined, { type: 'unknown' }).basic + const config = configReducer(undefined, { type: 'unknown' }).basic; - expect(config.projectName).toEqual('') - expect(config.calendarType).toEqual(REGULAR_CALENDAR) - expect(config.dateRange).toEqual({ startDate: null, endDate: null }) - }) + expect(config.projectName).toEqual(''); + expect(config.calendarType).toEqual(REGULAR_CALENDAR); + expect(config.dateRange).toEqual({ startDate: null, endDate: null }); + }); it('should update project name when change project name input', () => { - const config = configReducer(initialConfigState, updateProjectName('mock project name')).basic + const config = configReducer(initialConfigState, updateProjectName('mock project name')).basic; - expect(config.projectName).toEqual('mock project name') - }) + expect(config.projectName).toEqual('mock project name'); + }); it('should update calendar when change calendar types', () => { - const config = configReducer(initialConfigState, updateCalendarType(CHINA_CALENDAR)).basic + const config = configReducer(initialConfigState, updateCalendarType(CHINA_CALENDAR)).basic; - expect(config.calendarType).toEqual(CHINA_CALENDAR) - }) + expect(config.calendarType).toEqual(CHINA_CALENDAR); + }); it('should update date range when change date', () => { - const today = new Date().getMilliseconds() - const config = configReducer(initialConfigState, updateDateRange({ startDate: today, endDate: '' })).basic + const today = new Date().getMilliseconds(); + const config = configReducer(initialConfigState, updateDateRange({ startDate: today, endDate: '' })).basic; - expect(config.dateRange.startDate).toEqual(today) - expect(config.dateRange.endDate).toEqual('') - }) + expect(config.dateRange.startDate).toEqual(today); + expect(config.dateRange.endDate).toEqual(''); + }); it('should isProjectCreated is false when import file', () => { - const config = configReducer(initialConfigState, updateProjectCreatedState(false)) + const config = configReducer(initialConfigState, updateProjectCreatedState(false)); - expect(config.isProjectCreated).toEqual(false) - }) + expect(config.isProjectCreated).toEqual(false); + }); it('should update required data when change require data selections', () => { - const config = configReducer(initialConfigState, updateMetrics([VELOCITY])).basic + const config = configReducer(initialConfigState, updateMetrics([VELOCITY])).basic; - expect(config.metrics).toEqual([VELOCITY]) - }) + expect(config.metrics).toEqual([VELOCITY]); + }); it('should set warningMessage is null when projectName startDate endDate and metrics data have value', () => { const initialState = { ...initialConfigState, isProjectCreated: false, - } + }; const action = { type: 'config/updateBasicConfigState', payload: MockBasicState, - } + }; - const config = configReducer(initialState, action) + const config = configReducer(initialState, action); - expect(config.warningMessage).toBeNull() - }) + expect(config.warningMessage).toBeNull(); + }); it('should reset ImportedData when input new config', () => { - const initialState = initialConfigState + const initialState = initialConfigState; - const config = configReducer(initialState, resetImportedData()) + const config = configReducer(initialState, resetImportedData()); - expect(config).toEqual(initialConfigState) - }) + expect(config).toEqual(initialConfigState); + }); it.each([ ['projectName', { ...MockBasicState, projectName: '' }], @@ -90,14 +90,14 @@ describe('config reducer', () => { const initialState = { ...initialConfigState, isProjectCreated: false, - } + }; const action = { type: 'config/updateBasicConfigState', payload, - } + }; - const config = configReducer(initialState, action) + const config = configReducer(initialState, action); - expect(config.warningMessage).toEqual(CONFIG_PAGE_VERIFY_IMPORT_ERROR_MESSAGE) - }) -}) + expect(config.warningMessage).toEqual(CONFIG_PAGE_VERIFY_IMPORT_ERROR_MESSAGE); + }); +}); diff --git a/frontend/__tests__/src/context/headerSlice.test.ts b/frontend/__tests__/src/context/headerSlice.test.ts index 235ffcbe72..140c2dbcc3 100644 --- a/frontend/__tests__/src/context/headerSlice.test.ts +++ b/frontend/__tests__/src/context/headerSlice.test.ts @@ -1,16 +1,16 @@ -import headerReducer, { saveVersion } from '@src/context/header/headerSlice' -import { VERSION_RESPONSE } from '../fixtures' +import headerReducer, { saveVersion } from '@src/context/header/headerSlice'; +import { VERSION_RESPONSE } from '../fixtures'; describe('header reducer', () => { it('should get empty when handle initial state', () => { - const header = headerReducer(undefined, { type: 'unknown' }) + const header = headerReducer(undefined, { type: 'unknown' }); - expect(header.version).toEqual('') - }) + expect(header.version).toEqual(''); + }); it('should set 1.11 when handle saveVersion', () => { - const header = headerReducer(undefined, saveVersion(VERSION_RESPONSE.version)) + const header = headerReducer(undefined, saveVersion(VERSION_RESPONSE.version)); - expect(header.version).toEqual(VERSION_RESPONSE.version) - }) -}) + expect(header.version).toEqual(VERSION_RESPONSE.version); + }); +}); diff --git a/frontend/__tests__/src/context/metricsSlice.test.ts b/frontend/__tests__/src/context/metricsSlice.test.ts index 116ce03792..ec83d9e548 100644 --- a/frontend/__tests__/src/context/metricsSlice.test.ts +++ b/frontend/__tests__/src/context/metricsSlice.test.ts @@ -16,11 +16,11 @@ import saveMetricsSettingReducer, { updatePipelineSettings, updatePipelineStep, updateTreatFlagCardAsBlock, -} from '@src/context/Metrics/metricsSlice' -import { store } from '@src/store' -import { CLASSIFICATION_WARNING_MESSAGE, NO_RESULT_DASH, PIPELINE_SETTING_TYPES } from '../fixtures' -import { ASSIGNEE_FILTER_TYPES, MESSAGE } from '@src/constants/resources' -import { setupStore } from '../utils/setupStoreUtil' +} from '@src/context/Metrics/metricsSlice'; +import { store } from '@src/store'; +import { CLASSIFICATION_WARNING_MESSAGE, NO_RESULT_DASH, PIPELINE_SETTING_TYPES } from '../fixtures'; +import { ASSIGNEE_FILTER_TYPES, MESSAGE } from '@src/constants/resources'; +import { setupStore } from '../utils/setupStoreUtil'; const initState = { jiraColumns: [], @@ -52,7 +52,7 @@ const initState = { realDoneWarningMessage: null, deploymentWarningMessage: [], leadTimeWarningMessage: [], -} +}; const mockJiraResponse = { targetFields: [{ key: 'issuetype', name: 'Issue Type', flag: false }], @@ -81,22 +81,22 @@ const mockJiraResponse = { }, }, ], -} +}; describe('saveMetricsSetting reducer', () => { it('should show empty array when handle initial state', () => { - const savedMetricsSetting = saveMetricsSettingReducer(undefined, { type: 'unknown' }) + const savedMetricsSetting = saveMetricsSettingReducer(undefined, { type: 'unknown' }); - expect(savedMetricsSetting.users).toEqual([]) - expect(savedMetricsSetting.targetFields).toEqual([]) - expect(savedMetricsSetting.jiraColumns).toEqual([]) - expect(savedMetricsSetting.doneColumn).toEqual([]) - expect(savedMetricsSetting.cycleTimeSettings).toEqual([]) + expect(savedMetricsSetting.users).toEqual([]); + expect(savedMetricsSetting.targetFields).toEqual([]); + expect(savedMetricsSetting.jiraColumns).toEqual([]); + expect(savedMetricsSetting.doneColumn).toEqual([]); + expect(savedMetricsSetting.cycleTimeSettings).toEqual([]); expect(savedMetricsSetting.deploymentFrequencySettings).toEqual([ { id: 0, organization: '', pipelineName: '', step: '', branches: [] }, - ]) - expect(savedMetricsSetting.treatFlagCardAsBlock).toBe(true) - expect(savedMetricsSetting.assigneeFilter).toBe(ASSIGNEE_FILTER_TYPES.LAST_ASSIGNEE) + ]); + expect(savedMetricsSetting.treatFlagCardAsBlock).toBe(true); + expect(savedMetricsSetting.assigneeFilter).toBe(ASSIGNEE_FILTER_TYPES.LAST_ASSIGNEE); expect(savedMetricsSetting.importedData).toEqual({ importedCrews: [], importedAssigneeFilter: ASSIGNEE_FILTER_TYPES.LAST_ASSIGNEE, @@ -108,8 +108,8 @@ describe('saveMetricsSetting reducer', () => { importedClassification: [], importedDeployment: [], importedPipelineCrews: [], - }) - }) + }); + }); it('should store updated targetFields when its value changed', () => { const mockUpdatedTargetFields = { @@ -118,60 +118,60 @@ describe('saveMetricsSetting reducer', () => { { key: 'parent', name: 'Parent', flag: false }, { key: 'customfield_10020', name: 'Sprint', flag: false }, ], - } + }; const savedMetricsSetting = saveMetricsSettingReducer( initState, saveTargetFields({ targetFields: mockUpdatedTargetFields.targetFields, }) - ) + ); - expect(savedMetricsSetting.targetFields).toEqual(mockUpdatedTargetFields) - expect(savedMetricsSetting.users).toEqual([]) - expect(savedMetricsSetting.jiraColumns).toEqual([]) - }) + expect(savedMetricsSetting.targetFields).toEqual(mockUpdatedTargetFields); + expect(savedMetricsSetting.users).toEqual([]); + expect(savedMetricsSetting.jiraColumns).toEqual([]); + }); it('should store updated doneColumn when its value changed', () => { const mockUpdatedDoneColumn = { doneColumn: ['DONE', 'CANCELLED'], - } + }; const savedMetricsSetting = saveMetricsSettingReducer( initState, saveDoneColumn({ doneColumn: mockUpdatedDoneColumn.doneColumn, }) - ) + ); - expect(savedMetricsSetting.doneColumn).toEqual(mockUpdatedDoneColumn) - }) + expect(savedMetricsSetting.doneColumn).toEqual(mockUpdatedDoneColumn); + }); it('should store updated users when its value changed', () => { const mockUpdatedUsers = { users: ['userOne', 'userTwo', 'userThree'], - } + }; const savedMetricsSetting = saveMetricsSettingReducer( initState, saveUsers({ users: mockUpdatedUsers.users, }) - ) + ); - expect(savedMetricsSetting.users).toEqual(mockUpdatedUsers) - }) + expect(savedMetricsSetting.users).toEqual(mockUpdatedUsers); + }); it('should store saved cycleTimeSettings when its value changed', () => { const mockSavedCycleTimeSettings = { cycleTimeSettings: [{ name: 'TODO', value: 'To do' }], - } + }; const savedMetricsSetting = saveMetricsSettingReducer( initState, saveCycleTimeSettings({ cycleTimeSettings: mockSavedCycleTimeSettings.cycleTimeSettings, }) - ) + ); - expect(savedMetricsSetting.cycleTimeSettings).toEqual(mockSavedCycleTimeSettings) - }) + expect(savedMetricsSetting.cycleTimeSettings).toEqual(mockSavedCycleTimeSettings); + }); it('should update metricsImportedData when its value changed given initial state', () => { const mockMetricsImportedData = { @@ -186,8 +186,11 @@ describe('saveMetricsSetting reducer', () => { deployment: [{ id: 0, organization: 'organization', pipelineName: 'pipelineName', step: 'step' }], leadTime: [], pipelineCrews: [], - } - const savedMetricsSetting = saveMetricsSettingReducer(initState, updateMetricsImportedData(mockMetricsImportedData)) + }; + const savedMetricsSetting = saveMetricsSettingReducer( + initState, + updateMetricsImportedData(mockMetricsImportedData) + ); expect(savedMetricsSetting.importedData).toEqual({ importedCrews: mockMetricsImportedData.crews, @@ -201,8 +204,8 @@ describe('saveMetricsSetting reducer', () => { importedClassification: mockMetricsImportedData.classification, importedDeployment: mockMetricsImportedData.deployment, importedLeadTime: mockMetricsImportedData.leadTime, - }) - }) + }); + }); it('should not update metricsImportedData when imported data is broken given initial state', () => { const mockMetricsImportedData = { @@ -215,26 +218,29 @@ describe('saveMetricsSetting reducer', () => { classification: null, deployment: null, leadTime: null, - } + }; - const savedMetricsSetting = saveMetricsSettingReducer(initState, updateMetricsImportedData(mockMetricsImportedData)) - - expect(savedMetricsSetting.users).toEqual([]) - expect(savedMetricsSetting.targetFields).toEqual([]) - expect(savedMetricsSetting.jiraColumns).toEqual([]) - expect(savedMetricsSetting.doneColumn).toEqual([]) - expect(savedMetricsSetting.cycleTimeSettings).toEqual([]) - expect(savedMetricsSetting.treatFlagCardAsBlock).toEqual(true) + const savedMetricsSetting = saveMetricsSettingReducer( + initState, + updateMetricsImportedData(mockMetricsImportedData) + ); + + expect(savedMetricsSetting.users).toEqual([]); + expect(savedMetricsSetting.targetFields).toEqual([]); + expect(savedMetricsSetting.jiraColumns).toEqual([]); + expect(savedMetricsSetting.doneColumn).toEqual([]); + expect(savedMetricsSetting.cycleTimeSettings).toEqual([]); + expect(savedMetricsSetting.treatFlagCardAsBlock).toEqual(true); expect(savedMetricsSetting.deploymentFrequencySettings).toEqual([ { id: 0, organization: '', pipelineName: '', step: '', branches: [] }, - ]) - }) + ]); + }); it('should update metricsState when its value changed given isProjectCreated is false and selectedDoneColumns', () => { const mockUpdateMetricsStateArguments = { ...mockJiraResponse, isProjectCreated: false, - } + }; const savedMetricsSetting = saveMetricsSettingReducer( { ...initState, @@ -250,23 +256,23 @@ describe('saveMetricsSetting reducer', () => { }, }, updateMetricsState(mockUpdateMetricsStateArguments) - ) + ); - expect(savedMetricsSetting.targetFields).toEqual([{ key: 'issuetype', name: 'Issue Type', flag: true }]) - expect(savedMetricsSetting.users).toEqual(['User B']) + expect(savedMetricsSetting.targetFields).toEqual([{ key: 'issuetype', name: 'Issue Type', flag: true }]); + expect(savedMetricsSetting.users).toEqual(['User B']); expect(savedMetricsSetting.cycleTimeSettings).toEqual([ { name: 'Done', value: 'Done' }, { name: 'Doing', value: NO_RESULT_DASH }, { name: 'Testing', value: NO_RESULT_DASH }, - ]) - expect(savedMetricsSetting.doneColumn).toEqual(['DONE']) - }) + ]); + expect(savedMetricsSetting.doneColumn).toEqual(['DONE']); + }); it('should update metricsState when its value changed given isProjectCreated is false and no selectedDoneColumns', () => { const mockUpdateMetricsStateArguments = { ...mockJiraResponse, isProjectCreated: false, - } + }; const savedMetricsSetting = saveMetricsSettingReducer( { @@ -281,41 +287,41 @@ describe('saveMetricsSetting reducer', () => { }, }, updateMetricsState(mockUpdateMetricsStateArguments) - ) + ); - expect(savedMetricsSetting.doneColumn).toEqual([]) - }) + expect(savedMetricsSetting.doneColumn).toEqual([]); + }); it('should update metricsState when its value changed given isProjectCreated is true', () => { const mockUpdateMetricsStateArguments = { ...mockJiraResponse, isProjectCreated: true, - } + }; const savedMetricsSetting = saveMetricsSettingReducer( initState, updateMetricsState(mockUpdateMetricsStateArguments) - ) + ); - expect(savedMetricsSetting.targetFields).toEqual([{ key: 'issuetype', name: 'Issue Type', flag: false }]) - expect(savedMetricsSetting.users).toEqual(['User A', 'User B']) + expect(savedMetricsSetting.targetFields).toEqual([{ key: 'issuetype', name: 'Issue Type', flag: false }]); + expect(savedMetricsSetting.users).toEqual(['User A', 'User B']); expect(savedMetricsSetting.cycleTimeSettings).toEqual([ { name: 'Done', value: NO_RESULT_DASH }, { name: 'Doing', value: NO_RESULT_DASH }, { name: 'Testing', value: NO_RESULT_DASH }, - ]) - expect(savedMetricsSetting.doneColumn).toEqual([]) - }) + ]); + expect(savedMetricsSetting.doneColumn).toEqual([]); + }); it('should update deploymentFrequencySettings when handle updateDeploymentFrequencySettings given initial state', () => { const savedMetricsSetting = saveMetricsSettingReducer( initState, updateDeploymentFrequencySettings({ updateId: 0, label: 'Steps', value: 'step1' }) - ) + ); expect(savedMetricsSetting.deploymentFrequencySettings).toEqual([ { id: 0, organization: '', pipelineName: '', step: 'step1', branches: [] }, - ]) - }) + ]); + }); it('should update a deploymentFrequencySetting when handle updateDeploymentFrequencySettings given multiple deploymentFrequencySettings', () => { const multipleDeploymentFrequencySettingsInitState = { @@ -324,55 +330,55 @@ describe('saveMetricsSetting reducer', () => { { id: 0, organization: '', pipelineName: '', step: '', branches: [] }, { id: 1, organization: '', pipelineName: '', step: '', branches: [] }, ], - } + }; const updatedDeploymentFrequencySettings = [ { id: 0, organization: 'mock new organization', pipelineName: '', step: '', branches: [] }, { id: 1, organization: '', pipelineName: '', step: '', branches: [] }, - ] + ]; const savedMetricsSetting = saveMetricsSettingReducer( multipleDeploymentFrequencySettingsInitState, updateDeploymentFrequencySettings({ updateId: 0, label: 'organization', value: 'mock new organization' }) - ) + ); - expect(savedMetricsSetting.deploymentFrequencySettings).toEqual(updatedDeploymentFrequencySettings) - }) + expect(savedMetricsSetting.deploymentFrequencySettings).toEqual(updatedDeploymentFrequencySettings); + }); it('should add a deploymentFrequencySetting when handle addADeploymentFrequencySettings given initial state', () => { const addedDeploymentFrequencySettings = [ { id: 0, organization: '', pipelineName: '', step: '', branches: [] }, { id: 1, organization: '', pipelineName: '', step: '', branches: [] }, - ] + ]; - const savedMetricsSetting = saveMetricsSettingReducer(initState, addADeploymentFrequencySetting()) + const savedMetricsSetting = saveMetricsSettingReducer(initState, addADeploymentFrequencySetting()); - expect(savedMetricsSetting.deploymentFrequencySettings).toEqual(addedDeploymentFrequencySettings) - }) + expect(savedMetricsSetting.deploymentFrequencySettings).toEqual(addedDeploymentFrequencySettings); + }); it('should add a deploymentFrequencySetting when handle addADeploymentFrequencySettings but initState dont have DeploymentFrequencySettings', () => { - const addedDeploymentFrequencySettings = [{ id: 0, organization: '', pipelineName: '', step: '', branches: [] }] + const addedDeploymentFrequencySettings = [{ id: 0, organization: '', pipelineName: '', step: '', branches: [] }]; const initStateWithoutDeploymentFrequencySettings = { ...initState, deploymentFrequencySettings: [], - } + }; const savedMetricsSetting = saveMetricsSettingReducer( initStateWithoutDeploymentFrequencySettings, addADeploymentFrequencySetting() - ) + ); - expect(savedMetricsSetting.deploymentFrequencySettings).toEqual(addedDeploymentFrequencySettings) - }) + expect(savedMetricsSetting.deploymentFrequencySettings).toEqual(addedDeploymentFrequencySettings); + }); it('should delete a deploymentFrequencySetting when handle deleteADeploymentFrequencySettings given initial state', () => { - const savedMetricsSetting = saveMetricsSettingReducer(initState, deleteADeploymentFrequencySetting(0)) + const savedMetricsSetting = saveMetricsSettingReducer(initState, deleteADeploymentFrequencySetting(0)); - expect(savedMetricsSetting.deploymentFrequencySettings).toEqual([]) - }) + expect(savedMetricsSetting.deploymentFrequencySettings).toEqual([]); + }); it('should return deploymentFrequencySettings when call selectDeploymentFrequencySettings functions', () => { - expect(selectDeploymentFrequencySettings(store.getState())).toEqual(initState.deploymentFrequencySettings) - }) + expect(selectDeploymentFrequencySettings(store.getState())).toEqual(initState.deploymentFrequencySettings); + }); it('should init deploymentFrequencySettings when handle initDeploymentFrequencySettings given multiple deploymentFrequencySettings', () => { const multipleDeploymentFrequencySettingsInitState = { @@ -381,31 +387,31 @@ describe('saveMetricsSetting reducer', () => { { id: 0, organization: 'mockOrgName1', pipelineName: 'mockName1', step: 'step1', branches: [] }, { id: 1, organization: 'mockOrgName2', pipelineName: 'mockName2', step: 'step2', branches: [] }, ], - } + }; const savedMetricsSetting = saveMetricsSettingReducer( multipleDeploymentFrequencySettingsInitState, initDeploymentFrequencySettings() - ) + ); - expect(savedMetricsSetting.deploymentFrequencySettings).toEqual(initState.deploymentFrequencySettings) - }) + expect(savedMetricsSetting.deploymentFrequencySettings).toEqual(initState.deploymentFrequencySettings); + }); it('should return false when update TreatFlagCardAsBlock value given false', () => { - const savedMetricsSetting = saveMetricsSettingReducer(initState, updateTreatFlagCardAsBlock(false)) + const savedMetricsSetting = saveMetricsSettingReducer(initState, updateTreatFlagCardAsBlock(false)); - expect(savedMetricsSetting.treatFlagCardAsBlock).toBe(false) - }) + expect(savedMetricsSetting.treatFlagCardAsBlock).toBe(false); + }); describe('updatePipelineSettings', () => { const mockImportedDeployment = [ { id: 0, organization: 'mockOrganization1', pipelineName: 'mockPipelineName1', step: 'mockStep1', branches: [] }, { id: 1, organization: 'mockOrganization1', pipelineName: 'mockPipelineName2', step: 'mockStep2', branches: [] }, { id: 2, organization: 'mockOrganization2', pipelineName: 'mockPipelineName3', step: 'mockStep3', branches: [] }, - ] + ]; const mockImportedLeadTime = [ { id: 0, organization: 'mockOrganization1', pipelineName: 'mockPipelineName1', step: 'mockStep1', branches: [] }, - ] + ]; const mockInitState = { ...initState, importedData: { @@ -413,7 +419,7 @@ describe('saveMetricsSetting reducer', () => { importedDeployment: mockImportedDeployment, importedLeadTime: mockImportedLeadTime, }, - } + }; const mockPipelineList = [ { id: 'mockId1', @@ -424,7 +430,7 @@ describe('saveMetricsSetting reducer', () => { steps: ['mock step 1', 'mock step 2'], branches: [], }, - ] + ]; const testCases = [ { isProjectCreated: false, @@ -454,30 +460,30 @@ describe('saveMetricsSetting reducer', () => { leadTimeWarningMessage: [], }, }, - ] + ]; testCases.forEach(({ isProjectCreated, expectSetting }) => { it(`should update pipeline settings When call updatePipelineSettings given isProjectCreated ${isProjectCreated}`, () => { const savedMetricsSetting = saveMetricsSettingReducer( mockInitState, updatePipelineSettings({ pipelineList: mockPipelineList, isProjectCreated }) - ) + ); - expect(savedMetricsSetting.deploymentFrequencySettings).toEqual(expectSetting.deploymentFrequencySettings) - expect(savedMetricsSetting.deploymentWarningMessage).toEqual(expectSetting.deploymentWarningMessage) - }) - }) - }) + expect(savedMetricsSetting.deploymentFrequencySettings).toEqual(expectSetting.deploymentFrequencySettings); + expect(savedMetricsSetting.deploymentWarningMessage).toEqual(expectSetting.deploymentWarningMessage); + }); + }); + }); describe('updatePipelineSteps', () => { const mockImportedDeployment = [ { id: 0, organization: 'mockOrganization1', pipelineName: 'mockPipelineName1', step: 'mockStep1', branches: [] }, { id: 1, organization: 'mockOrganization1', pipelineName: 'mockPipelineName2', step: 'mockStep2', branches: [] }, { id: 2, organization: 'mockOrganization2', pipelineName: 'mockPipelineName3', step: 'mockStep3', branches: [] }, - ] + ]; const mockImportedLeadTime = [ { id: 0, organization: 'mockOrganization1', pipelineName: 'mockPipelineName1', step: 'mockStep1', branches: [] }, - ] + ]; const mockInitState = { ...initState, deploymentFrequencySettings: [ @@ -503,8 +509,8 @@ describe('saveMetricsSetting reducer', () => { { id: 1, organization: null, pipelineName: null, step: null }, ], leadTimeWarningMessage: [{ id: 0, organization: null, pipelineName: null, step: null }], - } - const mockSteps = ['mockStep1'] + }; + const mockSteps = ['mockStep1']; const testSettingsCases = [ { id: 0, @@ -543,11 +549,11 @@ describe('saveMetricsSetting reducer', () => { }, ], }, - ] + ]; testSettingsCases.forEach(({ id, type, steps, expectedSettings, expectedWarning }) => { - const settingsKey = 'deploymentFrequencySettings' - const warningKey = 'deploymentWarningMessage' + const settingsKey = 'deploymentFrequencySettings'; + const warningKey = 'deploymentWarningMessage'; it(`should update ${settingsKey} step when call updatePipelineSteps with id ${id}`, () => { const savedMetricsSetting = saveMetricsSettingReducer( @@ -557,19 +563,19 @@ describe('saveMetricsSetting reducer', () => { id: id, type: type, }) - ) + ); - expect(savedMetricsSetting[settingsKey]).toEqual(expectedSettings) - expect(savedMetricsSetting[warningKey]).toEqual(expectedWarning) - }) - }) - }) + expect(savedMetricsSetting[settingsKey]).toEqual(expectedSettings); + expect(savedMetricsSetting[warningKey]).toEqual(expectedWarning); + }); + }); + }); it('should set warningMessage have value when there are more values in the import file than in the response', () => { const mockUpdateMetricsStateArguments = { ...mockJiraResponse, isProjectCreated: false, - } + }; const savedMetricsSetting = saveMetricsSettingReducer( { ...initState, @@ -587,18 +593,18 @@ describe('saveMetricsSetting reducer', () => { }, }, updateMetricsState(mockUpdateMetricsStateArguments) - ) + ); expect(savedMetricsSetting.cycleTimeWarningMessage).toEqual( 'The column of ToDo is a deleted column, which means this column existed the time you saved config, but was deleted. Please confirm!' - ) - }) + ); + }); it('should set warningMessage have value when the values in the import file are less than those in the response', () => { const mockUpdateMetricsStateArguments = { ...mockJiraResponse, isProjectCreated: false, - } + }; const savedMetricsSetting = saveMetricsSettingReducer( { ...initState, @@ -611,18 +617,18 @@ describe('saveMetricsSetting reducer', () => { }, }, updateMetricsState(mockUpdateMetricsStateArguments) - ) + ); expect(savedMetricsSetting.cycleTimeWarningMessage).toEqual( 'The column of Testing is a new column. Please select a value for it!' - ) - }) + ); + }); it('should set warningMessage have value when the key value in the import file matches the value in the response, but the value does not match the fixed column', () => { const mockUpdateMetricsStateArguments = { ...mockJiraResponse, isProjectCreated: false, - } + }; const savedMetricsSetting = saveMetricsSettingReducer( { ...initState, @@ -635,18 +641,18 @@ describe('saveMetricsSetting reducer', () => { }, }, updateMetricsState(mockUpdateMetricsStateArguments) - ) + ); expect(savedMetricsSetting.cycleTimeWarningMessage).toEqual( 'The value of Doing in imported json is not in dropdown list now. Please select a value for it!' - ) - }) + ); + }); it('should set warningMessage null when the key value in the imported file matches the value in the response and the value matches the fixed column', () => { const mockUpdateMetricsStateArguments = { ...mockJiraResponse, isProjectCreated: false, - } + }; const savedMetricsSetting = saveMetricsSettingReducer( { ...initState, @@ -659,16 +665,16 @@ describe('saveMetricsSetting reducer', () => { }, }, updateMetricsState(mockUpdateMetricsStateArguments) - ) + ); - expect(savedMetricsSetting.cycleTimeWarningMessage).toBeNull() - }) + expect(savedMetricsSetting.cycleTimeWarningMessage).toBeNull(); + }); it('should set warningMessage null when importedCycleTimeSettings in the imported file matches is empty', () => { const mockUpdateMetricsStateArguments = { ...mockJiraResponse, isProjectCreated: false, - } + }; const savedMetricsSetting = saveMetricsSettingReducer( { ...initState, @@ -681,16 +687,16 @@ describe('saveMetricsSetting reducer', () => { }, }, updateMetricsState(mockUpdateMetricsStateArguments) - ) + ); - expect(savedMetricsSetting.cycleTimeWarningMessage).toBeNull() - }) + expect(savedMetricsSetting.cycleTimeWarningMessage).toBeNull(); + }); it('should set classification warningMessage null when the key value in the imported file matches the value in the response and the value matches the fixed column', () => { const mockUpdateMetricsStateArguments = { ...mockJiraResponse, isProjectCreated: false, - } + }; const savedMetricsSetting = saveMetricsSettingReducer( { ...initState, @@ -700,16 +706,16 @@ describe('saveMetricsSetting reducer', () => { }, }, updateMetricsState(mockUpdateMetricsStateArguments) - ) + ); - expect(savedMetricsSetting.classificationWarningMessage).toBeNull() - }) + expect(savedMetricsSetting.classificationWarningMessage).toBeNull(); + }); it('should set classification warningMessage have value when the key value in the imported file matches the value in the response and the value matches the fixed column', () => { const mockUpdateMetricsStateArguments = { ...mockJiraResponse, isProjectCreated: false, - } + }; const savedMetricsSetting = saveMetricsSettingReducer( { ...initState, @@ -719,10 +725,10 @@ describe('saveMetricsSetting reducer', () => { }, }, updateMetricsState(mockUpdateMetricsStateArguments) - ) + ); - expect(savedMetricsSetting.classificationWarningMessage).toEqual(CLASSIFICATION_WARNING_MESSAGE) - }) + expect(savedMetricsSetting.classificationWarningMessage).toEqual(CLASSIFICATION_WARNING_MESSAGE); + }); it('should set realDone warning message null when doneColumns in imported file matches the value in the response', () => { const mockUpdateMetricsStateArguments = { @@ -737,7 +743,7 @@ describe('saveMetricsSetting reducer', () => { }, ], isProjectCreated: false, - } + }; const savedMetricsSetting = saveMetricsSettingReducer( { ...initState, @@ -747,10 +753,10 @@ describe('saveMetricsSetting reducer', () => { }, }, updateMetricsState(mockUpdateMetricsStateArguments) - ) + ); - expect(savedMetricsSetting.realDoneWarningMessage).toBeNull() - }) + expect(savedMetricsSetting.realDoneWarningMessage).toBeNull(); + }); it('should set realDone warning message have value when doneColumns in imported file not matches the value in the response', () => { const mockUpdateMetricsStateArguments = { @@ -765,7 +771,7 @@ describe('saveMetricsSetting reducer', () => { }, ], isProjectCreated: false, - } + }; const savedMetricsSetting = saveMetricsSettingReducer( { ...initState, @@ -775,10 +781,10 @@ describe('saveMetricsSetting reducer', () => { }, }, updateMetricsState(mockUpdateMetricsStateArguments) - ) + ); - expect(savedMetricsSetting.realDoneWarningMessage).toEqual(MESSAGE.REAL_DONE_WARNING) - }) + expect(savedMetricsSetting.realDoneWarningMessage).toEqual(MESSAGE.REAL_DONE_WARNING); + }); it('should set realDone warning message null when doneColumns in imported file matches the value in cycleTimeSettings', () => { const mockUpdateMetricsStateArguments = { @@ -793,7 +799,7 @@ describe('saveMetricsSetting reducer', () => { }, ], isProjectCreated: false, - } + }; const savedMetricsSetting = saveMetricsSettingReducer( { ...initState, @@ -804,10 +810,10 @@ describe('saveMetricsSetting reducer', () => { }, }, updateMetricsState(mockUpdateMetricsStateArguments) - ) + ); - expect(savedMetricsSetting.realDoneWarningMessage).toBeNull() - }) + expect(savedMetricsSetting.realDoneWarningMessage).toBeNull(); + }); it('should set realDone warning message have value when doneColumns in imported file not matches the value in cycleTimeSettings', () => { const mockUpdateMetricsStateArguments = { @@ -822,7 +828,7 @@ describe('saveMetricsSetting reducer', () => { }, ], isProjectCreated: false, - } + }; const savedMetricsSetting = saveMetricsSettingReducer( { ...initState, @@ -833,10 +839,10 @@ describe('saveMetricsSetting reducer', () => { }, }, updateMetricsState(mockUpdateMetricsStateArguments) - ) + ); - expect(savedMetricsSetting.realDoneWarningMessage).toEqual(MESSAGE.REAL_DONE_WARNING) - }) + expect(savedMetricsSetting.realDoneWarningMessage).toEqual(MESSAGE.REAL_DONE_WARNING); + }); describe('select pipeline settings warning message', () => { const mockPipelineSettings = [ @@ -852,11 +858,11 @@ describe('saveMetricsSetting reducer', () => { pipelineName: 'mockPipelineName2', step: ' mockStep1', }, - ] + ]; const mockImportData = { deployment: mockPipelineSettings, leadTime: mockPipelineSettings, - } + }; const mockPipelineList = [ { id: 'mockId1', @@ -866,60 +872,48 @@ describe('saveMetricsSetting reducer', () => { repository: 'mockRepository1', steps: ['mock step 1', 'mock step 2'], }, - ] - const mockSteps = ['mockStep'] - const ORGANIZATION_WARNING_MESSAGE = 'This organization in import data might be removed' - const PIPELINE_NAME_WARNING_MESSAGE = 'This Pipeline in import data might be removed' - const STEP_WARNING_MESSAGE = 'Selected step of this pipeline in import data might be removed' + ]; + const mockSteps = ['mockStep']; + const ORGANIZATION_WARNING_MESSAGE = 'This organization in import data might be removed'; + const PIPELINE_NAME_WARNING_MESSAGE = 'This Pipeline in import data might be removed'; + const STEP_WARNING_MESSAGE = 'Selected step of this pipeline in import data might be removed'; - let store = setupStore() + let store = setupStore(); beforeEach(async () => { - store = setupStore() - await store.dispatch(updateMetricsImportedData(mockImportData)) - await store.dispatch(updatePipelineSettings({ pipelineList: mockPipelineList, isProjectCreated: false })) + store = setupStore(); + await store.dispatch(updateMetricsImportedData(mockImportData)); + await store.dispatch(updatePipelineSettings({ pipelineList: mockPipelineList, isProjectCreated: false })); await store.dispatch( updatePipelineStep({ steps: mockSteps, id: 0, type: PIPELINE_SETTING_TYPES.DEPLOYMENT_FREQUENCY_SETTINGS_TYPE, }) - ) + ); await store.dispatch( updatePipelineStep({ steps: mockSteps, id: 1, type: PIPELINE_SETTING_TYPES.DEPLOYMENT_FREQUENCY_SETTINGS_TYPE, }) - ) - }) + ); + }); afterEach(() => { - jest.clearAllMocks() - }) + jest.clearAllMocks(); + }); it('should return organization warning message given its id and type', () => { - expect( - selectOrganizationWarningMessage(store.getState(), 0, PIPELINE_SETTING_TYPES.DEPLOYMENT_FREQUENCY_SETTINGS_TYPE) - ).toEqual(ORGANIZATION_WARNING_MESSAGE) - expect( - selectOrganizationWarningMessage(store.getState(), 1, PIPELINE_SETTING_TYPES.DEPLOYMENT_FREQUENCY_SETTINGS_TYPE) - ).toBeNull() - }) + expect(selectOrganizationWarningMessage(store.getState(), 0)).toEqual(ORGANIZATION_WARNING_MESSAGE); + expect(selectOrganizationWarningMessage(store.getState(), 1)).toBeNull(); + }); it('should return pipelineName warning message given its id and type', () => { - expect( - selectPipelineNameWarningMessage(store.getState(), 0, PIPELINE_SETTING_TYPES.DEPLOYMENT_FREQUENCY_SETTINGS_TYPE) - ).toBeNull() - expect( - selectPipelineNameWarningMessage(store.getState(), 1, PIPELINE_SETTING_TYPES.DEPLOYMENT_FREQUENCY_SETTINGS_TYPE) - ).toEqual(PIPELINE_NAME_WARNING_MESSAGE) - }) + expect(selectPipelineNameWarningMessage(store.getState(), 0)).toBeNull(); + expect(selectPipelineNameWarningMessage(store.getState(), 1)).toEqual(PIPELINE_NAME_WARNING_MESSAGE); + }); it('should return step warning message given its id and type', () => { - expect( - selectStepWarningMessage(store.getState(), 0, PIPELINE_SETTING_TYPES.DEPLOYMENT_FREQUENCY_SETTINGS_TYPE) - ).toBeNull() - expect( - selectStepWarningMessage(store.getState(), 1, PIPELINE_SETTING_TYPES.DEPLOYMENT_FREQUENCY_SETTINGS_TYPE) - ).toEqual(STEP_WARNING_MESSAGE) - }) - }) -}) + expect(selectStepWarningMessage(store.getState(), 0)).toBeNull(); + expect(selectStepWarningMessage(store.getState(), 1)).toEqual(STEP_WARNING_MESSAGE); + }); + }); +}); diff --git a/frontend/__tests__/src/context/pipelineToolSlice.test.ts b/frontend/__tests__/src/context/pipelineToolSlice.test.ts index a3d9186869..d4ce64ea9d 100644 --- a/frontend/__tests__/src/context/pipelineToolSlice.test.ts +++ b/frontend/__tests__/src/context/pipelineToolSlice.test.ts @@ -8,11 +8,11 @@ import { updatePipelineToolVerifyResponse, updatePipelineToolVerifyResponseSteps, updatePipelineToolVerifyState, -} from '@src/context/config/configSlice' -import configReducer from '@src/context/config/configSlice' -import initialConfigState from '../initialConfigState' -import { MOCK_BUILD_KITE_VERIFY_RESPONSE, PIPELINE_TOOL_TYPES } from '../fixtures' -import { setupStore } from '../utils/setupStoreUtil' +} from '@src/context/config/configSlice'; +import configReducer from '@src/context/config/configSlice'; +import initialConfigState from '../initialConfigState'; +import { MOCK_BUILD_KITE_VERIFY_RESPONSE, PIPELINE_TOOL_TYPES } from '../fixtures'; +import { setupStore } from '../utils/setupStoreUtil'; describe('pipelineTool reducer', () => { const MOCK_PIPElINE_TOOL_VERIFY_RESPONSE = { @@ -26,7 +26,7 @@ describe('pipelineTool reducer', () => { steps: ['step1', 'step2'], }, ], - } + }; const MOCK_PIPElINE_TOOL_VERIFY_RESPONSE_SORT = { pipelineList: [ @@ -47,30 +47,30 @@ describe('pipelineTool reducer', () => { steps: ['step3', 'step4'], }, ], - } + }; const MOCK_DATE_RANGE = { startDate: '2023-04-04T00:00:00+08:00', endDate: '2023-04-18T00:00:00+08:00', - } + }; it('should set isPipelineToolVerified false when handle initial state', () => { - const result = configReducer(undefined, { type: 'unknown' }) + const result = configReducer(undefined, { type: 'unknown' }); - expect(result.pipelineTool.isVerified).toEqual(false) - }) + expect(result.pipelineTool.isVerified).toEqual(false); + }); it('should set isPipelineToolVerified true when handle updatePipelineToolVerifyState given isPipelineToolVerified is true', () => { - const result = configReducer(initialConfigState, updatePipelineToolVerifyState(true)) + const result = configReducer(initialConfigState, updatePipelineToolVerifyState(true)); - expect(result.pipelineTool.isVerified).toEqual(true) - }) + expect(result.pipelineTool.isVerified).toEqual(true); + }); it('should update pipelineTool fields when change pipelineTool fields input', () => { - const config = configReducer(initialConfigState, updatePipelineTool({ token: 'abcd' })) + const config = configReducer(initialConfigState, updatePipelineTool({ token: 'abcd' })); - expect(config.pipelineTool.config.token).toEqual('abcd') - }) + expect(config.pipelineTool.config.token).toEqual('abcd'); + }); it('should update pipelineList when get pipelineTool steps given pipelineList and right params', () => { const mockConfigStateHasPipelineList = { @@ -97,16 +97,16 @@ describe('pipelineTool reducer', () => { pipelineCrews: [], }, }, - } + }; const mockParams = { organization: MOCK_BUILD_KITE_VERIFY_RESPONSE.pipelineList[0].orgName, pipelineName: MOCK_BUILD_KITE_VERIFY_RESPONSE.pipelineList[0].name, steps: ['mock steps'], - } + }; const pipelineVerifiedResponse = configReducer( mockConfigStateHasPipelineList, updatePipelineToolVerifyResponseSteps(mockParams) - ) + ); expect(pipelineVerifiedResponse.pipelineTool.verifiedResponse.pipelineList).toEqual([ { @@ -117,8 +117,8 @@ describe('pipelineTool reducer', () => { repository: 'mock repository url', steps: ['mock steps'], }, - ]) - }) + ]); + }); it('should not update pipelineList when get pipelineTool steps given pipelineList and wrong params', () => { const mockConfigStateHasPipelineList = { @@ -145,16 +145,16 @@ describe('pipelineTool reducer', () => { pipelineCrews: [], }, }, - } + }; const mockParams = { organization: '', pipelineName: '', steps: ['mock steps'], - } + }; const pipelineVerifiedResponse = configReducer( mockConfigStateHasPipelineList, updatePipelineToolVerifyResponseSteps(mockParams) - ) + ); expect(pipelineVerifiedResponse.pipelineTool.verifiedResponse.pipelineList).toEqual([ { @@ -166,35 +166,35 @@ describe('pipelineTool reducer', () => { steps: [], branches: [], }, - ]) - }) + ]); + }); it('should return empty pipelineList when get pipelineTool steps given pipelineList is empty', () => { const mockParams = { organization: MOCK_BUILD_KITE_VERIFY_RESPONSE.pipelineList[0].orgName, pipelineName: MOCK_BUILD_KITE_VERIFY_RESPONSE.pipelineList[0].name, steps: ['mock steps'], - } + }; const pipelineVerifiedResponse = configReducer( initialConfigState, updatePipelineToolVerifyResponseSteps(mockParams) - ) + ); - expect(pipelineVerifiedResponse.pipelineTool.verifiedResponse.pipelineList).toEqual([]) - }) + expect(pipelineVerifiedResponse.pipelineTool.verifiedResponse.pipelineList).toEqual([]); + }); describe('pipelineToolVerifyResponse reducer', () => { it('should show empty array when handle initial state', () => { - const pipelineVerifiedResponse = configReducer(undefined, { type: 'unknown' }) + const pipelineVerifiedResponse = configReducer(undefined, { type: 'unknown' }); - expect(pipelineVerifiedResponse.pipelineTool.verifiedResponse.pipelineList).toEqual([]) - }) + expect(pipelineVerifiedResponse.pipelineTool.verifiedResponse.pipelineList).toEqual([]); + }); it('should store pipelineTool data when get network pipelineTool verify response', () => { const pipelineVerifiedResponse = configReducer( initialConfigState, updatePipelineToolVerifyResponse(MOCK_BUILD_KITE_VERIFY_RESPONSE) - ) + ); expect(pipelineVerifiedResponse.pipelineTool.verifiedResponse.pipelineList).toEqual([ { @@ -205,31 +205,31 @@ describe('pipelineTool reducer', () => { repository: 'mock repository url', steps: [], }, - ]) - }) - }) + ]); + }); + }); describe('selectPipelineNames', () => { it('should return PipelineNames when call selectPipelineNames function', async () => { - const store = setupStore() - await store.dispatch(updatePipelineToolVerifyResponse(MOCK_PIPElINE_TOOL_VERIFY_RESPONSE)) - expect(selectPipelineNames(store.getState(), 'mockOrgName')).toEqual(['mockName']) - }) + const store = setupStore(); + await store.dispatch(updatePipelineToolVerifyResponse(MOCK_PIPElINE_TOOL_VERIFY_RESPONSE)); + expect(selectPipelineNames(store.getState(), 'mockOrgName')).toEqual(['mockName']); + }); it('should sort PipelineNames when call selectPipelineNames function', async () => { - const store = setupStore() - await store.dispatch(updatePipelineToolVerifyResponse(MOCK_PIPElINE_TOOL_VERIFY_RESPONSE_SORT)) - expect(selectPipelineNames(store.getState(), 'mockOrgName')).toEqual(['mockName', 'MockName']) - }) - }) + const store = setupStore(); + await store.dispatch(updatePipelineToolVerifyResponse(MOCK_PIPElINE_TOOL_VERIFY_RESPONSE_SORT)); + expect(selectPipelineNames(store.getState(), 'mockOrgName')).toEqual(['mockName', 'MockName']); + }); + }); describe('selectStepsParams', () => { - let store = setupStore() + let store = setupStore(); beforeEach(async () => { - store = setupStore() - await store.dispatch(updatePipelineToolVerifyResponse(MOCK_PIPElINE_TOOL_VERIFY_RESPONSE)) - await store.dispatch(updateDateRange(MOCK_DATE_RANGE)) - }) + store = setupStore(); + await store.dispatch(updatePipelineToolVerifyResponse(MOCK_PIPElINE_TOOL_VERIFY_RESPONSE)); + await store.dispatch(updateDateRange(MOCK_DATE_RANGE)); + }); it('should return true StepsParams when call selectStepsParams function given right organization name and pipeline name', async () => { expect(selectStepsParams(store.getState(), 'mockOrgName', 'mockName')).toEqual({ @@ -244,8 +244,8 @@ describe('pipelineTool reducer', () => { }, pipelineType: 'BuildKite', token: '', - }) - }) + }); + }); it('should return StepsParams when call selectStepsParams function given empty organization name and empty pipeline name', async () => { expect(selectStepsParams(store.getState(), '', '')).toEqual({ @@ -260,23 +260,23 @@ describe('pipelineTool reducer', () => { }, pipelineType: 'BuildKite', token: '', - }) - }) - }) + }); + }); + }); describe('selectSteps', () => { it('should return steps when call selectSteps function', async () => { - const store = setupStore() - await store.dispatch(updatePipelineToolVerifyResponse(MOCK_PIPElINE_TOOL_VERIFY_RESPONSE)) - expect(selectSteps(store.getState(), 'mockOrgName', 'mockName')).toEqual([]) - }) - }) + const store = setupStore(); + await store.dispatch(updatePipelineToolVerifyResponse(MOCK_PIPElINE_TOOL_VERIFY_RESPONSE)); + expect(selectSteps(store.getState(), 'mockOrgName', 'mockName')).toEqual([]); + }); + }); describe('selectPipelineOrganizations', () => { it('should return organizations when call selectPipelineOrganizations function', async () => { - const store = setupStore() - await store.dispatch(updatePipelineToolVerifyResponse(MOCK_PIPElINE_TOOL_VERIFY_RESPONSE)) - expect(selectPipelineOrganizations(store.getState())).toEqual(['mockOrgName']) - }) - }) -}) + const store = setupStore(); + await store.dispatch(updatePipelineToolVerifyResponse(MOCK_PIPElINE_TOOL_VERIFY_RESPONSE)); + expect(selectPipelineOrganizations(store.getState())).toEqual(['mockOrgName']); + }); + }); +}); diff --git a/frontend/__tests__/src/context/sourceControlSlice.test.ts b/frontend/__tests__/src/context/sourceControlSlice.test.ts index a292f76a89..6f37a64be5 100644 --- a/frontend/__tests__/src/context/sourceControlSlice.test.ts +++ b/frontend/__tests__/src/context/sourceControlSlice.test.ts @@ -2,45 +2,45 @@ import sourceControlReducer, { updateSourceControl, updateSourceControlVerifiedResponse, updateSourceControlVerifyState, -} from '@src/context/config/configSlice' -import { MOCK_GITHUB_VERIFY_RESPONSE } from '../fixtures' -import initialConfigState from '../initialConfigState' +} from '@src/context/config/configSlice'; +import { MOCK_GITHUB_VERIFY_RESPONSE } from '../fixtures'; +import initialConfigState from '../initialConfigState'; describe('sourceControl reducer', () => { it('should set isSourceControlVerified false when handle initial state', () => { - const sourceControl = sourceControlReducer(undefined, { type: 'unknown' }) + const sourceControl = sourceControlReducer(undefined, { type: 'unknown' }); - expect(sourceControl.sourceControl.isVerified).toEqual(false) - }) + expect(sourceControl.sourceControl.isVerified).toEqual(false); + }); it('should return true when handle changeSourceControlVerifyState given isSourceControlVerified is true', () => { - const sourceControl = sourceControlReducer(initialConfigState, updateSourceControlVerifyState(true)) + const sourceControl = sourceControlReducer(initialConfigState, updateSourceControlVerifyState(true)); - expect(sourceControl.sourceControl.isVerified).toEqual(true) - }) + expect(sourceControl.sourceControl.isVerified).toEqual(true); + }); it('should update sourceControl fields when change sourceControl fields input', () => { - const sourceControl = sourceControlReducer(initialConfigState, updateSourceControl({ token: 'token' })) + const sourceControl = sourceControlReducer(initialConfigState, updateSourceControl({ token: 'token' })); - expect(sourceControl.sourceControl.config.token).toEqual('token') - }) + expect(sourceControl.sourceControl.config.token).toEqual('token'); + }); describe('sourceControlVerifyResponse reducer', () => { it('should show empty array when handle initial state', () => { - const sourceControlVerifyResponse = sourceControlReducer(undefined, { type: 'unknown' }) + const sourceControlVerifyResponse = sourceControlReducer(undefined, { type: 'unknown' }); - expect(sourceControlVerifyResponse.sourceControl.verifiedResponse.repoList).toEqual([]) - }) + expect(sourceControlVerifyResponse.sourceControl.verifiedResponse.repoList).toEqual([]); + }); it('should store sourceControl data when get network sourceControl verify response', () => { const sourceControlResponse = sourceControlReducer( initialConfigState, updateSourceControlVerifiedResponse(MOCK_GITHUB_VERIFY_RESPONSE) - ) + ); expect(sourceControlResponse.sourceControl.verifiedResponse.repoList).toEqual( MOCK_GITHUB_VERIFY_RESPONSE.githubRepos - ) - }) - }) -}) + ); + }); + }); +}); diff --git a/frontend/__tests__/src/context/stepperSlice.test.ts b/frontend/__tests__/src/context/stepperSlice.test.ts index 3bfd6e07a8..2815f4097a 100644 --- a/frontend/__tests__/src/context/stepperSlice.test.ts +++ b/frontend/__tests__/src/context/stepperSlice.test.ts @@ -1,19 +1,19 @@ -import stepperReducer, { backStep, nextStep, resetStep, updateTimeStamp } from '@src/context/stepper/StepperSlice' -import { ZERO } from '../fixtures' +import stepperReducer, { backStep, nextStep, resetStep, updateTimeStamp } from '@src/context/stepper/StepperSlice'; +import { ZERO } from '../fixtures'; describe('stepper reducer', () => { it('should get 0 when handle initial state', () => { - const stepper = stepperReducer(undefined, { type: 'unknown' }) + const stepper = stepperReducer(undefined, { type: 'unknown' }); - expect(stepper.stepNumber).toEqual(ZERO) - }) + expect(stepper.stepNumber).toEqual(ZERO); + }); it('should reset to 0 when handle reset', () => { - const stepper = stepperReducer(undefined, resetStep) + const stepper = stepperReducer(undefined, resetStep); - expect(stepper.stepNumber).toEqual(ZERO) - expect(stepper.timeStamp).toEqual(ZERO) - }) + expect(stepper.stepNumber).toEqual(ZERO); + expect(stepper.timeStamp).toEqual(ZERO); + }); it('should get 1 when handle next step given stepNumber is 0', () => { const stepper = stepperReducer( @@ -22,10 +22,10 @@ describe('stepper reducer', () => { timeStamp: 0, }, nextStep() - ) + ); - expect(stepper.stepNumber).toEqual(1) - }) + expect(stepper.stepNumber).toEqual(1); + }); it('should get 0 when handle back step given stepNumber is 0', () => { const stepper = stepperReducer( @@ -34,10 +34,10 @@ describe('stepper reducer', () => { timeStamp: 0, }, backStep() - ) + ); - expect(stepper.stepNumber).toEqual(ZERO) - }) + expect(stepper.stepNumber).toEqual(ZERO); + }); it('should get 1 when handle back step given stepNumber is 2', () => { const stepper = stepperReducer( @@ -46,21 +46,21 @@ describe('stepper reducer', () => { timeStamp: 0, }, backStep() - ) + ); - expect(stepper.stepNumber).toEqual(1) - }) + expect(stepper.stepNumber).toEqual(1); + }); it('should get current time when handle updateTimeStamp', () => { - const mockTime = new Date().getTime() + const mockTime = new Date().getTime(); const stepper = stepperReducer( { stepNumber: 2, timeStamp: 0, }, updateTimeStamp(mockTime) - ) + ); - expect(stepper.timeStamp).toEqual(mockTime) - }) -}) + expect(stepper.timeStamp).toEqual(mockTime); + }); +}); diff --git a/frontend/__tests__/src/emojis/emoji.test.tsx b/frontend/__tests__/src/emojis/emoji.test.tsx index d558aa7fe3..eaef32ee18 100644 --- a/frontend/__tests__/src/emojis/emoji.test.tsx +++ b/frontend/__tests__/src/emojis/emoji.test.tsx @@ -1,45 +1,45 @@ -import { getEmojiUrls, removeExtraEmojiName } from '@src/emojis/emoji' +import { getEmojiUrls, removeExtraEmojiName } from '@src/emojis/emoji'; jest.mock('@src/utils/util', () => ({ transformToCleanedBuildKiteEmoji: jest.fn().mockReturnValue([ { image: 'abc1.png', aliases: ['zap1'] }, { image: 'abc2.png', aliases: ['zap2'] }, ]), -})) +})); describe('#emojis', () => { describe('#getEmojiUrls', () => { - const EMOJI_URL_PREFIX = 'https://buildkiteassets.com/emojis/' + const EMOJI_URL_PREFIX = 'https://buildkiteassets.com/emojis/'; it('should get empty images when can not parse name from input', () => { - const mockPipelineStepName = 'one emojis' + const mockPipelineStepName = 'one emojis'; - expect(getEmojiUrls(mockPipelineStepName)).toEqual([]) - }) + expect(getEmojiUrls(mockPipelineStepName)).toEqual([]); + }); it('should get default image url when can not match any emoji', () => { - const mockPipelineStepName = ':zap: one emojis' + const mockPipelineStepName = ':zap: one emojis'; - expect(getEmojiUrls(mockPipelineStepName)).toEqual([`${EMOJI_URL_PREFIX}img-buildkite-64/buildkite.png`]) - }) + expect(getEmojiUrls(mockPipelineStepName)).toEqual([`${EMOJI_URL_PREFIX}img-buildkite-64/buildkite.png`]); + }); it('should get single emoji image', () => { - const mockPipelineStepName = ':zap1: one emojis' + const mockPipelineStepName = ':zap1: one emojis'; - expect(getEmojiUrls(mockPipelineStepName)).toEqual([`${EMOJI_URL_PREFIX}abc1.png`]) - }) + expect(getEmojiUrls(mockPipelineStepName)).toEqual([`${EMOJI_URL_PREFIX}abc1.png`]); + }); it('should get multi-emoji images', () => { - const input = ':zap1: :zap2:one emojis' + const input = ':zap1: :zap2:one emojis'; - expect(getEmojiUrls(input)).toEqual([`${EMOJI_URL_PREFIX}abc1.png`, `${EMOJI_URL_PREFIX}abc2.png`]) - }) - }) + expect(getEmojiUrls(input)).toEqual([`${EMOJI_URL_PREFIX}abc1.png`, `${EMOJI_URL_PREFIX}abc2.png`]); + }); + }); describe('#removeExtraEmojiName', () => { it('should remove extra emojis names', () => { - const input = ':zap: :www:one emojis' + const input = ':zap: :www:one emojis'; - expect(removeExtraEmojiName(input)).toEqual(' one emojis') - }) - }) -}) + expect(removeExtraEmojiName(input)).toEqual(' one emojis'); + }); + }); +}); diff --git a/frontend/__tests__/src/fileConfig/fileConfig.test.ts b/frontend/__tests__/src/fileConfig/fileConfig.test.ts index a268771859..e86799ac9b 100644 --- a/frontend/__tests__/src/fileConfig/fileConfig.test.ts +++ b/frontend/__tests__/src/fileConfig/fileConfig.test.ts @@ -1,10 +1,10 @@ -import { convertToNewFileConfig } from '@src/fileConfig/fileConfig' +import { convertToNewFileConfig } from '@src/fileConfig/fileConfig'; import { IMPORTED_NEW_CONFIG_FIXTURE, BASIC_IMPORTED_OLD_CONFIG_FIXTURE, REGULAR_CALENDAR, CHINA_CALENDAR, -} from '../fixtures' +} from '../fixtures'; describe('#fileConfig', () => { const BASIC_NEW_CONFIG = { @@ -43,35 +43,35 @@ describe('#fileConfig', () => { organization: 'Thoughtworks-Heartbeat', }, ], - } + }; it('should return original config when it is not old config', () => { - expect(convertToNewFileConfig(IMPORTED_NEW_CONFIG_FIXTURE)).toEqual(IMPORTED_NEW_CONFIG_FIXTURE) - }) + expect(convertToNewFileConfig(IMPORTED_NEW_CONFIG_FIXTURE)).toEqual(IMPORTED_NEW_CONFIG_FIXTURE); + }); it('should convert to new config when it is old config and considerHoliday is false', () => { const expected = { ...BASIC_NEW_CONFIG, calendarType: REGULAR_CALENDAR, - } + }; expect( convertToNewFileConfig({ ...BASIC_IMPORTED_OLD_CONFIG_FIXTURE, considerHoliday: false, }) - ).toEqual(expected) - }) + ).toEqual(expected); + }); it('should convert to new config when it is old config and considerHoliday is true', () => { const expected = { ...BASIC_NEW_CONFIG, calendarType: CHINA_CALENDAR, - } + }; expect( convertToNewFileConfig({ ...BASIC_IMPORTED_OLD_CONFIG_FIXTURE, considerHoliday: true, }) - ).toEqual(expected) - }) -}) + ).toEqual(expected); + }); +}); diff --git a/frontend/__tests__/src/fixtures.ts b/frontend/__tests__/src/fixtures.ts index 11e6e6a3e0..a094c5c754 100644 --- a/frontend/__tests__/src/fixtures.ts +++ b/frontend/__tests__/src/fixtures.ts @@ -1,39 +1,43 @@ -import { CSVReportRequestDTO, ReportRequestDTO } from '@src/clients/report/dto/request' -import { ReportResponseDTO } from '@src/clients/report/dto/response' +import { CSVReportRequestDTO, ReportRequestDTO } from '@src/clients/report/dto/request'; +import { ReportResponseDTO } from '@src/clients/report/dto/response'; -export const PROJECT_NAME = 'Heartbeat' +export const PROJECT_NAME = 'Heartbeat'; export const PROJECT_DESCRIPTION = - 'Heartbeat is a tool for tracking project delivery metrics that can help you get a better understanding of delivery performance. This product allows you easily get all aspects of source data faster and more accurate to analyze team delivery performance which enables delivery teams and team leaders focusing on driving continuous improvement and enhancing team productivity and efficiency.' + 'Heartbeat is a tool for tracking project delivery metrics that can help you get a better understanding of delivery performance. This product allows you easily get all aspects of source data faster and more accurate to analyze team delivery performance which enables delivery teams and team leaders focusing on driving continuous improvement and enhancing team productivity and efficiency.'; -export const ZERO = 0 +export const ZERO = 0; -export const REGULAR_CALENDAR = 'Regular Calendar(Weekend Considered)' +export const REGULAR_CALENDAR = 'Regular Calendar(Weekend Considered)'; -export const CHINA_CALENDAR = 'Calendar with Chinese Holiday' +export const CHINA_CALENDAR = 'Calendar with Chinese Holiday'; -export const NEXT = 'Next' +export const NEXT = 'Next'; -export const BACK = 'Previous' +export const PREVIOUS = 'Previous'; -export const SAVE = 'Save' +export const SAVE = 'Save'; -export const VERIFY = 'Verify' +export const SHOW_MORE = 'show more >'; -export const RESET = 'Reset' +export const BACK = 'Back'; -export const EXPORT_PIPELINE_DATA = 'Export pipeline data' +export const VERIFY = 'Verify'; -export const EXPORT_BOARD_DATA = 'Export board data' +export const RESET = 'Reset'; -export const EXPORT_METRIC_DATA = 'Export metric data' +export const EXPORT_PIPELINE_DATA = 'Export pipeline data'; -export const VERIFIED = 'Verified' +export const EXPORT_BOARD_DATA = 'Export board data'; -export const TOKEN_ERROR_MESSAGE = ['Token is invalid', 'Token is required'] +export const EXPORT_METRIC_DATA = 'Export metric data'; -export const PROJECT_NAME_LABEL = 'Project name' +export const VERIFIED = 'Verified'; -export const STEPPER = ['Config', 'Metrics', 'Report'] +export const TOKEN_ERROR_MESSAGE = ['Token is invalid', 'Token is required']; + +export const PROJECT_NAME_LABEL = 'Project name'; + +export const STEPPER = ['Config', 'Metrics', 'Report']; export const REQUIRED_DATA_LIST = [ 'All', @@ -44,36 +48,36 @@ export const REQUIRED_DATA_LIST = [ 'Deployment frequency', 'Change failure rate', 'Mean time to recovery', -] -export const ALL = 'All' -export const VELOCITY = 'Velocity' -export const CYCLE_TIME = 'Cycle time' -export const CLASSIFICATION = 'Classification' -export const LEAD_TIME_FOR_CHANGES = 'Lead time for changes' -export const DEPLOYMENT_FREQUENCY = 'Deployment frequency' -export const CHANGE_FAILURE_RATE = 'Change failure rate' -export const MEAN_TIME_TO_RECOVERY = 'Mean time to recovery' -export const REQUIRED_DATA = 'Required metrics' -export const TEST_PROJECT_NAME = 'test project Name' -export const ERROR_MESSAGE_COLOR = 'color: #d32f2f' -export const ERROR_DATE = '02/03/' -export const CREATE_NEW_PROJECT = 'Create a new project' -export const IMPORT_PROJECT_FROM_FILE = 'Import project from file' -export const EXPORT_EXPIRED_CSV_MESSAGE = 'The report has been expired, please generate it again' +]; +export const ALL = 'All'; +export const VELOCITY = 'Velocity'; +export const CYCLE_TIME = 'Cycle time'; +export const CLASSIFICATION = 'Classification'; +export const LEAD_TIME_FOR_CHANGES = 'Lead time for changes'; +export const DEPLOYMENT_FREQUENCY = 'Deployment frequency'; +export const CHANGE_FAILURE_RATE = 'Change failure rate'; +export const MEAN_TIME_TO_RECOVERY = 'Mean time to recovery'; +export const REQUIRED_DATA = 'Required metrics'; +export const TEST_PROJECT_NAME = 'test project Name'; +export const ERROR_MESSAGE_COLOR = 'color: #d32f2f'; +export const ERROR_DATE = '02/03/'; +export const CREATE_NEW_PROJECT = 'Create a new project'; +export const IMPORT_PROJECT_FROM_FILE = 'Import project from file'; +export const EXPORT_EXPIRED_CSV_MESSAGE = 'The report has been expired, please generate it again'; export const BOARD_TYPES = { CLASSIC_JIRA: 'Classic Jira', JIRA: 'Jira', -} +}; export const PIPELINE_TOOL_TYPES = { BUILD_KITE: 'BuildKite', GO_CD: 'GoCD', -} +}; export const SOURCE_CONTROL_TYPES = { GITHUB: 'GitHub', -} +}; export enum CONFIG_TITLE { BOARD = 'Board', @@ -81,22 +85,22 @@ export enum CONFIG_TITLE { SOURCE_CONTROL = 'Source Control', } -export const BOARD_FIELDS = ['Board', 'Board Id', 'Email', 'Project Key', 'Site', 'Token'] -export const PIPELINE_TOOL_FIELDS = ['Pipeline Tool', 'Token'] -export const SOURCE_CONTROL_FIELDS = ['Source Control', 'Token'] +export const BOARD_FIELDS = ['Board', 'Board Id', 'Email', 'Project Key', 'Site', 'Token']; +export const PIPELINE_TOOL_FIELDS = ['Pipeline Tool', 'Token']; +export const SOURCE_CONTROL_FIELDS = ['Source Control', 'Token']; -export const BASE_URL = 'api/v1' -export const MOCK_BOARD_URL_FOR_JIRA = `${BASE_URL}/boards/jira` -export const MOCK_BOARD_URL_FOR_CLASSIC_JIRA = `${BASE_URL}/boards/classic-jira` -export const MOCK_PIPELINE_URL = `${BASE_URL}/pipelines/buildkite` -export const MOCK_SOURCE_CONTROL_URL = `${BASE_URL}/source-control` -export const MOCK_REPORT_URL = `${BASE_URL}/reports` -export const MOCK_VERSION_URL = `${BASE_URL}/version` -export const MOCK_EXPORT_CSV_URL = `${BASE_URL}/reports/:dataType/:fileName` +export const BASE_URL = 'api/v1'; +export const MOCK_BOARD_URL_FOR_JIRA = `${BASE_URL}/boards/jira`; +export const MOCK_BOARD_URL_FOR_CLASSIC_JIRA = `${BASE_URL}/boards/classic-jira`; +export const MOCK_PIPELINE_URL = `${BASE_URL}/pipelines/buildkite`; +export const MOCK_SOURCE_CONTROL_URL = `${BASE_URL}/source-control`; +export const MOCK_REPORT_URL = `${BASE_URL}/reports`; +export const MOCK_VERSION_URL = `${BASE_URL}/version`; +export const MOCK_EXPORT_CSV_URL = `${BASE_URL}/reports/:dataType/:fileName`; export const VERSION_RESPONSE = { version: '1.11', -} +}; export enum VERIFY_ERROR_MESSAGE { BAD_REQUEST = 'Please reconfirm the input', @@ -108,7 +112,7 @@ export enum VERIFY_ERROR_MESSAGE { UNKNOWN = 'Unknown', } -export const VERIFY_FAILED = 'verify failed' +export const VERIFY_FAILED = 'verify failed'; export const MOCK_BOARD_VERIFY_REQUEST_PARAMS = { token: 'mockToken', @@ -118,7 +122,7 @@ export const MOCK_BOARD_VERIFY_REQUEST_PARAMS = { startTime: 1613664000000, endTime: 1614873600000, boardId: '1', -} +}; export const MOCK_CLASSIC_JIRA_BOARD_VERIFY_REQUEST_PARAMS = { token: 'mockToken', @@ -128,21 +132,21 @@ export const MOCK_CLASSIC_JIRA_BOARD_VERIFY_REQUEST_PARAMS = { startTime: 1613664000000, endTime: 1614873600000, boardId: '2', -} +}; export const MOCK_PIPELINE_VERIFY_REQUEST_PARAMS = { token: 'mockToken', type: PIPELINE_TOOL_TYPES.BUILD_KITE, startTime: 1613664000000, endTime: 1614873600000, -} +}; export const MOCK_SOURCE_CONTROL_VERIFY_REQUEST_PARAMS = { token: 'mockToken', type: SOURCE_CONTROL_TYPES.GITHUB, startTime: 1613664000000, endTime: 1614873600000, -} +}; export const MOCK_GENERATE_REPORT_REQUEST_PARAMS: ReportRequestDTO = { metrics: [], @@ -193,7 +197,7 @@ export const MOCK_GENERATE_REPORT_REQUEST_PARAMS: ReportRequestDTO = { targetFields: [{ key: 'parent', name: 'Parent', flag: false }], doneColumn: ['Done'], }, -} +}; export const IMPORTED_NEW_CONFIG_FIXTURE = { projectName: 'ConfigFileForImporting', @@ -231,14 +235,14 @@ export const IMPORTED_NEW_CONFIG_FIXTURE = { 'Ready For Dev': 'Analysis', }, ], -} +}; export const MOCK_EXPORT_CSV_REQUEST_PARAMS: CSVReportRequestDTO = { csvTimeStamp: 1613664000000, dataType: 'pipeline', startDate: IMPORTED_NEW_CONFIG_FIXTURE.dateRange.startDate, endDate: IMPORTED_NEW_CONFIG_FIXTURE.dateRange.endDate, -} +}; export const MOCK_IMPORT_FILE = { projectName: 'Mock Project Name', @@ -248,7 +252,7 @@ export const MOCK_IMPORT_FILE = { endDate: '2023-03-29T16:00:00.000Z', }, metrics: [], -} +}; export const MOCK_JIRA_VERIFY_RESPONSE = { jiraColumns: [ @@ -279,7 +283,7 @@ export const MOCK_JIRA_VERIFY_RESPONSE = { { key: 'customfield_10017', name: 'Issue color', flag: false }, { key: 'customfield_10027', name: 'Feature/Operation', flag: false }, ], -} +}; export const MOCK_BUILD_KITE_VERIFY_RESPONSE = { pipelineList: [ @@ -292,7 +296,7 @@ export const MOCK_BUILD_KITE_VERIFY_RESPONSE = { steps: [], }, ], -} +}; export const FILTER_CYCLE_TIME_SETTINGS = [ { name: 'TODO', value: 'TODO' }, @@ -300,34 +304,34 @@ export const FILTER_CYCLE_TIME_SETTINGS = [ { name: 'IN DEV', value: 'IN DEV' }, { name: 'DOING', value: 'IN DEV' }, { name: 'DONE', value: 'DONE' }, -] +]; export const MOCK_CYCLE_TIME_SETTING = [ { name: 'TODO', value: 'TODO' }, { name: 'IN DEV', value: 'IN DEV' }, { name: 'DONE', value: 'DONE' }, -] +]; export const MOCK_JIRA_WITH_STATUES_SETTING = [ { name: 'TODO', statuses: ['TODO', 'BACKLOG'] }, { name: 'IN DEV', statuses: ['IN DEV', 'DOING'] }, { name: 'DONE', statuses: ['DONE'] }, -] +]; export const MOCK_GITHUB_VERIFY_RESPONSE = { githubRepos: ['https://github.com/xxxx1/repo1', 'https://github.com/xxxx1/repo2'], -} +}; -export const CREWS_SETTING = 'Crew settings' -export const CYCLE_TIME_SETTINGS = 'Cycle time settings' -export const CLASSIFICATION_SETTING = 'Classification setting' -export const REAL_DONE = 'Real done setting' -export const DEPLOYMENT_FREQUENCY_SETTINGS = 'Pipeline settings' +export const CREWS_SETTING = 'Crew settings'; +export const CYCLE_TIME_SETTINGS = 'Cycle time settings'; +export const CLASSIFICATION_SETTING = 'Classification setting'; +export const REAL_DONE = 'Real done setting'; +export const DEPLOYMENT_FREQUENCY_SETTINGS = 'Pipeline settings'; export enum PIPELINE_SETTING_TYPES { DEPLOYMENT_FREQUENCY_SETTINGS_TYPE = 'DeploymentFrequencySettings', } -export const CONFIRM_DIALOG_DESCRIPTION = 'All the filled data will be cleared. Continue to Home page?' +export const CONFIRM_DIALOG_DESCRIPTION = 'All the filled data will be cleared. Continue to Home page?'; export const MOCK_GET_STEPS_PARAMS = { params: { @@ -341,17 +345,17 @@ export const MOCK_GET_STEPS_PARAMS = { organizationId: 'mockOrganizationId', pipelineType: 'BuildKite', token: 'mockToken', -} +}; -export const REMOVE_BUTTON = 'Remove' -export const ORGANIZATION = 'Organization' -export const PIPELINE_NAME = 'Pipeline Name' -export const STEP = 'Step' -export const BRANCH = 'Branches' +export const REMOVE_BUTTON = 'Remove'; +export const ORGANIZATION = 'Organization'; +export const PIPELINE_NAME = 'Pipeline Name'; +export const STEP = 'Step'; +export const BRANCH = 'Branches'; -export const PR_LEAD_TIME = 'PR Lead Time' -export const PIPELINE_LEAD_TIME = 'Pipeline Lead Time' -export const TOTAL_DELAY_TIME = 'Total Lead Time' +export const PR_LEAD_TIME = 'PR Lead Time'; +export const PIPELINE_LEAD_TIME = 'Pipeline Lead Time'; +export const TOTAL_DELAY_TIME = 'Total Lead Time'; export const MOCK_REPORT_RESPONSE: ReportResponseDTO = { velocity: { @@ -469,12 +473,12 @@ export const MOCK_REPORT_RESPONSE: ReportResponseDTO = { isPipelineMetricsReady: true, isSourceControlMetricsReady: true, isAllMetricsReady: true, -} +}; export const MOCK_RETRIEVE_REPORT_RESPONSE = { callbackUrl: 'reports/123', interval: 10, -} +}; export const EXPECTED_REPORT_VALUES = { velocityList: [ @@ -618,7 +622,7 @@ export const EXPECTED_REPORT_VALUES = { }, ], exportValidityTimeMin: 30, -} +}; export const EMPTY_REPORT_VALUES: ReportResponseDTO = { velocity: null, @@ -633,10 +637,10 @@ export const EMPTY_REPORT_VALUES: ReportResponseDTO = { isPipelineMetricsReady: false, isSourceControlMetricsReady: false, isAllMetricsReady: false, -} +}; export const CONFIG_PAGE_VERIFY_IMPORT_ERROR_MESSAGE = - 'Imported data is not perfectly matched. Please review carefully before going next!' + 'Imported data is not perfectly matched. Please review carefully before going next!'; export const BASIC_IMPORTED_OLD_CONFIG_FIXTURE = { projectName: 'ConfigFileForImporting', @@ -680,40 +684,40 @@ export const BASIC_IMPORTED_OLD_CONFIG_FIXTURE = { orgId: 'Thoughtworks-Heartbeat', }, ], -} +}; -export const ERROR_MESSAGE_TIME_DURATION = 4000 -export const CLASSIFICATION_WARNING_MESSAGE = `Some classifications in import data might be removed.` +export const ERROR_MESSAGE_TIME_DURATION = 4000; +export const CLASSIFICATION_WARNING_MESSAGE = `Some classifications in import data might be removed.`; export const HOME_VERIFY_IMPORT_WARNING_MESSAGE = - 'The content of the imported JSON file is empty. Please confirm carefully' + 'The content of the imported JSON file is empty. Please confirm carefully'; -export const INTERNAL_SERVER_ERROR_MESSAGE = 'Internal Server Error' +export const INTERNAL_SERVER_ERROR_MESSAGE = 'Internal Server Error'; -export const BASE_PAGE_ROUTE = '/' +export const BASE_PAGE_ROUTE = '/'; -export const ERROR_PAGE_ROUTE = '/error-page' +export const ERROR_PAGE_ROUTE = '/error-page'; -export const METRICS_PAGE_ROUTE = '/metrics' +export const METRICS_PAGE_ROUTE = '/metrics'; export const ERROR_PAGE_MESSAGE = - 'Something on internet is not quite right. Perhaps head back to our homepage and try again.' + 'Something on internet is not quite right. Perhaps head back to our homepage and try again.'; -export const RETRY_BUTTON = 'Go to homepage' +export const RETRY_BUTTON = 'Go to homepage'; export const NO_CARD_ERROR_MESSAGE = - 'Sorry there is no card within selected date range, please change your collection date!' + 'Sorry there is no card within selected date range, please change your collection date!'; -export const LIST_OPEN = 'Open' +export const LIST_OPEN = 'Open'; -export const NO_RESULT_DASH = '----' +export const NO_RESULT_DASH = '----'; -export const MOCK_AUTOCOMPLETE_LIST = ['Option 1', 'Option 2', 'Option 3'] +export const MOCK_AUTOCOMPLETE_LIST = ['Option 1', 'Option 2', 'Option 3']; -export const AUTOCOMPLETE_SELECT_ACTION = 'selectOption' +export const AUTOCOMPLETE_SELECT_ACTION = 'selectOption'; -export const TIME_DISPLAY_TITTLE_START = 'START' -export const TIME_DISPLAY_TITTLE_END = 'END' -export const CYCLE_TIME_SETTINGS_SECTION = 'Cycle time settings section' -export const REAL_DONE_SETTING_SECTION = 'Real done setting section' -export const SELECT_CONSIDER_AS_DONE_MESSAGE = 'Must select which you want to consider as Done' +export const TIME_DISPLAY_TITTLE_START = 'START'; +export const TIME_DISPLAY_TITTLE_END = 'END'; +export const CYCLE_TIME_SETTINGS_SECTION = 'Cycle time settings section'; +export const REAL_DONE_SETTING_SECTION = 'Real done setting section'; +export const SELECT_CONSIDER_AS_DONE_MESSAGE = 'Must select which you want to consider as Done'; diff --git a/frontend/__tests__/src/hooks/reportMapper/changeFailureRate.test.tsx b/frontend/__tests__/src/hooks/reportMapper/changeFailureRate.test.tsx index b7a7b2e1a4..91511818d6 100644 --- a/frontend/__tests__/src/hooks/reportMapper/changeFailureRate.test.tsx +++ b/frontend/__tests__/src/hooks/reportMapper/changeFailureRate.test.tsx @@ -1,4 +1,4 @@ -import { changeFailureRateMapper } from '@src/hooks/reportMapper/changeFailureRate' +import { changeFailureRateMapper } from '@src/hooks/reportMapper/changeFailureRate'; describe('change failure rate data mapper', () => { const mockChangeFailureRateRes = { @@ -17,7 +17,7 @@ describe('change failure rate data mapper', () => { failureRate: 0.0, }, ], - } + }; it('maps response change failure rate values to ui display value', () => { const expectedChangeFailureRateValues = [ { @@ -40,9 +40,9 @@ describe('change failure rate data mapper', () => { }, ], }, - ] - const mappedChangeFailureRate = changeFailureRateMapper(mockChangeFailureRateRes) + ]; + const mappedChangeFailureRate = changeFailureRateMapper(mockChangeFailureRateRes); - expect(mappedChangeFailureRate).toEqual(expectedChangeFailureRateValues) - }) -}) + expect(mappedChangeFailureRate).toEqual(expectedChangeFailureRateValues); + }); +}); diff --git a/frontend/__tests__/src/hooks/reportMapper/classfication.test.tsx b/frontend/__tests__/src/hooks/reportMapper/classfication.test.tsx index 4f374f975a..b4227a4b0d 100644 --- a/frontend/__tests__/src/hooks/reportMapper/classfication.test.tsx +++ b/frontend/__tests__/src/hooks/reportMapper/classfication.test.tsx @@ -1,4 +1,4 @@ -import { classificationMapper } from '@src/hooks/reportMapper/classification' +import { classificationMapper } from '@src/hooks/reportMapper/classification'; describe('classification data mapper', () => { const mockClassificationRes = [ @@ -19,7 +19,7 @@ describe('classification data mapper', () => { }, ], }, - ] + ]; it('maps response Classification values to ui display value', () => { const expectedClassificationValues = [ { @@ -31,9 +31,9 @@ describe('classification data mapper', () => { { name: 'Feature Work - Unplanned', value: '7.14%' }, ], }, - ] - const mappedClassifications = classificationMapper(mockClassificationRes) + ]; + const mappedClassifications = classificationMapper(mockClassificationRes); - expect(mappedClassifications).toEqual(expectedClassificationValues) - }) -}) + expect(mappedClassifications).toEqual(expectedClassificationValues); + }); +}); diff --git a/frontend/__tests__/src/hooks/reportMapper/cycleTime.test.tsx b/frontend/__tests__/src/hooks/reportMapper/cycleTime.test.tsx index df54c82e4e..ef86c576d1 100644 --- a/frontend/__tests__/src/hooks/reportMapper/cycleTime.test.tsx +++ b/frontend/__tests__/src/hooks/reportMapper/cycleTime.test.tsx @@ -1,4 +1,4 @@ -import { cycleTimeMapper } from '@src/hooks/reportMapper/cycleTime' +import { cycleTimeMapper } from '@src/hooks/reportMapper/cycleTime'; describe('cycleTime data mapper', () => { const mockCycleTimeRes = { @@ -19,7 +19,7 @@ describe('cycleTime data mapper', () => { totalTime: 3.21, }, ], - } + }; it('maps response cycleTime values to ui display value', () => { const expectedCycleValues = [ { @@ -48,9 +48,9 @@ describe('cycleTime data mapper', () => { { value: '0.23', unit: '(days/card)' }, ], }, - ] - const mappedCycleValues = cycleTimeMapper(mockCycleTimeRes) + ]; + const mappedCycleValues = cycleTimeMapper(mockCycleTimeRes); - expect(mappedCycleValues).toEqual(expectedCycleValues) - }) -}) + expect(mappedCycleValues).toEqual(expectedCycleValues); + }); +}); diff --git a/frontend/__tests__/src/hooks/reportMapper/deploymentFrequency.test.tsx b/frontend/__tests__/src/hooks/reportMapper/deploymentFrequency.test.tsx index 71ba5f3e7f..3e6827c646 100644 --- a/frontend/__tests__/src/hooks/reportMapper/deploymentFrequency.test.tsx +++ b/frontend/__tests__/src/hooks/reportMapper/deploymentFrequency.test.tsx @@ -1,4 +1,4 @@ -import { deploymentFrequencyMapper } from '@src/hooks/reportMapper/deploymentFrequency' +import { deploymentFrequencyMapper } from '@src/hooks/reportMapper/deploymentFrequency'; describe('deployment frequency data mapper', () => { const mockDeploymentFrequencyRes = { @@ -27,7 +27,7 @@ describe('deployment frequency data mapper', () => { ], }, ], - } + }; it('maps response deployment frequency values to ui display value', () => { const expectedDeploymentFrequencyValues = [ { @@ -50,9 +50,9 @@ describe('deployment frequency data mapper', () => { }, ], }, - ] - const mappedDeploymentFrequency = deploymentFrequencyMapper(mockDeploymentFrequencyRes) + ]; + const mappedDeploymentFrequency = deploymentFrequencyMapper(mockDeploymentFrequencyRes); - expect(mappedDeploymentFrequency).toEqual(expectedDeploymentFrequencyValues) - }) -}) + expect(mappedDeploymentFrequency).toEqual(expectedDeploymentFrequencyValues); + }); +}); diff --git a/frontend/__tests__/src/hooks/reportMapper/exportValidityTime.test.tsx b/frontend/__tests__/src/hooks/reportMapper/exportValidityTime.test.tsx index 6217b79847..36a435f49f 100644 --- a/frontend/__tests__/src/hooks/reportMapper/exportValidityTime.test.tsx +++ b/frontend/__tests__/src/hooks/reportMapper/exportValidityTime.test.tsx @@ -1,15 +1,15 @@ -import { exportValidityTimeMapper } from '@src/hooks/reportMapper/exportValidityTime' +import { exportValidityTimeMapper } from '@src/hooks/reportMapper/exportValidityTime'; describe('export validity time mapper', () => { it('should return 30 when call exportValidityTimeMapper given the param to 1800000', () => { - const result = exportValidityTimeMapper(1800000) + const result = exportValidityTimeMapper(1800000); - expect(result).toEqual(30) - }) + expect(result).toEqual(30); + }); it('should return null when call exportValidityTimeMapper given the param to null', () => { - const result = exportValidityTimeMapper(null) + const result = exportValidityTimeMapper(null); - expect(result).toEqual(null) - }) -}) + expect(result).toEqual(null); + }); +}); diff --git a/frontend/__tests__/src/hooks/reportMapper/leadTimeForChanges.test.tsx b/frontend/__tests__/src/hooks/reportMapper/leadTimeForChanges.test.tsx index 611a2bcaf7..27fc3d8f15 100644 --- a/frontend/__tests__/src/hooks/reportMapper/leadTimeForChanges.test.tsx +++ b/frontend/__tests__/src/hooks/reportMapper/leadTimeForChanges.test.tsx @@ -1,5 +1,5 @@ -import { PIPELINE_LEAD_TIME, PR_LEAD_TIME, TOTAL_DELAY_TIME } from '../../fixtures' -import { leadTimeForChangesMapper } from '@src/hooks/reportMapper/leadTimeForChanges' +import { PIPELINE_LEAD_TIME, PR_LEAD_TIME, TOTAL_DELAY_TIME } from '../../fixtures'; +import { leadTimeForChangesMapper } from '@src/hooks/reportMapper/leadTimeForChanges'; describe('lead time for changes data mapper', () => { const mockLeadTimeForChangesRes = { @@ -18,7 +18,7 @@ describe('lead time for changes data mapper', () => { pipelineLeadTime: 4167.97, totalDelayTime: 18313.579999999998, }, - } + }; it('maps response lead time for changes values to ui display value', () => { const expectedLeadTimeForChangesValues = [ { @@ -57,11 +57,11 @@ describe('lead time for changes data mapper', () => { }, ], }, - ] - const mappedLeadTimeForChanges = leadTimeForChangesMapper(mockLeadTimeForChangesRes) + ]; + const mappedLeadTimeForChanges = leadTimeForChangesMapper(mockLeadTimeForChangesRes); - expect(mappedLeadTimeForChanges).toEqual(expectedLeadTimeForChangesValues) - }) + expect(mappedLeadTimeForChanges).toEqual(expectedLeadTimeForChangesValues); + }); it('should map time to 0 minute when it is 0', () => { const mockLeadTimeForChangesResMock = { @@ -80,7 +80,7 @@ describe('lead time for changes data mapper', () => { pipelineLeadTime: 0, totalDelayTime: 0, }, - } + }; const expectedLeadTimeForChangesValues = [ { @@ -101,9 +101,9 @@ describe('lead time for changes data mapper', () => { { name: TOTAL_DELAY_TIME, value: '0.00' }, ], }, - ] - const mappedLeadTimeForChanges = leadTimeForChangesMapper(mockLeadTimeForChangesResMock) + ]; + const mappedLeadTimeForChanges = leadTimeForChangesMapper(mockLeadTimeForChangesResMock); - expect(mappedLeadTimeForChanges).toEqual(expectedLeadTimeForChangesValues) - }) -}) + expect(mappedLeadTimeForChanges).toEqual(expectedLeadTimeForChangesValues); + }); +}); diff --git a/frontend/__tests__/src/hooks/reportMapper/meanTimeToRecovery.test.tsx b/frontend/__tests__/src/hooks/reportMapper/meanTimeToRecovery.test.tsx index 2325cd7cfe..98b6ff3029 100644 --- a/frontend/__tests__/src/hooks/reportMapper/meanTimeToRecovery.test.tsx +++ b/frontend/__tests__/src/hooks/reportMapper/meanTimeToRecovery.test.tsx @@ -1,4 +1,4 @@ -import { meanTimeToRecoveryMapper } from '@src/hooks/reportMapper/meanTimeToRecovery' +import { meanTimeToRecoveryMapper } from '@src/hooks/reportMapper/meanTimeToRecovery'; describe('mean time to recovery data mapper', () => { const mockMeanTimeToRecovery = { @@ -13,7 +13,7 @@ describe('mean time to recovery data mapper', () => { timeToRecovery: 162120031.8, }, ], - } + }; it('maps response change failure rate values to ui display value', () => { const expectedMeanTimeToRecovery = [ { @@ -36,11 +36,11 @@ describe('mean time to recovery data mapper', () => { }, ], }, - ] - const mappedMeanTimeToRecovery = meanTimeToRecoveryMapper(mockMeanTimeToRecovery) + ]; + const mappedMeanTimeToRecovery = meanTimeToRecoveryMapper(mockMeanTimeToRecovery); - expect(mappedMeanTimeToRecovery).toEqual(expectedMeanTimeToRecovery) - }) + expect(mappedMeanTimeToRecovery).toEqual(expectedMeanTimeToRecovery); + }); it('should format time when timeToRecovery is greater than 0 but less than 1', () => { const mockMeanTimeToRecovery = { @@ -55,7 +55,7 @@ describe('mean time to recovery data mapper', () => { timeToRecovery: 0.32, }, ], - } + }; const expectedMeanTimeToRecovery = [ { id: 0, @@ -77,11 +77,11 @@ describe('mean time to recovery data mapper', () => { }, ], }, - ] - const mappedMeanTimeToRecovery = meanTimeToRecoveryMapper(mockMeanTimeToRecovery) + ]; + const mappedMeanTimeToRecovery = meanTimeToRecoveryMapper(mockMeanTimeToRecovery); - expect(mappedMeanTimeToRecovery).toEqual(expectedMeanTimeToRecovery) - }) + expect(mappedMeanTimeToRecovery).toEqual(expectedMeanTimeToRecovery); + }); it('should map time to 0 minute when it is 0', () => { const mockMeanTimeToRecovery = { @@ -96,7 +96,7 @@ describe('mean time to recovery data mapper', () => { timeToRecovery: 0, }, ], - } + }; const expectedMeanTimeToRecovery = [ { id: 0, @@ -118,9 +118,9 @@ describe('mean time to recovery data mapper', () => { }, ], }, - ] - const mappedMeanTimeToRecovery = meanTimeToRecoveryMapper(mockMeanTimeToRecovery) + ]; + const mappedMeanTimeToRecovery = meanTimeToRecoveryMapper(mockMeanTimeToRecovery); - expect(mappedMeanTimeToRecovery).toEqual(expectedMeanTimeToRecovery) - }) -}) + expect(mappedMeanTimeToRecovery).toEqual(expectedMeanTimeToRecovery); + }); +}); diff --git a/frontend/__tests__/src/hooks/reportMapper/report.test.tsx b/frontend/__tests__/src/hooks/reportMapper/report.test.tsx index 8cf8339baf..a72ffdd2c9 100644 --- a/frontend/__tests__/src/hooks/reportMapper/report.test.tsx +++ b/frontend/__tests__/src/hooks/reportMapper/report.test.tsx @@ -1,10 +1,10 @@ -import { reportMapper } from '@src/hooks/reportMapper/report' -import { EXPECTED_REPORT_VALUES, MOCK_REPORT_RESPONSE } from '../../fixtures' +import { reportMapper } from '@src/hooks/reportMapper/report'; +import { EXPECTED_REPORT_VALUES, MOCK_REPORT_RESPONSE } from '../../fixtures'; describe('report response data mapper', () => { it('maps response velocity values to ui display value', () => { - const mappedReportResponseValues = reportMapper(MOCK_REPORT_RESPONSE) + const mappedReportResponseValues = reportMapper(MOCK_REPORT_RESPONSE); - expect(mappedReportResponseValues).toEqual(EXPECTED_REPORT_VALUES) - }) -}) + expect(mappedReportResponseValues).toEqual(EXPECTED_REPORT_VALUES); + }); +}); diff --git a/frontend/__tests__/src/hooks/reportMapper/velocity.test.tsx b/frontend/__tests__/src/hooks/reportMapper/velocity.test.tsx index f6d75ec869..0c18ed1645 100644 --- a/frontend/__tests__/src/hooks/reportMapper/velocity.test.tsx +++ b/frontend/__tests__/src/hooks/reportMapper/velocity.test.tsx @@ -1,18 +1,18 @@ -import { velocityMapper } from '@src/hooks/reportMapper/velocity' +import { velocityMapper } from '@src/hooks/reportMapper/velocity'; describe('velocity data mapper', () => { it('maps response velocity values to ui display value', () => { const mockVelocityRes = { velocityForSP: 20, velocityForCards: 15, - } + }; const expectedVelocityValues = [ { id: 0, name: 'Velocity(Story Point)', valueList: [{ value: 20 }] }, { id: 1, name: 'Throughput(Cards Count)', valueList: [{ value: 15 }] }, - ] + ]; - const mappedVelocityValues = velocityMapper(mockVelocityRes) + const mappedVelocityValues = velocityMapper(mockVelocityRes); - expect(mappedVelocityValues).toEqual(expectedVelocityValues) - }) -}) + expect(mappedVelocityValues).toEqual(expectedVelocityValues); + }); +}); diff --git a/frontend/__tests__/src/hooks/useExportCsvEffect.test.tsx b/frontend/__tests__/src/hooks/useExportCsvEffect.test.tsx index d0dfaca730..dd09ea54e5 100644 --- a/frontend/__tests__/src/hooks/useExportCsvEffect.test.tsx +++ b/frontend/__tests__/src/hooks/useExportCsvEffect.test.tsx @@ -1,54 +1,54 @@ -import { act, renderHook } from '@testing-library/react' -import { csvClient } from '@src/clients/report/CSVClient' -import { useExportCsvEffect } from '@src/hooks/useExportCsvEffect' -import { ERROR_MESSAGE_TIME_DURATION, MOCK_EXPORT_CSV_REQUEST_PARAMS } from '../fixtures' -import { InternalServerException } from '@src/exceptions/InternalServerException' -import { NotFoundException } from '@src/exceptions/NotFoundException' -import { HttpStatusCode } from 'axios' +import { act, renderHook } from '@testing-library/react'; +import { csvClient } from '@src/clients/report/CSVClient'; +import { useExportCsvEffect } from '@src/hooks/useExportCsvEffect'; +import { ERROR_MESSAGE_TIME_DURATION, MOCK_EXPORT_CSV_REQUEST_PARAMS } from '../fixtures'; +import { InternalServerException } from '@src/exceptions/InternalServerException'; +import { NotFoundException } from '@src/exceptions/NotFoundException'; +import { HttpStatusCode } from 'axios'; describe('use export csv effect', () => { afterEach(() => { - jest.resetAllMocks() - }) + jest.resetAllMocks(); + }); it('should set error message empty when export csv throw error and last for 4 seconds', async () => { - jest.useFakeTimers() + jest.useFakeTimers(); csvClient.exportCSVData = jest.fn().mockImplementation(() => { - throw new Error('error') - }) - const { result } = renderHook(() => useExportCsvEffect()) + throw new Error('error'); + }); + const { result } = renderHook(() => useExportCsvEffect()); act(() => { - result.current.fetchExportData(MOCK_EXPORT_CSV_REQUEST_PARAMS) - jest.advanceTimersByTime(ERROR_MESSAGE_TIME_DURATION) - }) + result.current.fetchExportData(MOCK_EXPORT_CSV_REQUEST_PARAMS); + jest.advanceTimersByTime(ERROR_MESSAGE_TIME_DURATION); + }); - expect(result.current.errorMessage).toEqual('') - }) + expect(result.current.errorMessage).toEqual(''); + }); it('should set error message when export csv response status 500', async () => { csvClient.exportCSVData = jest.fn().mockImplementation(() => { - throw new InternalServerException('error message', HttpStatusCode.InternalServerError) - }) - const { result } = renderHook(() => useExportCsvEffect()) + throw new InternalServerException('error message', HttpStatusCode.InternalServerError); + }); + const { result } = renderHook(() => useExportCsvEffect()); act(() => { - result.current.fetchExportData(MOCK_EXPORT_CSV_REQUEST_PARAMS) - }) + result.current.fetchExportData(MOCK_EXPORT_CSV_REQUEST_PARAMS); + }); - expect(result.current.errorMessage).toEqual('failed to export csv: error message') - }) + expect(result.current.errorMessage).toEqual('failed to export csv: error message'); + }); it('should set error message when export csv response status 404', async () => { csvClient.exportCSVData = jest.fn().mockImplementation(() => { - throw new NotFoundException('error message', HttpStatusCode.NotFound) - }) - const { result } = renderHook(() => useExportCsvEffect()) + throw new NotFoundException('error message', HttpStatusCode.NotFound); + }); + const { result } = renderHook(() => useExportCsvEffect()); act(() => { - result.current.fetchExportData(MOCK_EXPORT_CSV_REQUEST_PARAMS) - }) + result.current.fetchExportData(MOCK_EXPORT_CSV_REQUEST_PARAMS); + }); - expect(result.current.isExpired).toEqual(true) - }) -}) + expect(result.current.isExpired).toEqual(true); + }); +}); diff --git a/frontend/__tests__/src/hooks/useGenerateReportEffect.test.tsx b/frontend/__tests__/src/hooks/useGenerateReportEffect.test.tsx index 26adf0e76e..b94fb8c24b 100644 --- a/frontend/__tests__/src/hooks/useGenerateReportEffect.test.tsx +++ b/frontend/__tests__/src/hooks/useGenerateReportEffect.test.tsx @@ -1,360 +1,360 @@ -import { act, renderHook, waitFor } from '@testing-library/react' -import { useGenerateReportEffect } from '@src/hooks/useGenerateReportEffect' +import { act, renderHook, waitFor } from '@testing-library/react'; +import { useGenerateReportEffect } from '@src/hooks/useGenerateReportEffect'; import { ERROR_MESSAGE_TIME_DURATION, INTERNAL_SERVER_ERROR_MESSAGE, MOCK_GENERATE_REPORT_REQUEST_PARAMS, MOCK_REPORT_RESPONSE, MOCK_RETRIEVE_REPORT_RESPONSE, -} from '../fixtures' -import { reportClient } from '@src/clients/report/ReportClient' -import { NotFoundException } from '@src/exceptions/NotFoundException' -import { UnknownException } from '@src/exceptions/UnkonwException' -import { InternalServerException } from '@src/exceptions/InternalServerException' -import { HttpStatusCode } from 'axios' -import clearAllMocks = jest.clearAllMocks -import resetAllMocks = jest.resetAllMocks +} from '../fixtures'; +import { reportClient } from '@src/clients/report/ReportClient'; +import { NotFoundException } from '@src/exceptions/NotFoundException'; +import { UnknownException } from '@src/exceptions/UnkonwException'; +import { InternalServerException } from '@src/exceptions/InternalServerException'; +import { HttpStatusCode } from 'axios'; +import clearAllMocks = jest.clearAllMocks; +import resetAllMocks = jest.resetAllMocks; jest.mock('@src/hooks/reportMapper/report', () => ({ pipelineReportMapper: jest.fn(), sourceControlReportMapper: jest.fn(), -})) +})); describe('use generate report effect', () => { afterAll(() => { - clearAllMocks() - }) + clearAllMocks(); + }); beforeEach(() => { - jest.useFakeTimers() - }) + jest.useFakeTimers(); + }); afterEach(() => { - resetAllMocks() - jest.useRealTimers() - }) + resetAllMocks(); + jest.useRealTimers(); + }); it('should set error message when generate report throw error', async () => { reportClient.retrieveReportByUrl = jest.fn().mockImplementation(async () => { - throw new Error('error') - }) + throw new Error('error'); + }); - const { result } = renderHook(() => useGenerateReportEffect()) + const { result } = renderHook(() => useGenerateReportEffect()); await waitFor(() => { - result.current.startToRequestBoardData(MOCK_GENERATE_REPORT_REQUEST_PARAMS) - expect(result.current.errorMessage).toEqual('generate report: error') - }) + result.current.startToRequestBoardData(MOCK_GENERATE_REPORT_REQUEST_PARAMS); + expect(result.current.errorMessage).toEqual('generate report: error'); + }); act(() => { - jest.advanceTimersByTime(ERROR_MESSAGE_TIME_DURATION) - }) + jest.advanceTimersByTime(ERROR_MESSAGE_TIME_DURATION); + }); await waitFor(() => { - expect(result.current.errorMessage).toEqual('') - }) - }) + expect(result.current.errorMessage).toEqual(''); + }); + }); it('should set error message when generate report response status 404', async () => { reportClient.retrieveReportByUrl = jest.fn().mockImplementation(async () => { - throw new NotFoundException('error message', HttpStatusCode.NotFound) - }) + throw new NotFoundException('error message', HttpStatusCode.NotFound); + }); - const { result } = renderHook(() => useGenerateReportEffect()) + const { result } = renderHook(() => useGenerateReportEffect()); await waitFor(() => { - result.current.startToRequestBoardData(MOCK_GENERATE_REPORT_REQUEST_PARAMS) - expect(result.current.errorMessage).toEqual('generate report: error message') - }) + result.current.startToRequestBoardData(MOCK_GENERATE_REPORT_REQUEST_PARAMS); + expect(result.current.errorMessage).toEqual('generate report: error message'); + }); act(() => { - jest.advanceTimersByTime(ERROR_MESSAGE_TIME_DURATION) - }) + jest.advanceTimersByTime(ERROR_MESSAGE_TIME_DURATION); + }); await waitFor(() => { - expect(result.current.errorMessage).toEqual('') - }) - }) + expect(result.current.errorMessage).toEqual(''); + }); + }); it('should set error message when generate report response status 500', async () => { reportClient.retrieveReportByUrl = jest.fn().mockImplementation(async () => { - throw new InternalServerException(INTERNAL_SERVER_ERROR_MESSAGE, HttpStatusCode.InternalServerError) - }) + throw new InternalServerException(INTERNAL_SERVER_ERROR_MESSAGE, HttpStatusCode.InternalServerError); + }); - const { result } = renderHook(() => useGenerateReportEffect()) + const { result } = renderHook(() => useGenerateReportEffect()); await waitFor(() => { - result.current.startToRequestBoardData(MOCK_GENERATE_REPORT_REQUEST_PARAMS) - expect(result.current.isServerError).toEqual(true) - }) - }) + result.current.startToRequestBoardData(MOCK_GENERATE_REPORT_REQUEST_PARAMS); + expect(result.current.isServerError).toEqual(true); + }); + }); it('should set isServerError is true when throw unknownException', async () => { reportClient.retrieveReportByUrl = jest.fn().mockImplementation(async () => { - throw new UnknownException() - }) + throw new UnknownException(); + }); - const { result } = renderHook(() => useGenerateReportEffect()) + const { result } = renderHook(() => useGenerateReportEffect()); await waitFor(() => { - result.current.startToRequestBoardData(MOCK_GENERATE_REPORT_REQUEST_PARAMS) - expect(result.current.isServerError).toEqual(true) - }) - }) + result.current.startToRequestBoardData(MOCK_GENERATE_REPORT_REQUEST_PARAMS); + expect(result.current.isServerError).toEqual(true); + }); + }); it('should return error message when calling startToRequestBoardData given pollingReport response return 5xx ', async () => { reportClient.pollingReport = jest.fn().mockImplementation(async () => { - throw new InternalServerException('error', HttpStatusCode.InternalServerError) - }) + throw new InternalServerException('error', HttpStatusCode.InternalServerError); + }); reportClient.retrieveReportByUrl = jest .fn() - .mockImplementation(async () => ({ response: MOCK_RETRIEVE_REPORT_RESPONSE })) + .mockImplementation(async () => ({ response: MOCK_RETRIEVE_REPORT_RESPONSE })); - const { result } = renderHook(() => useGenerateReportEffect()) + const { result } = renderHook(() => useGenerateReportEffect()); await waitFor(() => { - result.current.startToRequestBoardData(MOCK_GENERATE_REPORT_REQUEST_PARAMS) - expect(reportClient.pollingReport).toBeCalledTimes(1) - expect(result.current.isServerError).toEqual(true) - }) - }) + result.current.startToRequestBoardData(MOCK_GENERATE_REPORT_REQUEST_PARAMS); + expect(reportClient.pollingReport).toBeCalledTimes(1); + expect(result.current.isServerError).toEqual(true); + }); + }); it('should return error message when calling startToRequestBoardData given pollingReport response return 4xx ', async () => { reportClient.pollingReport = jest.fn().mockImplementation(async () => { - throw new NotFoundException('file not found', HttpStatusCode.NotFound) - }) + throw new NotFoundException('file not found', HttpStatusCode.NotFound); + }); reportClient.retrieveReportByUrl = jest .fn() - .mockImplementation(async () => ({ response: MOCK_RETRIEVE_REPORT_RESPONSE })) + .mockImplementation(async () => ({ response: MOCK_RETRIEVE_REPORT_RESPONSE })); - const { result } = renderHook(() => useGenerateReportEffect()) - result.current.stopPollingReports = jest.fn() + const { result } = renderHook(() => useGenerateReportEffect()); + result.current.stopPollingReports = jest.fn(); await waitFor(() => { - result.current.startToRequestBoardData(MOCK_GENERATE_REPORT_REQUEST_PARAMS) - expect(result.current.errorMessage).toEqual('generate report: file not found') - }) + result.current.startToRequestBoardData(MOCK_GENERATE_REPORT_REQUEST_PARAMS); + expect(result.current.errorMessage).toEqual('generate report: file not found'); + }); act(() => { - jest.advanceTimersByTime(ERROR_MESSAGE_TIME_DURATION) - }) + jest.advanceTimersByTime(ERROR_MESSAGE_TIME_DURATION); + }); await waitFor(() => { - expect(result.current.errorMessage).toEqual('') - }) - }) + expect(result.current.errorMessage).toEqual(''); + }); + }); it('should call polling report and setTimeout when calling startToRequestBoardData given pollingReport response return 204 ', async () => { reportClient.pollingReport = jest .fn() - .mockImplementation(async () => ({ status: HttpStatusCode.NoContent, response: MOCK_REPORT_RESPONSE })) + .mockImplementation(async () => ({ status: HttpStatusCode.NoContent, response: MOCK_REPORT_RESPONSE })); reportClient.retrieveReportByUrl = jest .fn() - .mockImplementation(async () => ({ response: MOCK_RETRIEVE_REPORT_RESPONSE })) + .mockImplementation(async () => ({ response: MOCK_RETRIEVE_REPORT_RESPONSE })); - const { result } = renderHook(() => useGenerateReportEffect()) + const { result } = renderHook(() => useGenerateReportEffect()); await waitFor(() => { - result.current.startToRequestBoardData(MOCK_GENERATE_REPORT_REQUEST_PARAMS) - }) + result.current.startToRequestBoardData(MOCK_GENERATE_REPORT_REQUEST_PARAMS); + }); - jest.runOnlyPendingTimers() + jest.runOnlyPendingTimers(); await waitFor(() => { - expect(reportClient.pollingReport).toHaveBeenCalledTimes(1) - }) - }) + expect(reportClient.pollingReport).toHaveBeenCalledTimes(1); + }); + }); it('should call polling report more than one time when allMetricsReady field in response is false', async () => { reportClient.pollingReport = jest.fn().mockImplementation(async () => ({ status: HttpStatusCode.NoContent, response: { ...MOCK_REPORT_RESPONSE, isAllMetricsReady: false }, - })) + })); reportClient.retrieveReportByUrl = jest .fn() - .mockImplementation(async () => ({ response: MOCK_RETRIEVE_REPORT_RESPONSE })) + .mockImplementation(async () => ({ response: MOCK_RETRIEVE_REPORT_RESPONSE })); - const { result } = renderHook(() => useGenerateReportEffect()) + const { result } = renderHook(() => useGenerateReportEffect()); await waitFor(() => { - result.current.startToRequestBoardData(MOCK_GENERATE_REPORT_REQUEST_PARAMS) - }) + result.current.startToRequestBoardData(MOCK_GENERATE_REPORT_REQUEST_PARAMS); + }); act(() => { - jest.advanceTimersByTime(10000) - }) + jest.advanceTimersByTime(10000); + }); await waitFor(() => { - expect(reportClient.pollingReport).toHaveBeenCalledTimes(2) - }) - }) + expect(reportClient.pollingReport).toHaveBeenCalledTimes(2); + }); + }); it('should call polling report only once when calling startToRequestBoardData but startToRequestDoraData called before', async () => { reportClient.pollingReport = jest .fn() - .mockImplementation(async () => ({ status: HttpStatusCode.NoContent, response: MOCK_REPORT_RESPONSE })) + .mockImplementation(async () => ({ status: HttpStatusCode.NoContent, response: MOCK_REPORT_RESPONSE })); reportClient.retrieveReportByUrl = jest .fn() - .mockImplementation(async () => ({ response: MOCK_RETRIEVE_REPORT_RESPONSE })) + .mockImplementation(async () => ({ response: MOCK_RETRIEVE_REPORT_RESPONSE })); - const { result } = renderHook(() => useGenerateReportEffect()) + const { result } = renderHook(() => useGenerateReportEffect()); await waitFor(() => { - result.current.startToRequestDoraData(MOCK_GENERATE_REPORT_REQUEST_PARAMS) - result.current.startToRequestBoardData(MOCK_GENERATE_REPORT_REQUEST_PARAMS) - }) + result.current.startToRequestDoraData(MOCK_GENERATE_REPORT_REQUEST_PARAMS); + result.current.startToRequestBoardData(MOCK_GENERATE_REPORT_REQUEST_PARAMS); + }); - jest.runOnlyPendingTimers() + jest.runOnlyPendingTimers(); await waitFor(() => { - expect(reportClient.pollingReport).toHaveBeenCalledTimes(1) - }) - }) + expect(reportClient.pollingReport).toHaveBeenCalledTimes(1); + }); + }); it('should set error message when generate report throw error', async () => { reportClient.retrieveReportByUrl = jest.fn().mockImplementation(async () => { - throw new Error('error') - }) + throw new Error('error'); + }); - const { result } = renderHook(() => useGenerateReportEffect()) + const { result } = renderHook(() => useGenerateReportEffect()); await waitFor(() => { - result.current.startToRequestDoraData(MOCK_GENERATE_REPORT_REQUEST_PARAMS) - expect(result.current.errorMessage).toEqual('generate report: error') - }) + result.current.startToRequestDoraData(MOCK_GENERATE_REPORT_REQUEST_PARAMS); + expect(result.current.errorMessage).toEqual('generate report: error'); + }); act(() => { - jest.advanceTimersByTime(ERROR_MESSAGE_TIME_DURATION) - }) + jest.advanceTimersByTime(ERROR_MESSAGE_TIME_DURATION); + }); await waitFor(() => { - expect(result.current.errorMessage).toEqual('') - }) - }) + expect(result.current.errorMessage).toEqual(''); + }); + }); it('should set error message when generate report response status 404', async () => { reportClient.retrieveReportByUrl = jest.fn().mockImplementation(async () => { - throw new NotFoundException('error message', HttpStatusCode.NotFound) - }) + throw new NotFoundException('error message', HttpStatusCode.NotFound); + }); - const { result } = renderHook(() => useGenerateReportEffect()) + const { result } = renderHook(() => useGenerateReportEffect()); await waitFor(() => { - result.current.startToRequestDoraData(MOCK_GENERATE_REPORT_REQUEST_PARAMS) - expect(result.current.errorMessage).toEqual('generate report: error message') - }) + result.current.startToRequestDoraData(MOCK_GENERATE_REPORT_REQUEST_PARAMS); + expect(result.current.errorMessage).toEqual('generate report: error message'); + }); act(() => { - jest.advanceTimersByTime(ERROR_MESSAGE_TIME_DURATION) - }) + jest.advanceTimersByTime(ERROR_MESSAGE_TIME_DURATION); + }); await waitFor(() => { - expect(result.current.errorMessage).toEqual('') - }) - }) + expect(result.current.errorMessage).toEqual(''); + }); + }); it('should set error message when generate report response status 500', async () => { reportClient.retrieveReportByUrl = jest.fn().mockImplementation(async () => { - throw new InternalServerException(INTERNAL_SERVER_ERROR_MESSAGE, HttpStatusCode.NotFound) - }) + throw new InternalServerException(INTERNAL_SERVER_ERROR_MESSAGE, HttpStatusCode.NotFound); + }); - const { result } = renderHook(() => useGenerateReportEffect()) + const { result } = renderHook(() => useGenerateReportEffect()); await waitFor(() => { - result.current.startToRequestDoraData(MOCK_GENERATE_REPORT_REQUEST_PARAMS) - expect(result.current.isServerError).toEqual(true) - }) - }) + result.current.startToRequestDoraData(MOCK_GENERATE_REPORT_REQUEST_PARAMS); + expect(result.current.isServerError).toEqual(true); + }); + }); it('should set isServerError is true when throw unknownException', async () => { reportClient.retrieveReportByUrl = jest.fn().mockImplementation(async () => { - throw new UnknownException() - }) + throw new UnknownException(); + }); - const { result } = renderHook(() => useGenerateReportEffect()) + const { result } = renderHook(() => useGenerateReportEffect()); await waitFor(() => { - result.current.startToRequestDoraData(MOCK_GENERATE_REPORT_REQUEST_PARAMS) - expect(result.current.isServerError).toEqual(true) - }) - }) + result.current.startToRequestDoraData(MOCK_GENERATE_REPORT_REQUEST_PARAMS); + expect(result.current.isServerError).toEqual(true); + }); + }); it('should return error message when calling startToRequestDoraData given pollingReport response return 5xx ', async () => { reportClient.pollingReport = jest.fn().mockImplementation(async () => { - throw new InternalServerException('error', HttpStatusCode.InternalServerError) - }) + throw new InternalServerException('error', HttpStatusCode.InternalServerError); + }); reportClient.retrieveReportByUrl = jest .fn() - .mockImplementation(async () => ({ response: MOCK_RETRIEVE_REPORT_RESPONSE })) + .mockImplementation(async () => ({ response: MOCK_RETRIEVE_REPORT_RESPONSE })); - const { result } = renderHook(() => useGenerateReportEffect()) + const { result } = renderHook(() => useGenerateReportEffect()); await waitFor(() => { - result.current.startToRequestDoraData(MOCK_GENERATE_REPORT_REQUEST_PARAMS) - expect(reportClient.pollingReport).toBeCalledTimes(1) - expect(result.current.isServerError).toEqual(true) - }) - }) + result.current.startToRequestDoraData(MOCK_GENERATE_REPORT_REQUEST_PARAMS); + expect(reportClient.pollingReport).toBeCalledTimes(1); + expect(result.current.isServerError).toEqual(true); + }); + }); it('should return error message when calling startToRequestDoraData given pollingReport response return 4xx ', async () => { reportClient.pollingReport = jest.fn().mockImplementation(async () => { - throw new NotFoundException('file not found', HttpStatusCode.NotFound) - }) + throw new NotFoundException('file not found', HttpStatusCode.NotFound); + }); reportClient.retrieveReportByUrl = jest .fn() - .mockImplementation(async () => ({ response: MOCK_RETRIEVE_REPORT_RESPONSE })) + .mockImplementation(async () => ({ response: MOCK_RETRIEVE_REPORT_RESPONSE })); - const { result } = renderHook(() => useGenerateReportEffect()) - result.current.stopPollingReports = jest.fn() + const { result } = renderHook(() => useGenerateReportEffect()); + result.current.stopPollingReports = jest.fn(); await waitFor(() => { - result.current.startToRequestDoraData(MOCK_GENERATE_REPORT_REQUEST_PARAMS) - expect(result.current.errorMessage).toEqual('generate report: file not found') - }) + result.current.startToRequestDoraData(MOCK_GENERATE_REPORT_REQUEST_PARAMS); + expect(result.current.errorMessage).toEqual('generate report: file not found'); + }); act(() => { - jest.advanceTimersByTime(ERROR_MESSAGE_TIME_DURATION) - }) + jest.advanceTimersByTime(ERROR_MESSAGE_TIME_DURATION); + }); await waitFor(() => { - expect(result.current.errorMessage).toEqual('') - }) - }) + expect(result.current.errorMessage).toEqual(''); + }); + }); it('should call polling report and setTimeout when calling startToRequestDoraData given pollingReport response return 204 ', async () => { reportClient.pollingReport = jest .fn() - .mockImplementation(async () => ({ status: HttpStatusCode.NoContent, response: MOCK_REPORT_RESPONSE })) + .mockImplementation(async () => ({ status: HttpStatusCode.NoContent, response: MOCK_REPORT_RESPONSE })); reportClient.retrieveReportByUrl = jest .fn() - .mockImplementation(async () => ({ response: MOCK_RETRIEVE_REPORT_RESPONSE })) + .mockImplementation(async () => ({ response: MOCK_RETRIEVE_REPORT_RESPONSE })); - const { result } = renderHook(() => useGenerateReportEffect()) + const { result } = renderHook(() => useGenerateReportEffect()); await waitFor(() => { - result.current.startToRequestDoraData(MOCK_GENERATE_REPORT_REQUEST_PARAMS) - }) + result.current.startToRequestDoraData(MOCK_GENERATE_REPORT_REQUEST_PARAMS); + }); - jest.runOnlyPendingTimers() + jest.runOnlyPendingTimers(); await waitFor(() => { - expect(reportClient.pollingReport).toHaveBeenCalledTimes(1) - }) - }) + expect(reportClient.pollingReport).toHaveBeenCalledTimes(1); + }); + }); it('should call polling report only once when calling startToRequestDoraData but startToRequestBoardData called before', async () => { reportClient.pollingReport = jest .fn() - .mockImplementation(async () => ({ status: HttpStatusCode.NoContent, response: MOCK_REPORT_RESPONSE })) + .mockImplementation(async () => ({ status: HttpStatusCode.NoContent, response: MOCK_REPORT_RESPONSE })); reportClient.retrieveReportByUrl = jest .fn() - .mockImplementation(async () => ({ response: MOCK_RETRIEVE_REPORT_RESPONSE })) + .mockImplementation(async () => ({ response: MOCK_RETRIEVE_REPORT_RESPONSE })); - const { result } = renderHook(() => useGenerateReportEffect()) + const { result } = renderHook(() => useGenerateReportEffect()); await waitFor(() => { - result.current.startToRequestBoardData(MOCK_GENERATE_REPORT_REQUEST_PARAMS) - result.current.startToRequestDoraData(MOCK_GENERATE_REPORT_REQUEST_PARAMS) - }) + result.current.startToRequestBoardData(MOCK_GENERATE_REPORT_REQUEST_PARAMS); + result.current.startToRequestDoraData(MOCK_GENERATE_REPORT_REQUEST_PARAMS); + }); - jest.runOnlyPendingTimers() + jest.runOnlyPendingTimers(); await waitFor(() => { - expect(reportClient.pollingReport).toHaveBeenCalledTimes(1) - }) - }) -}) + expect(reportClient.pollingReport).toHaveBeenCalledTimes(1); + }); + }); +}); diff --git a/frontend/__tests__/src/hooks/useGetMetricsStepsEffect.test.tsx b/frontend/__tests__/src/hooks/useGetMetricsStepsEffect.test.tsx index 49d1457054..2c139ea3fa 100644 --- a/frontend/__tests__/src/hooks/useGetMetricsStepsEffect.test.tsx +++ b/frontend/__tests__/src/hooks/useGetMetricsStepsEffect.test.tsx @@ -1,45 +1,45 @@ -import { act, renderHook } from '@testing-library/react' -import { useGetMetricsStepsEffect } from '@src/hooks/useGetMetricsStepsEffect' -import { metricsClient } from '@src/clients/MetricsClient' -import { InternalServerException } from '@src/exceptions/InternalServerException' -import { ERROR_MESSAGE_TIME_DURATION, MOCK_GET_STEPS_PARAMS } from '../fixtures' -import { HttpStatusCode } from 'axios' +import { act, renderHook } from '@testing-library/react'; +import { useGetMetricsStepsEffect } from '@src/hooks/useGetMetricsStepsEffect'; +import { metricsClient } from '@src/clients/MetricsClient'; +import { InternalServerException } from '@src/exceptions/InternalServerException'; +import { ERROR_MESSAGE_TIME_DURATION, MOCK_GET_STEPS_PARAMS } from '../fixtures'; +import { HttpStatusCode } from 'axios'; describe('use get steps effect', () => { - const { params, buildId, organizationId, pipelineType, token } = MOCK_GET_STEPS_PARAMS + const { params, buildId, organizationId, pipelineType, token } = MOCK_GET_STEPS_PARAMS; it('should init data state when render hook', async () => { - const { result } = renderHook(() => useGetMetricsStepsEffect()) + const { result } = renderHook(() => useGetMetricsStepsEffect()); - expect(result.current.isLoading).toEqual(false) - }) + expect(result.current.isLoading).toEqual(false); + }); it('should set error message when get steps throw error', async () => { - jest.useFakeTimers() + jest.useFakeTimers(); metricsClient.getSteps = jest.fn().mockImplementation(() => { - throw new Error('error') - }) - const { result } = renderHook(() => useGetMetricsStepsEffect()) + throw new Error('error'); + }); + const { result } = renderHook(() => useGetMetricsStepsEffect()); - expect(result.current.isLoading).toEqual(false) + expect(result.current.isLoading).toEqual(false); act(() => { - result.current.getSteps(params, buildId, organizationId, pipelineType, token) - jest.advanceTimersByTime(ERROR_MESSAGE_TIME_DURATION) - }) + result.current.getSteps(params, buildId, organizationId, pipelineType, token); + jest.advanceTimersByTime(ERROR_MESSAGE_TIME_DURATION); + }); - expect(result.current.errorMessage).toEqual('') - }) + expect(result.current.errorMessage).toEqual(''); + }); it('should set error message when get steps response status 500', async () => { metricsClient.getSteps = jest.fn().mockImplementation(() => { - throw new InternalServerException('error message', HttpStatusCode.InternalServerError) - }) - const { result } = renderHook(() => useGetMetricsStepsEffect()) + throw new InternalServerException('error message', HttpStatusCode.InternalServerError); + }); + const { result } = renderHook(() => useGetMetricsStepsEffect()); act(() => { - result.current.getSteps(params, buildId, organizationId, pipelineType, token) - }) + result.current.getSteps(params, buildId, organizationId, pipelineType, token); + }); - expect(result.current.errorMessage).toEqual('BuildKite get steps failed: error message') - }) -}) + expect(result.current.errorMessage).toEqual('BuildKite get steps failed: error message'); + }); +}); diff --git a/frontend/__tests__/src/hooks/useMetricsStepValidationCheckContext.test.tsx b/frontend/__tests__/src/hooks/useMetricsStepValidationCheckContext.test.tsx index 0fcb7d258e..3f4407bab8 100644 --- a/frontend/__tests__/src/hooks/useMetricsStepValidationCheckContext.test.tsx +++ b/frontend/__tests__/src/hooks/useMetricsStepValidationCheckContext.test.tsx @@ -1,14 +1,14 @@ -import { act, renderHook } from '@testing-library/react' -import { ContextProvider, useMetricsStepValidationCheckContext } from '@src/hooks/useMetricsStepValidationCheckContext' -import React from 'react' -import { addADeploymentFrequencySetting, updateDeploymentFrequencySettings } from '@src/context/Metrics/metricsSlice' -import { Provider } from 'react-redux' -import { setupStore } from '../utils/setupStoreUtil' -import { ToolkitStore } from '@reduxjs/toolkit/dist/configureStore' +import { act, renderHook } from '@testing-library/react'; +import { ContextProvider, useMetricsStepValidationCheckContext } from '@src/hooks/useMetricsStepValidationCheckContext'; +import React from 'react'; +import { addADeploymentFrequencySetting, updateDeploymentFrequencySettings } from '@src/context/Metrics/metricsSlice'; +import { Provider } from 'react-redux'; +import { setupStore } from '../utils/setupStoreUtil'; +import { ToolkitStore } from '@reduxjs/toolkit/dist/configureStore'; describe('useMetricsStepValidationCheckContext', () => { - const DEPLOYMENT_FREQUENCY_SETTINGS = 'DeploymentFrequencySettings' - const LEAD_TIME_FOR_CHANGES = 'LeadTimeForChanges' + const DEPLOYMENT_FREQUENCY_SETTINGS = 'DeploymentFrequencySettings'; + const LEAD_TIME_FOR_CHANGES = 'LeadTimeForChanges'; const duplicatedData = [ { @@ -21,92 +21,92 @@ describe('useMetricsStepValidationCheckContext', () => { { updateId: 1, label: 'organization', value: 'mockOrganization' }, { updateId: 1, label: 'pipelineName', value: 'mockPipelineName' }, { updateId: 1, label: 'step', value: 'mockstep' }, - ] + ]; const setup = () => { - const store = setupStore() + const store = setupStore(); const wrapper = ({ children }: { children: React.ReactNode }) => ( {children} - ) - const { result } = renderHook(() => useMetricsStepValidationCheckContext(), { wrapper }) + ); + const { result } = renderHook(() => useMetricsStepValidationCheckContext(), { wrapper }); - return { result, store } - } + return { result, store }; + }; const setDuplicatedDataToStore = (store: ToolkitStore) => { - store.dispatch(addADeploymentFrequencySetting()) + store.dispatch(addADeploymentFrequencySetting()); duplicatedData.map((data) => { - store.dispatch(updateDeploymentFrequencySettings(data)) - }) - } + store.dispatch(updateDeploymentFrequencySettings(data)); + }); + }; it('should return initial ValidationContext ', () => { - const { result } = renderHook(() => useMetricsStepValidationCheckContext()) + const { result } = renderHook(() => useMetricsStepValidationCheckContext()); - expect(result.current?.isPipelineValid(DEPLOYMENT_FREQUENCY_SETTINGS)).toBe(false) - expect(result.current?.isPipelineValid(LEAD_TIME_FOR_CHANGES)).toBe(false) + expect(result.current?.isPipelineValid(DEPLOYMENT_FREQUENCY_SETTINGS)).toBe(false); + expect(result.current?.isPipelineValid(LEAD_TIME_FOR_CHANGES)).toBe(false); expect( result.current?.getDuplicatedPipeLineIds([{ id: 1, organization: '', pipelineName: '', step: '', branches: [] }]) - ).toEqual([]) - }) + ).toEqual([]); + }); it('should return useMetricsStepValidationCheckContext correctly ', () => { - const { result } = setup() + const { result } = setup(); - expect(result.current?.isPipelineValid).toBeInstanceOf(Function) - }) + expect(result.current?.isPipelineValid).toBeInstanceOf(Function); + }); it('should return true when call isPipelineValid given valid data', () => { - const { result, store } = setup() + const { result, store } = setup(); act(() => { store.dispatch( updateDeploymentFrequencySettings({ updateId: 0, label: 'organization', value: 'mockOrganization' }) - ) + ); store.dispatch( updateDeploymentFrequencySettings({ updateId: 0, label: 'pipelineName', value: 'mockPipelineName' }) - ) - store.dispatch(updateDeploymentFrequencySettings({ updateId: 0, label: 'step', value: 'mockstep' })) - }) + ); + store.dispatch(updateDeploymentFrequencySettings({ updateId: 0, label: 'step', value: 'mockstep' })); + }); act(() => { - expect(result.current?.isPipelineValid(DEPLOYMENT_FREQUENCY_SETTINGS)).toBe(true) - }) - }) + expect(result.current?.isPipelineValid(DEPLOYMENT_FREQUENCY_SETTINGS)).toBe(true); + }); + }); it('should return false when call isPipelineValid given duplicated data', () => { - const { result, store } = setup() + const { result, store } = setup(); act(() => { - setDuplicatedDataToStore(store) - }) + setDuplicatedDataToStore(store); + }); act(() => { - expect(result.current?.isPipelineValid(DEPLOYMENT_FREQUENCY_SETTINGS)).toBe(false) - }) - }) + expect(result.current?.isPipelineValid(DEPLOYMENT_FREQUENCY_SETTINGS)).toBe(false); + }); + }); it('should return false when call isPipelineValid given empty data', () => { - const { result } = setup() + const { result } = setup(); act(() => { - expect(result.current?.isPipelineValid(DEPLOYMENT_FREQUENCY_SETTINGS)).toBe(false) - }) - }) + expect(result.current?.isPipelineValid(DEPLOYMENT_FREQUENCY_SETTINGS)).toBe(false); + }); + }); it('multiple situation test', async () => { - const { result, store } = setup() + const { result, store } = setup(); act(() => { - setDuplicatedDataToStore(store) - store.dispatch(addADeploymentFrequencySetting()) - }) + setDuplicatedDataToStore(store); + store.dispatch(addADeploymentFrequencySetting()); + }); act(() => { - expect(result.current?.isPipelineValid(DEPLOYMENT_FREQUENCY_SETTINGS)).toBe(false) - }) + expect(result.current?.isPipelineValid(DEPLOYMENT_FREQUENCY_SETTINGS)).toBe(false); + }); act(() => { - store.dispatch(addADeploymentFrequencySetting()) - }) - }) -}) + store.dispatch(addADeploymentFrequencySetting()); + }); + }); +}); diff --git a/frontend/__tests__/src/hooks/useNotificationLayoutEffect.test.tsx b/frontend/__tests__/src/hooks/useNotificationLayoutEffect.test.tsx index 9db07332ba..9c4ba1a883 100644 --- a/frontend/__tests__/src/hooks/useNotificationLayoutEffect.test.tsx +++ b/frontend/__tests__/src/hooks/useNotificationLayoutEffect.test.tsx @@ -1,60 +1,60 @@ -import { act, renderHook, waitFor } from '@testing-library/react' -import { useNotificationLayoutEffect } from '@src/hooks/useNotificationLayoutEffect' -import clearAllMocks = jest.clearAllMocks -import { DURATION } from '@src/constants/commons' +import { act, renderHook, waitFor } from '@testing-library/react'; +import { useNotificationLayoutEffect } from '@src/hooks/useNotificationLayoutEffect'; +import clearAllMocks = jest.clearAllMocks; +import { DURATION } from '@src/constants/commons'; describe('useNotificationLayoutEffect', () => { afterAll(() => { - clearAllMocks() - }) + clearAllMocks(); + }); const defaultProps = { title: '', open: false, closeAutomatically: false, durationTimeout: DURATION.NOTIFICATION_TIME, - } + }; it('should init the state of notificationProps when render hook', async () => { - const { result } = renderHook(() => useNotificationLayoutEffect()) + const { result } = renderHook(() => useNotificationLayoutEffect()); - expect(result.current.notificationProps).toEqual(defaultProps) - }) + expect(result.current.notificationProps).toEqual(defaultProps); + }); it('should reset the notificationProps when call resetProps given mock props', async () => { - const mockProps = { title: 'Test', open: true, closeAutomatically: false } - const { result } = renderHook(() => useNotificationLayoutEffect()) + const mockProps = { title: 'Test', open: true, closeAutomatically: false }; + const { result } = renderHook(() => useNotificationLayoutEffect()); act(() => { - result.current.notificationProps = mockProps - result.current.resetProps?.() - }) + result.current.notificationProps = mockProps; + result.current.resetProps?.(); + }); - expect(result.current.notificationProps).toEqual(defaultProps) - }) + expect(result.current.notificationProps).toEqual(defaultProps); + }); it('should update the notificationProps when call resetProps given mock props', async () => { - const mockProps = { title: 'Test', open: true, closeAutomatically: false } - const { result } = renderHook(() => useNotificationLayoutEffect()) + const mockProps = { title: 'Test', open: true, closeAutomatically: false }; + const { result } = renderHook(() => useNotificationLayoutEffect()); act(() => { - result.current.notificationProps = defaultProps - result.current.updateProps?.(mockProps) - }) + result.current.notificationProps = defaultProps; + result.current.updateProps?.(mockProps); + }); - expect(result.current.notificationProps).toEqual(mockProps) - }) + expect(result.current.notificationProps).toEqual(mockProps); + }); it('should reset the notificationProps when update the value of closeAutomatically given closeAutomatically equals to true', async () => { - jest.useFakeTimers() - const mockProps = { title: 'Test', open: true, closeAutomatically: true } - const { result } = renderHook(() => useNotificationLayoutEffect()) + jest.useFakeTimers(); + const mockProps = { title: 'Test', open: true, closeAutomatically: true }; + const { result } = renderHook(() => useNotificationLayoutEffect()); act(() => { - result.current.notificationProps = defaultProps - result.current.updateProps?.(mockProps) - }) + result.current.notificationProps = defaultProps; + result.current.updateProps?.(mockProps); + }); act(() => { - jest.advanceTimersByTime(DURATION.NOTIFICATION_TIME) - }) + jest.advanceTimersByTime(DURATION.NOTIFICATION_TIME); + }); await waitFor(() => { expect(result.current.notificationProps).toEqual({ @@ -62,42 +62,42 @@ describe('useNotificationLayoutEffect', () => { title: '', closeAutomatically: false, durationTimeout: DURATION.NOTIFICATION_TIME, - }) - }) + }); + }); - jest.useRealTimers() - }) + jest.useRealTimers(); + }); it('should reset the notificationProps after 5s when update the value of closeAutomatically given durationTimeout equals to 5s', async () => { - jest.useFakeTimers() - const { result } = renderHook(() => useNotificationLayoutEffect()) - const expectedTime = 5000 - const mockProps = { title: 'Test', open: true, closeAutomatically: true, durationTimeout: expectedTime } + jest.useFakeTimers(); + const { result } = renderHook(() => useNotificationLayoutEffect()); + const expectedTime = 5000; + const mockProps = { title: 'Test', open: true, closeAutomatically: true, durationTimeout: expectedTime }; const expectedProps = { open: false, title: '', closeAutomatically: false, durationTimeout: DURATION.NOTIFICATION_TIME, - } + }; act(() => { - result.current.notificationProps = defaultProps - result.current.updateProps?.(mockProps) - }) + result.current.notificationProps = defaultProps; + result.current.updateProps?.(mockProps); + }); - jest.advanceTimersByTime(1000) + jest.advanceTimersByTime(1000); await waitFor(() => { - expect(result.current.notificationProps).not.toEqual(expectedProps) - }) + expect(result.current.notificationProps).not.toEqual(expectedProps); + }); act(() => { - jest.advanceTimersByTime(expectedTime) - }) + jest.advanceTimersByTime(expectedTime); + }); await waitFor(() => { - expect(result.current.notificationProps).toEqual(expectedProps) - }) + expect(result.current.notificationProps).toEqual(expectedProps); + }); - jest.useRealTimers() - }) -}) + jest.useRealTimers(); + }); +}); diff --git a/frontend/__tests__/src/hooks/useVerifyBoardEffect.test.tsx b/frontend/__tests__/src/hooks/useVerifyBoardEffect.test.tsx index be63591fef..d9ef94e670 100644 --- a/frontend/__tests__/src/hooks/useVerifyBoardEffect.test.tsx +++ b/frontend/__tests__/src/hooks/useVerifyBoardEffect.test.tsx @@ -1,44 +1,44 @@ -import { act, renderHook } from '@testing-library/react' -import { useVerifyBoardEffect } from '@src/hooks/useVerifyBoardEffect' -import { boardClient } from '@src/clients/board/BoardClient' -import { ERROR_MESSAGE_TIME_DURATION, MOCK_BOARD_VERIFY_REQUEST_PARAMS, VERIFY_FAILED } from '../fixtures' -import { InternalServerException } from '@src/exceptions/InternalServerException' -import { HttpStatusCode } from 'axios' +import { act, renderHook } from '@testing-library/react'; +import { useVerifyBoardEffect } from '@src/hooks/useVerifyBoardEffect'; +import { boardClient } from '@src/clients/board/BoardClient'; +import { ERROR_MESSAGE_TIME_DURATION, MOCK_BOARD_VERIFY_REQUEST_PARAMS, VERIFY_FAILED } from '../fixtures'; +import { InternalServerException } from '@src/exceptions/InternalServerException'; +import { HttpStatusCode } from 'axios'; describe('use verify board state', () => { it('should initial data state when render hook', async () => { - const { result } = renderHook(() => useVerifyBoardEffect()) + const { result } = renderHook(() => useVerifyBoardEffect()); - expect(result.current.isLoading).toEqual(false) - }) + expect(result.current.isLoading).toEqual(false); + }); it('should set error message when get verify board throw error', async () => { - jest.useFakeTimers() + jest.useFakeTimers(); boardClient.getVerifyBoard = jest.fn().mockImplementation(() => { - throw new Error('error') - }) - const { result } = renderHook(() => useVerifyBoardEffect()) + throw new Error('error'); + }); + const { result } = renderHook(() => useVerifyBoardEffect()); - expect(result.current.isLoading).toEqual(false) + expect(result.current.isLoading).toEqual(false); act(() => { - result.current.verifyJira(MOCK_BOARD_VERIFY_REQUEST_PARAMS) - jest.advanceTimersByTime(ERROR_MESSAGE_TIME_DURATION) - }) + result.current.verifyJira(MOCK_BOARD_VERIFY_REQUEST_PARAMS); + jest.advanceTimersByTime(ERROR_MESSAGE_TIME_DURATION); + }); - expect(result.current.errorMessage).toEqual('') - }) + expect(result.current.errorMessage).toEqual(''); + }); it('should set error message when get verify board response status 500', async () => { boardClient.getVerifyBoard = jest.fn().mockImplementation(() => { - throw new InternalServerException('error message', HttpStatusCode.InternalServerError) - }) - const { result } = renderHook(() => useVerifyBoardEffect()) + throw new InternalServerException('error message', HttpStatusCode.InternalServerError); + }); + const { result } = renderHook(() => useVerifyBoardEffect()); act(() => { - result.current.verifyJira(MOCK_BOARD_VERIFY_REQUEST_PARAMS) - }) + result.current.verifyJira(MOCK_BOARD_VERIFY_REQUEST_PARAMS); + }); expect(result.current.errorMessage).toEqual( `${MOCK_BOARD_VERIFY_REQUEST_PARAMS.type} ${VERIFY_FAILED}: error message` - ) - }) -}) + ); + }); +}); diff --git a/frontend/__tests__/src/hooks/useVerifyPipelineToolEffect.test.tsx b/frontend/__tests__/src/hooks/useVerifyPipelineToolEffect.test.tsx index b886f708ef..aabdfa7920 100644 --- a/frontend/__tests__/src/hooks/useVerifyPipelineToolEffect.test.tsx +++ b/frontend/__tests__/src/hooks/useVerifyPipelineToolEffect.test.tsx @@ -1,45 +1,45 @@ -import { act, renderHook } from '@testing-library/react' -import { useVerifyPipelineToolEffect } from '@src/hooks/useVerifyPipelineToolEffect' -import { pipelineToolClient } from '@src/clients/pipeline/PipelineToolClient' -import { ERROR_MESSAGE_TIME_DURATION, MOCK_PIPELINE_VERIFY_REQUEST_PARAMS, VERIFY_FAILED } from '../fixtures' -import { InternalServerException } from '@src/exceptions/InternalServerException' -import { HttpStatusCode } from 'axios' +import { act, renderHook } from '@testing-library/react'; +import { useVerifyPipelineToolEffect } from '@src/hooks/useVerifyPipelineToolEffect'; +import { pipelineToolClient } from '@src/clients/pipeline/PipelineToolClient'; +import { ERROR_MESSAGE_TIME_DURATION, MOCK_PIPELINE_VERIFY_REQUEST_PARAMS, VERIFY_FAILED } from '../fixtures'; +import { InternalServerException } from '@src/exceptions/InternalServerException'; +import { HttpStatusCode } from 'axios'; describe('use verify pipelineTool state', () => { it('should initial data state when render hook', async () => { - const { result } = renderHook(() => useVerifyPipelineToolEffect()) + const { result } = renderHook(() => useVerifyPipelineToolEffect()); - expect(result.current.isLoading).toEqual(false) - }) + expect(result.current.isLoading).toEqual(false); + }); it('should set error message when get verify pipelineTool throw error', async () => { - jest.useFakeTimers() + jest.useFakeTimers(); pipelineToolClient.verifyPipelineTool = jest.fn().mockImplementation(() => { - throw new Error('error') - }) - const { result } = renderHook(() => useVerifyPipelineToolEffect()) + throw new Error('error'); + }); + const { result } = renderHook(() => useVerifyPipelineToolEffect()); - expect(result.current.isLoading).toEqual(false) + expect(result.current.isLoading).toEqual(false); act(() => { - result.current.verifyPipelineTool(MOCK_PIPELINE_VERIFY_REQUEST_PARAMS) - jest.advanceTimersByTime(ERROR_MESSAGE_TIME_DURATION) - }) + result.current.verifyPipelineTool(MOCK_PIPELINE_VERIFY_REQUEST_PARAMS); + jest.advanceTimersByTime(ERROR_MESSAGE_TIME_DURATION); + }); - expect(result.current.errorMessage).toEqual('') - }) + expect(result.current.errorMessage).toEqual(''); + }); it('should set error message when get verify pipeline response status 500', async () => { pipelineToolClient.verifyPipelineTool = jest.fn().mockImplementation(() => { - throw new InternalServerException('error message', HttpStatusCode.InternalServerError) - }) - const { result } = renderHook(() => useVerifyPipelineToolEffect()) + throw new InternalServerException('error message', HttpStatusCode.InternalServerError); + }); + const { result } = renderHook(() => useVerifyPipelineToolEffect()); act(() => { - result.current.verifyPipelineTool(MOCK_PIPELINE_VERIFY_REQUEST_PARAMS) - }) + result.current.verifyPipelineTool(MOCK_PIPELINE_VERIFY_REQUEST_PARAMS); + }); expect(result.current.errorMessage).toEqual( `${MOCK_PIPELINE_VERIFY_REQUEST_PARAMS.type} ${VERIFY_FAILED}: error message` - ) - }) -}) + ); + }); +}); diff --git a/frontend/__tests__/src/hooks/useVerifySourceControlEffect.test.tsx b/frontend/__tests__/src/hooks/useVerifySourceControlEffect.test.tsx index 4db96316d4..7da8dd95a7 100644 --- a/frontend/__tests__/src/hooks/useVerifySourceControlEffect.test.tsx +++ b/frontend/__tests__/src/hooks/useVerifySourceControlEffect.test.tsx @@ -1,46 +1,46 @@ -import { act, renderHook } from '@testing-library/react' -import { useVerifySourceControlEffect } from '@src/hooks/useVeritySourceControlEffect' -import { sourceControlClient } from '@src/clients/sourceControl/SourceControlClient' -import { ERROR_MESSAGE_TIME_DURATION, MOCK_SOURCE_CONTROL_VERIFY_REQUEST_PARAMS, VERIFY_FAILED } from '../fixtures' -import { InternalServerException } from '@src/exceptions/InternalServerException' -import { HttpStatusCode } from 'axios' +import { act, renderHook } from '@testing-library/react'; +import { useVerifySourceControlEffect } from '@src/hooks/useVeritySourceControlEffect'; +import { sourceControlClient } from '@src/clients/sourceControl/SourceControlClient'; +import { ERROR_MESSAGE_TIME_DURATION, MOCK_SOURCE_CONTROL_VERIFY_REQUEST_PARAMS, VERIFY_FAILED } from '../fixtures'; +import { InternalServerException } from '@src/exceptions/InternalServerException'; +import { HttpStatusCode } from 'axios'; describe('use verify sourceControl state', () => { it('should initial data state when render hook', async () => { - const { result } = renderHook(() => useVerifySourceControlEffect()) + const { result } = renderHook(() => useVerifySourceControlEffect()); - expect(result.current.isLoading).toEqual(false) - }) + expect(result.current.isLoading).toEqual(false); + }); it('should set error message when get verify sourceControl throw error', async () => { - jest.useFakeTimers() + jest.useFakeTimers(); sourceControlClient.getVerifySourceControl = jest.fn().mockImplementation(() => { - throw new Error('error') - }) - const { result } = renderHook(() => useVerifySourceControlEffect()) + throw new Error('error'); + }); + const { result } = renderHook(() => useVerifySourceControlEffect()); - expect(result.current.isLoading).toEqual(false) + expect(result.current.isLoading).toEqual(false); act(() => { - result.current.verifyGithub(MOCK_SOURCE_CONTROL_VERIFY_REQUEST_PARAMS) - jest.advanceTimersByTime(ERROR_MESSAGE_TIME_DURATION) - }) + result.current.verifyGithub(MOCK_SOURCE_CONTROL_VERIFY_REQUEST_PARAMS); + jest.advanceTimersByTime(ERROR_MESSAGE_TIME_DURATION); + }); - expect(result.current.errorMessage).toEqual('') - }) + expect(result.current.errorMessage).toEqual(''); + }); it('should set error message when get verify sourceControl response status 500', async () => { sourceControlClient.getVerifySourceControl = jest.fn().mockImplementation(() => { - throw new InternalServerException('error message', HttpStatusCode.InternalServerError) - }) - const { result } = renderHook(() => useVerifySourceControlEffect()) + throw new InternalServerException('error message', HttpStatusCode.InternalServerError); + }); + const { result } = renderHook(() => useVerifySourceControlEffect()); act(() => { - result.current.verifyGithub(MOCK_SOURCE_CONTROL_VERIFY_REQUEST_PARAMS) - }) + result.current.verifyGithub(MOCK_SOURCE_CONTROL_VERIFY_REQUEST_PARAMS); + }); expect(result.current.errorMessage).toEqual( `${MOCK_SOURCE_CONTROL_VERIFY_REQUEST_PARAMS.type} ${VERIFY_FAILED}: error message` - ) - }) -}) + ); + }); +}); diff --git a/frontend/__tests__/src/initialConfigState.ts b/frontend/__tests__/src/initialConfigState.ts index 91a08e98c7..3d47466e9d 100644 --- a/frontend/__tests__/src/initialConfigState.ts +++ b/frontend/__tests__/src/initialConfigState.ts @@ -1,5 +1,5 @@ -import { BOARD_TYPES, PIPELINE_TOOL_TYPES, REGULAR_CALENDAR, SOURCE_CONTROL_TYPES } from './fixtures' -import { BasicConfigState } from '@src/context/config/configSlice' +import { BOARD_TYPES, PIPELINE_TOOL_TYPES, REGULAR_CALENDAR, SOURCE_CONTROL_TYPES } from './fixtures'; +import { BasicConfigState } from '@src/context/config/configSlice'; const initialConfigState: BasicConfigState = { isProjectCreated: true, @@ -53,6 +53,6 @@ const initialConfigState: BasicConfigState = { }, }, warningMessage: null, -} +}; -export default initialConfigState +export default initialConfigState; diff --git a/frontend/__tests__/src/layouts/Header.test.tsx b/frontend/__tests__/src/layouts/Header.test.tsx index e61aa5edce..008b58e854 100644 --- a/frontend/__tests__/src/layouts/Header.test.tsx +++ b/frontend/__tests__/src/layouts/Header.test.tsx @@ -1,22 +1,22 @@ -import { act, fireEvent, render } from '@testing-library/react' -import Header from '@src/layouts/Header' -import { BrowserRouter, MemoryRouter } from 'react-router-dom' -import { navigateMock } from '../../setupTests' -import { PROJECT_NAME } from '../fixtures' -import { headerClient } from '@src/clients/header/HeaderClient' -import { Provider } from 'react-redux' -import { setupStore } from '../utils/setupStoreUtil' +import { act, fireEvent, render } from '@testing-library/react'; +import Header from '@src/layouts/Header'; +import { BrowserRouter, MemoryRouter } from 'react-router-dom'; +import { navigateMock } from '../../setupTests'; +import { PROJECT_NAME } from '../fixtures'; +import { headerClient } from '@src/clients/header/HeaderClient'; +import { Provider } from 'react-redux'; +import { setupStore } from '../utils/setupStoreUtil'; describe('Header', () => { - let store = setupStore() + let store = setupStore(); beforeEach(() => { - headerClient.getVersion = jest.fn().mockResolvedValue('') - store = setupStore() - }) + headerClient.getVersion = jest.fn().mockResolvedValue(''); + store = setupStore(); + }); afterEach(() => { - jest.clearAllMocks() - }) + jest.clearAllMocks(); + }); const setup = () => render( @@ -25,46 +25,46 @@ describe('Header', () => {
- ) + ); it('should show project name', () => { - const { getByText } = setup() + const { getByText } = setup(); - expect(getByText(PROJECT_NAME)).toBeInTheDocument() - }) + expect(getByText(PROJECT_NAME)).toBeInTheDocument(); + }); it('should show version info when request succeed', async () => { - headerClient.getVersion = jest.fn().mockResolvedValueOnce('1.11') - const { getByText } = await act(async () => setup()) + headerClient.getVersion = jest.fn().mockResolvedValueOnce('1.11'); + const { getByText } = await act(async () => setup()); - expect(getByText(/v1.11/)).toBeInTheDocument() - }) + expect(getByText(/v1.11/)).toBeInTheDocument(); + }); it('should show version info when request failed', async () => { - headerClient.getVersion = jest.fn().mockResolvedValueOnce('') - const { queryByText } = await act(async () => setup()) + headerClient.getVersion = jest.fn().mockResolvedValueOnce(''); + const { queryByText } = await act(async () => setup()); - expect(queryByText(/v/)).not.toBeInTheDocument() - }) + expect(queryByText(/v/)).not.toBeInTheDocument(); + }); it('should show project logo', () => { - const { getByRole } = setup() + const { getByRole } = setup(); - const logoInstance = getByRole('img') - expect(logoInstance).toBeInTheDocument() - expect(logoInstance.getAttribute('alt')).toContain('logo') - }) + const logoInstance = getByRole('img'); + expect(logoInstance).toBeInTheDocument(); + expect(logoInstance.getAttribute('alt')).toContain('logo'); + }); it('should go to home page when click logo', () => { - const { getByText } = setup() + const { getByText } = setup(); - fireEvent.click(getByText(PROJECT_NAME)) + fireEvent.click(getByText(PROJECT_NAME)); - expect(window.location.pathname).toEqual('/') - }) + expect(window.location.pathname).toEqual('/'); + }); describe('HomeIcon', () => { - const homeBtnText = 'Home' + const homeBtnText = 'Home'; const setup = (pathname: string) => render( @@ -72,61 +72,61 @@ describe('Header', () => {
- ) + ); afterEach(() => { - jest.clearAllMocks() - }) + jest.clearAllMocks(); + }); it('should not show home icon when pathname is others', () => { - const { queryByTitle } = setup('/not/home/page') + const { queryByTitle } = setup('/not/home/page'); - expect(queryByTitle(homeBtnText)).not.toBeInTheDocument() - }) + expect(queryByTitle(homeBtnText)).not.toBeInTheDocument(); + }); it('should not show home icon when pathname is index.html', () => { - const { queryByTitle } = setup('/index.html') + const { queryByTitle } = setup('/index.html'); - expect(queryByTitle(homeBtnText)).not.toBeInTheDocument() - }) + expect(queryByTitle(homeBtnText)).not.toBeInTheDocument(); + }); it('should navigate to home page', () => { - const { getByTitle } = setup('/error-page') + const { getByTitle } = setup('/error-page'); - fireEvent.click(getByTitle(homeBtnText)) + fireEvent.click(getByTitle(homeBtnText)); - expect(navigateMock).toBeCalledTimes(1) - expect(navigateMock).toBeCalledWith('/') - }) + expect(navigateMock).toBeCalledTimes(1); + expect(navigateMock).toBeCalledWith('/'); + }); it('should navigate to home page', () => { - const { getByTitle } = setup('/metrics') + const { getByTitle } = setup('/metrics'); - fireEvent.click(getByTitle(homeBtnText)) + fireEvent.click(getByTitle(homeBtnText)); - expect(navigateMock).toBeCalledTimes(1) - expect(navigateMock).toBeCalledWith('/') - }) + expect(navigateMock).toBeCalledTimes(1); + expect(navigateMock).toBeCalledWith('/'); + }); it('should go to home page when click logo given a not home page path', () => { - const { getByText } = setup('/not/home/page') + const { getByText } = setup('/not/home/page'); - fireEvent.click(getByText(PROJECT_NAME)) + fireEvent.click(getByText(PROJECT_NAME)); - expect(window.location.pathname).toEqual('/') - }) + expect(window.location.pathname).toEqual('/'); + }); it('should go to home page when click logo given a not home page path', () => { - const { getByText } = setup('/index.html') + const { getByText } = setup('/index.html'); - fireEvent.click(getByText(PROJECT_NAME)) + fireEvent.click(getByText(PROJECT_NAME)); - expect(window.location.pathname).toEqual('/') - }) + expect(window.location.pathname).toEqual('/'); + }); it('should render notification button when location equals to "/metrics".', () => { - const { getByTestId } = setup('/metrics') - expect(getByTestId('NotificationButton')).toBeInTheDocument() - }) - }) -}) + const { getByTestId } = setup('/metrics'); + expect(getByTestId('NotificationButton')).toBeInTheDocument(); + }); + }); +}); diff --git a/frontend/__tests__/src/pages/Home.test.tsx b/frontend/__tests__/src/pages/Home.test.tsx index bf801e94eb..8b8443531a 100644 --- a/frontend/__tests__/src/pages/Home.test.tsx +++ b/frontend/__tests__/src/pages/Home.test.tsx @@ -1,26 +1,26 @@ -import { render } from '@testing-library/react' -import { PROJECT_NAME } from '../fixtures' -import Home from '@src/pages/Home' -import { MemoryRouter } from 'react-router-dom' -import { setupStore } from '../utils/setupStoreUtil' -import { Provider } from 'react-redux' +import { render } from '@testing-library/react'; +import { PROJECT_NAME } from '../fixtures'; +import Home from '@src/pages/Home'; +import { MemoryRouter } from 'react-router-dom'; +import { setupStore } from '../utils/setupStoreUtil'; +import { Provider } from 'react-redux'; const setup = () => { - store = setupStore() + store = setupStore(); return render( - ) -} -let store = null + ); +}; +let store = null; describe('Home', () => { it('should render home page', () => { - const { getByText } = setup() + const { getByText } = setup(); - expect(getByText(PROJECT_NAME)).toBeInTheDocument() - }) -}) + expect(getByText(PROJECT_NAME)).toBeInTheDocument(); + }); +}); diff --git a/frontend/__tests__/src/pages/Metrics.test.tsx b/frontend/__tests__/src/pages/Metrics.test.tsx index e031f957a3..09f325d8ad 100644 --- a/frontend/__tests__/src/pages/Metrics.test.tsx +++ b/frontend/__tests__/src/pages/Metrics.test.tsx @@ -1,8 +1,8 @@ -import { render } from '@testing-library/react' -import Metrics from '@src/pages/Metrics' -import { Provider } from 'react-redux' -import { store } from '@src/store' -import { MemoryRouter } from 'react-router-dom' +import { render } from '@testing-library/react'; +import Metrics from '@src/pages/Metrics'; +import { Provider } from 'react-redux'; +import { store } from '@src/store'; +import { MemoryRouter } from 'react-router-dom'; describe('Metrics', () => { it('should render Metrics page', () => { @@ -12,11 +12,11 @@ describe('Metrics', () => { - ) - const steps = ['Config', 'Metrics', 'Report'] + ); + const steps = ['Config', 'Metrics', 'Report']; steps.map((label) => { - expect(getByText(label)).toBeVisible() - }) - }) -}) + expect(getByText(label)).toBeVisible(); + }); + }); +}); diff --git a/frontend/__tests__/src/router.test.tsx b/frontend/__tests__/src/router.test.tsx index b0049bcacd..aa039153c4 100644 --- a/frontend/__tests__/src/router.test.tsx +++ b/frontend/__tests__/src/router.test.tsx @@ -1,15 +1,15 @@ -import { render, waitFor } from '@testing-library/react' -import { MemoryRouter } from 'react-router-dom' -import Router from '@src/router' -import { Provider } from 'react-redux' -import { store } from '@src/store' -import { ERROR_PAGE_MESSAGE, ERROR_PAGE_ROUTE, BASE_PAGE_ROUTE, METRICS_PAGE_ROUTE } from './fixtures' +import { render, waitFor } from '@testing-library/react'; +import { MemoryRouter } from 'react-router-dom'; +import Router from '@src/router'; +import { Provider } from 'react-redux'; +import { store } from '@src/store'; +import { ERROR_PAGE_MESSAGE, ERROR_PAGE_ROUTE, BASE_PAGE_ROUTE, METRICS_PAGE_ROUTE } from './fixtures'; jest.mock('@src/pages/Metrics', () => ({ __esModule: true, default: () =>
Mocked Metrics Page
, -})) -jest.useFakeTimers() +})); +jest.useFakeTimers(); describe('router', () => { const setup = (routeUrl: string) => render( @@ -18,39 +18,39 @@ describe('router', () => { - ) + ); it('should show home page when loading on a bad page', async () => { - const badRoute = '/some/bad/route' + const badRoute = '/some/bad/route'; - setup(badRoute) + setup(badRoute); await waitFor(() => { - expect(window.location.pathname).toEqual('/') - }) - }) + expect(window.location.pathname).toEqual('/'); + }); + }); it('should show home page when go through base route', async () => { - setup(BASE_PAGE_ROUTE) + setup(BASE_PAGE_ROUTE); await waitFor(() => { - expect(window.location.pathname).toEqual('/') - }) - }) + expect(window.location.pathname).toEqual('/'); + }); + }); it('should show Metrics page when go Metrics page', async () => { - const { getByText } = setup(METRICS_PAGE_ROUTE) + const { getByText } = setup(METRICS_PAGE_ROUTE); await waitFor(() => { - expect(getByText('Mocked Metrics Page')).toBeInTheDocument() - }) - }) + expect(getByText('Mocked Metrics Page')).toBeInTheDocument(); + }); + }); it('should show error page when go error page', async () => { - const { getByText } = setup(ERROR_PAGE_ROUTE) + const { getByText } = setup(ERROR_PAGE_ROUTE); await waitFor(() => { - expect(getByText(ERROR_PAGE_MESSAGE)).toBeInTheDocument() - }) - }) -}) + expect(getByText(ERROR_PAGE_MESSAGE)).toBeInTheDocument(); + }); + }); +}); diff --git a/frontend/__tests__/src/updatedConfigState.ts b/frontend/__tests__/src/updatedConfigState.ts index 8699d8597d..0bfcc3a271 100644 --- a/frontend/__tests__/src/updatedConfigState.ts +++ b/frontend/__tests__/src/updatedConfigState.ts @@ -4,7 +4,7 @@ import { CONFIG_PAGE_VERIFY_IMPORT_ERROR_MESSAGE, PIPELINE_TOOL_TYPES, SOURCE_CONTROL_TYPES, -} from './fixtures' +} from './fixtures'; const updatedConfigState = { isProjectCreated: true, @@ -57,6 +57,6 @@ const updatedConfigState = { }, }, warningMessage: CONFIG_PAGE_VERIFY_IMPORT_ERROR_MESSAGE, -} +}; -export default updatedConfigState +export default updatedConfigState; diff --git a/frontend/__tests__/src/utils/Util.test.tsx b/frontend/__tests__/src/utils/Util.test.tsx index 99de1930e4..0d60391ee6 100644 --- a/frontend/__tests__/src/utils/Util.test.tsx +++ b/frontend/__tests__/src/utils/Util.test.tsx @@ -6,27 +6,27 @@ import { formatMinToHours, getJiraBoardToken, transformToCleanedBuildKiteEmoji, -} from '@src/utils/util' -import { CleanedBuildKiteEmoji, OriginBuildKiteEmoji } from '@src/emojis/emoji' -import { EMPTY_STRING } from '@src/constants/commons' +} from '@src/utils/util'; +import { CleanedBuildKiteEmoji, OriginBuildKiteEmoji } from '@src/emojis/emoji'; +import { EMPTY_STRING } from '@src/constants/commons'; import { FILTER_CYCLE_TIME_SETTINGS, MOCK_CYCLE_TIME_SETTING, MOCK_JIRA_WITH_STATUES_SETTING, PIPELINE_TOOL_TYPES, -} from '../fixtures' +} from '../fixtures'; describe('exportToJsonFile function', () => { it('should create a link element with the correct attributes and click it', () => { - const filename = 'test' - const json = { key: 'value' } - const documentCreateSpy = jest.spyOn(document, 'createElement') + const filename = 'test'; + const json = { key: 'value' }; + const documentCreateSpy = jest.spyOn(document, 'createElement'); - exportToJsonFile(filename, json) + exportToJsonFile(filename, json); - expect(documentCreateSpy).toHaveBeenCalledWith('a') - }) -}) + expect(documentCreateSpy).toHaveBeenCalledWith('a'); + }); +}); describe('transformToCleanedBuildKiteEmoji function', () => { it('should transform to cleaned emoji', () => { @@ -34,111 +34,111 @@ describe('transformToCleanedBuildKiteEmoji function', () => { name: 'zap', image: 'abc.com', aliases: [], - } + }; const expectedCleanedEmoji: CleanedBuildKiteEmoji = { image: 'abc.com', aliases: ['zap'], - } + }; - const [result] = transformToCleanedBuildKiteEmoji([mockOriginEmoji]) + const [result] = transformToCleanedBuildKiteEmoji([mockOriginEmoji]); - expect(result).toEqual(expectedCleanedEmoji) - }) -}) + expect(result).toEqual(expectedCleanedEmoji); + }); +}); describe('getJiraToken function', () => { it('should return an valid string when token is not empty string', () => { - const email = 'test@example.com' - const token = 'myToken' + const email = 'test@example.com'; + const token = 'myToken'; - const jiraToken = getJiraBoardToken(token, email) - const encodedMsg = `Basic ${btoa(`${email}:${token}`)}` + const jiraToken = getJiraBoardToken(token, email); + const encodedMsg = `Basic ${btoa(`${email}:${token}`)}`; - expect(jiraToken).toBe(encodedMsg) - }) + expect(jiraToken).toBe(encodedMsg); + }); it('should return an empty string when token is missing', () => { - const email = 'test@example.com' - const token = '' + const email = 'test@example.com'; + const token = ''; - const jiraToken = getJiraBoardToken(token, email) + const jiraToken = getJiraBoardToken(token, email); - expect(jiraToken).toBe('') - }) -}) + expect(jiraToken).toBe(''); + }); +}); describe('findCaseInsensitiveType function', () => { it('Should return "BuildKite" when passing a type given case insensitive input bUildkite', () => { - const selectedValue = 'bUildkite' - const value = findCaseInsensitiveType(Object.values(PIPELINE_TOOL_TYPES), selectedValue) - expect(value).toBe(PIPELINE_TOOL_TYPES.BUILD_KITE) - }) + const selectedValue = 'bUildkite'; + const value = findCaseInsensitiveType(Object.values(PIPELINE_TOOL_TYPES), selectedValue); + expect(value).toBe(PIPELINE_TOOL_TYPES.BUILD_KITE); + }); it('Should return "GoCD" when passing a type given case sensitive input GoCD', () => { - const selectedValue = 'GoCD' - const value = findCaseInsensitiveType(Object.values(PIPELINE_TOOL_TYPES), selectedValue) - expect(value).toBe(PIPELINE_TOOL_TYPES.GO_CD) - }) + const selectedValue = 'GoCD'; + const value = findCaseInsensitiveType(Object.values(PIPELINE_TOOL_TYPES), selectedValue); + expect(value).toBe(PIPELINE_TOOL_TYPES.GO_CD); + }); it('Should return "GoCD" when passing a type given case insensitive input Gocd', () => { - const selectedValue = 'Gocd' - const value = findCaseInsensitiveType(Object.values(PIPELINE_TOOL_TYPES), selectedValue) - expect(value).toBe(PIPELINE_TOOL_TYPES.GO_CD) - }) + const selectedValue = 'Gocd'; + const value = findCaseInsensitiveType(Object.values(PIPELINE_TOOL_TYPES), selectedValue); + expect(value).toBe(PIPELINE_TOOL_TYPES.GO_CD); + }); it('Should return "_BuildKite" when passing a type given the value mismatches with PIPELINE_TOOL_TYPES', () => { - const selectedValue = '_BuildKite' - const value = findCaseInsensitiveType(Object.values(PIPELINE_TOOL_TYPES), selectedValue) - expect(value).not.toBe(PIPELINE_TOOL_TYPES.BUILD_KITE) - expect(value).not.toBe(PIPELINE_TOOL_TYPES.GO_CD) - expect(value).toBe(selectedValue) - }) + const selectedValue = '_BuildKite'; + const value = findCaseInsensitiveType(Object.values(PIPELINE_TOOL_TYPES), selectedValue); + expect(value).not.toBe(PIPELINE_TOOL_TYPES.BUILD_KITE); + expect(value).not.toBe(PIPELINE_TOOL_TYPES.GO_CD); + expect(value).toBe(selectedValue); + }); it('Should return empty string when passing a type given empty string', () => { - const selectedValue = '' - const value = findCaseInsensitiveType(Object.values(PIPELINE_TOOL_TYPES), selectedValue) - expect(value).toBe(EMPTY_STRING) - }) -}) + const selectedValue = ''; + const value = findCaseInsensitiveType(Object.values(PIPELINE_TOOL_TYPES), selectedValue); + expect(value).toBe(EMPTY_STRING); + }); +}); describe('filterAndMapCycleTimeSettings function', () => { it('should filter and map CycleTimeSettings when generate report', () => { - const value = filterAndMapCycleTimeSettings(MOCK_CYCLE_TIME_SETTING, MOCK_JIRA_WITH_STATUES_SETTING) - expect(value).toStrictEqual(FILTER_CYCLE_TIME_SETTINGS) - }) + const value = filterAndMapCycleTimeSettings(MOCK_CYCLE_TIME_SETTING, MOCK_JIRA_WITH_STATUES_SETTING); + expect(value).toStrictEqual(FILTER_CYCLE_TIME_SETTINGS); + }); it('should filter and map CycleTimeSettings when generate report', () => { const filterCycleTimeSettings = [ { name: 'IN DEV', value: 'IN DEV' }, { name: 'DOING', value: 'IN DEV' }, { name: 'DONE', value: 'DONE' }, - ] + ]; const MOCK_CYCLE_TIME_SETTING = [ { name: 'TODO', value: 'TODO' }, { name: 'IN DEV', value: 'IN DEV' }, { name: 'DONE', value: 'DONE' }, - ] + ]; const MOCK_JIRA_WITH_STATUES_SETTING = [ { name: 'todo', statuses: ['TODO', 'BACKLOG'] }, { name: 'IN DEV', statuses: ['IN DEV', 'DOING'] }, { name: 'DONE', statuses: ['DONE'] }, - ] - const value = filterAndMapCycleTimeSettings(MOCK_CYCLE_TIME_SETTING, MOCK_JIRA_WITH_STATUES_SETTING) - expect(value).toStrictEqual(filterCycleTimeSettings) - }) + ]; + const value = filterAndMapCycleTimeSettings(MOCK_CYCLE_TIME_SETTING, MOCK_JIRA_WITH_STATUES_SETTING); + expect(value).toStrictEqual(filterCycleTimeSettings); + }); it('Should return 2 hours when passing a min', () => { - const expected = 2 - const result = formatMinToHours(120) - expect(result).toEqual(expected) - }) + const expected = 2; + const result = formatMinToHours(120); + expect(result).toEqual(expected); + }); it('Should return 2 hours when passing a Milliseconds', () => { - const expected = 2 - const result = formatMillisecondsToHours(7200000) - expect(result).toEqual(expected) - }) -}) + const expected = 2; + const result = formatMillisecondsToHours(7200000); + expect(result).toEqual(expected); + }); +}); diff --git a/frontend/__tests__/src/utils/setupStoreUtil.tsx b/frontend/__tests__/src/utils/setupStoreUtil.tsx index 03add66c2b..d4349076d8 100644 --- a/frontend/__tests__/src/utils/setupStoreUtil.tsx +++ b/frontend/__tests__/src/utils/setupStoreUtil.tsx @@ -1,8 +1,8 @@ -import { configureStore } from '@reduxjs/toolkit' -import { stepperSlice } from '@src/context/stepper/StepperSlice' -import { configSlice } from '@src/context/config/configSlice' -import { metricsSlice } from '@src/context/Metrics/metricsSlice' -import { headerSlice } from '@src/context/header/headerSlice' +import { configureStore } from '@reduxjs/toolkit'; +import { stepperSlice } from '@src/context/stepper/StepperSlice'; +import { configSlice } from '@src/context/config/configSlice'; +import { metricsSlice } from '@src/context/Metrics/metricsSlice'; +import { headerSlice } from '@src/context/header/headerSlice'; export const setupStore = () => { return configureStore({ @@ -13,5 +13,5 @@ export const setupStore = () => { [headerSlice.name]: headerSlice.reducer, }, middleware: [], - }) -} + }); +}; diff --git a/frontend/cypress.config.ts b/frontend/cypress.config.ts index 896ea4948c..f747f3128d 100644 --- a/frontend/cypress.config.ts +++ b/frontend/cypress.config.ts @@ -1,4 +1,4 @@ -import { defineConfig } from 'cypress' +import { defineConfig } from 'cypress'; export default defineConfig({ video: process.env.APP_ENV !== 'local', @@ -8,16 +8,16 @@ export default defineConfig({ baseUrl: process.env.APP_ORIGIN || 'http://localhost:4321', setupNodeEvents(on, config) { // eslint-disable-next-line @typescript-eslint/no-var-requires - require('./cypress/plugins/readDir.ts')(on, config) + require('./cypress/plugins/readDir.ts')(on, config); // eslint-disable-next-line @typescript-eslint/no-var-requires - require('./cypress/plugins/clearDownloadFile.ts')(on, config) + require('./cypress/plugins/clearDownloadFile.ts')(on, config); // eslint-disable-next-line @typescript-eslint/no-var-requires - require('cypress-mochawesome-reporter/plugin')(on) - return config + require('cypress-mochawesome-reporter/plugin')(on); + return config; }, }, chromeWebSecurity: false, modifyObstructiveCode: false, //Increase timeout - defaultCommandTimeout: 6000, -}) + defaultCommandTimeout: 10000, +}); diff --git a/frontend/cypress/e2e/createANewProject.cy.ts b/frontend/cypress/e2e/createANewProject.cy.ts index 9bfc8366b3..136fa923d2 100644 --- a/frontend/cypress/e2e/createANewProject.cy.ts +++ b/frontend/cypress/e2e/createANewProject.cy.ts @@ -1,31 +1,31 @@ -import { GITHUB_TOKEN, METRICS_TITLE } from '../fixtures/fixtures' -import homePage from '../pages/home' -import configPage from '../pages/metrics/config' -import metricsPage from '../pages/metrics/metrics' -import reportPage from '../pages/metrics/report' -import { TIPS } from '../../src/constants/resources' +import { GITHUB_TOKEN, METRICS_TITLE } from '../fixtures/fixtures'; +import homePage from '../pages/home'; +import configPage from '../pages/metrics/config'; +import metricsPage from '../pages/metrics/metrics'; +import reportPage from '../pages/metrics/report'; +import { TIPS } from '../../src/constants/resources'; const cycleTimeData = [ { label: 'Average Cycle Time(Days/SP)', value: '6.75' }, { label: 'Average Cycle Time(Days/Card)', value: '9.85' }, -] +]; const velocityData = [ { label: 'Velocity(Story Point)', value: '17.5' }, { label: 'Throughput(Cards Count)', value: '12' }, -] +]; -const deploymentFrequencyData = [{ label: 'Deployment Frequency(Deployments/Day)', value: '2.36' }] +const deploymentFrequencyData = [{ label: 'Deployment Frequency(Deployments/Day)', value: '2.36' }]; -const meanTimeToRecoveryData = [{ label: 'Mean Time To Recovery(Hours)', value: '4.43' }] +const meanTimeToRecoveryData = [{ label: 'Mean Time To Recovery(Hours)', value: '4.43' }]; const leadTimeForChangeData = [ { label: 'PR Lead Time(Hours)', value: '0.00' }, { label: 'Pipeline Lead Time(Hours)', value: '-4.87' }, { label: 'Total Lead Time(Hours)', value: '-4.87' }, -] +]; -const changeFailureRateData = [{ label: 'Failure Rate', value: '49' }] +const changeFailureRateData = [{ label: 'Failure Rate', value: '49' }]; const metricsTextList = [ 'Board configuration', @@ -80,12 +80,12 @@ const metricsTextList = [ 'Time to Detect - Hrs', 'Cause by - System', 'Pipeline settings', -] +]; const pipelineSettingsAutoCompleteTextList = [ { name: 'Organization', value: 'XXXX' }, { name: 'Step', value: 'RECORD RELEASE TO PROD' }, -] +]; const cycleTimeSettingsAutoCompleteTextList = [ { name: 'In Analysis', value: 'Analysis' }, @@ -96,7 +96,7 @@ const cycleTimeSettingsAutoCompleteTextList = [ { name: 'In Test', value: 'Testing' }, { name: 'Ready to Deploy', value: 'Review' }, { name: 'Done', value: 'Done' }, -] +]; const configTextList = [ 'Project name *', @@ -104,7 +104,7 @@ const configTextList = [ 'Classic Jira', 'BuildKite', 'GitHub', -] +]; const textInputValues = [ { index: 0, value: 'E2E Project' }, @@ -114,109 +114,129 @@ const textInputValues = [ { index: 4, value: 'test@test.com' }, { index: 5, value: 'PLL' }, { index: 6, value: 'site' }, -] +]; const tokenInputValues = [ { index: 0, value: 'mockToken' }, { index: 1, value: 'mock1234'.repeat(5) }, { index: 2, value: `${GITHUB_TOKEN}` }, -] +]; interface MetricsDataItem { - label: string - value?: string + label: string; + value?: string; } const checkMetricsCalculation = (testId: string, boardData: MetricsDataItem[]) => { - cy.get(testId).should('exist') + cy.get(testId).should('exist'); cy.get(testId) .children('[data-test-id="report-section"]') .children() .each((section, index) => { cy.wrap(section).within(() => { - cy.contains(boardData[index].label).should('exist') - cy.contains(boardData[index].value).should('exist') - }) - }) -} + cy.contains(boardData[index].label).should('exist'); + cy.contains(boardData[index].value).should('exist'); + }); + }); +}; + +const checkBoardShowMore = () => { + reportPage.showMoreBoardButton.should('exist'); + reportPage.goToBoardDetailPage(); + cy.get(`[data-test-id="${METRICS_TITLE.VELOCITY}"]`).find('tbody > tr').should('have.length', 2); + cy.get(`[data-test-id="${METRICS_TITLE.CYCLE_TIME}"]`).find('tbody > tr').should('have.length', 17); + cy.get(`[data-test-id="${METRICS_TITLE.CLASSIFICATION}"]`).find('tbody > tr').should('have.length', 103); + + reportPage.exportBoardData(); + checkBoardCSV(); + + reportPage.boardGoToReportPage(); +}; + +const checkDoraShowMore = () => { + reportPage.showMoreDoraButton.should('exist'); + reportPage.goToDoraDetailPage(); -// const checkPipelineCalculation = (testId: string) => { -// cy.get(testId).find('tr').contains('Deployment frequency(deployments/day)').should('exist') -// } + cy.get(`[data-test-id="${METRICS_TITLE.DEPLOYMENT_FREQUENCY}"]`).find('tbody > tr').should('have.length', 2); + cy.get(`[data-test-id="${METRICS_TITLE.LEAD_TIME_FOR_CHANGES}"]`).find('tbody > tr').should('have.length', 4); + cy.get(`[data-test-id="${METRICS_TITLE.CHANGE_FAILURE_RATE}"]`).find('tbody > tr').should('have.length', 2); + cy.get(`[data-test-id="${METRICS_TITLE.MEAN_TIME_TO_RECOVERY}"]`).find('tbody > tr').should('have.length', 2); -// const checkTimeToRecoveryPipelineCalculation = (testId: string) => { -// cy.get(testId).find('tr').contains('Mean Time To Recovery').should('exist') -// } + reportPage.exportPipelineData(); + checkPipelineCSV(); + + reportPage.doraGoToReportPage(); +}; const checkCycleTimeTooltip = () => { - metricsPage.cycleTimeTitleTooltip.trigger('mouseover') - cy.contains(TIPS.CYCLE_TIME).should('be.visible') -} + metricsPage.cycleTimeTitleTooltip.trigger('mouseover'); + cy.contains(TIPS.CYCLE_TIME).should('be.visible'); +}; const clearDownloadFile = () => { - cy.task('clearDownloads') - cy.wait(500) -} + cy.task('clearDownloads'); + cy.wait(500); +}; const checkMetricCSV = () => { - cy.wait(2000) + cy.wait(2000); cy.fixture('metric.csv').then((localFileContent) => { cy.task('readDir', 'cypress/downloads').then((files: string[]) => { - expect(files).to.match(new RegExp(/metric-.*\.csv/)) + expect(files).to.match(new RegExp(/metric-.*\.csv/)); files.forEach((file: string) => { if (file.match(/metric-.*\.csv/)) { cy.readFile(`cypress/downloads/${file}`).then((fileContent) => { - expect(fileContent).to.contains(localFileContent) - }) + expect(fileContent).to.contains(localFileContent); + }); } - }) - }) - }) -} + }); + }); + }); +}; const checkPipelineCSV = () => { - cy.wait(2000) + cy.wait(2000); return cy.task('readDir', 'cypress/downloads').then((files) => { - expect(files).to.match(new RegExp(/pipeline-.*\.csv/)) - }) -} + expect(files).to.match(new RegExp(/pipeline-.*\.csv/)); + }); +}; const checkBoardCSV = () => { - cy.wait(2000) + cy.wait(2000); return cy.task('readDir', 'cypress/downloads').then((files) => { - expect(files).to.match(new RegExp(/board-.*\.csv/)) - }) -} + expect(files).to.match(new RegExp(/board-.*\.csv/)); + }); +}; const checkFieldsExist = (fields: string[]) => { fields.forEach((item) => { - cy.contains(item).should('exist') - }) -} + cy.contains(item).should('exist'); + }); +}; const checkPipelineSettingsAutoCompleteFields = (fields: { name: string; value: string }[]) => { fields.forEach((item) => { - metricsPage.getPipelineSettingsAutoCompleteField(item.name).find('input').should('have.value', item.value) - }) -} + metricsPage.getPipelineSettingsAutoCompleteField(item.name).find('input').should('have.value', item.value); + }); +}; const checkCycleTimeSettingsAutoCompleteFields = (fields: { name: string; value: string }[]) => { fields.forEach((item) => { - metricsPage.getCycleTimeSettingsAutoCompleteField(item.name).find('input').should('have.value', item.value) - }) -} + metricsPage.getCycleTimeSettingsAutoCompleteField(item.name).find('input').should('have.value', item.value); + }); +}; const checkTextInputValuesExist = (fields: { index: number; value: string }[]) => { fields.forEach(({ index, value }) => { - cy.get('.MuiInputBase-root input[type="text"]').eq(index).should('have.value', value) - }) -} + cy.get('.MuiInputBase-root input[type="text"]').eq(index).should('have.value', value); + }); +}; const checkTokenInputValuesExist = (fields: { index: number; value: string }[]) => { fields.forEach(({ index, value }) => { - cy.get('[type="password"]').eq(index).should('have.value', value) - }) -} + cy.get('[type="password"]').eq(index).should('have.value', value); + }); +}; describe('Create a new project', () => { beforeEach(() => { @@ -224,128 +244,131 @@ describe('Create a new project', () => { method: '*', pattern: '/api/**', alias: 'api', - }) - }) + }); + }); it('Should create a new project manually', () => { - homePage.navigate() + homePage.navigate(); + + homePage.headerVersion.should('exist'); - homePage.headerVersion.should('exist') + homePage.createANewProject(); + cy.url().should('include', '/metrics'); - homePage.createANewProject() - cy.url().should('include', '/metrics') + configPage.typeProjectName('E2E Project'); - configPage.typeProjectName('E2E Project') + configPage.goHomePage(); + cy.url().should('include', '/'); - configPage.goHomePage() - cy.url().should('include', '/') + homePage.createANewProject(); + cy.contains('Project name *').should('have.value', ''); - homePage.createANewProject() - cy.contains('Project name *').should('have.value', '') + configPage.typeProjectName('E2E Project'); - configPage.typeProjectName('E2E Project') + configPage.selectDateRange(); - configPage.selectDateRange() + configPage.nextStepButton.should('be.disabled'); - configPage.nextStepButton.should('be.disabled') + configPage.selectMetricsData(); - configPage.selectMetricsData() + configPage.fillBoardInfoAndVerifyWithClassicJira('1963', 'test@test.com', 'PLL', 'site', 'mockToken'); + configPage.getVerifiedButton(configPage.boardConfigSection).should('be.disabled'); + configPage.getResetButton(configPage.boardConfigSection).should('be.enabled'); - configPage.fillBoardInfoAndVerifyWithClassicJira('1963', 'test@test.com', 'PLL', 'site', 'mockToken') - configPage.getVerifiedButton(configPage.boardConfigSection).should('be.disabled') - configPage.getResetButton(configPage.boardConfigSection).should('be.enabled') + configPage.fillPipelineToolFieldsInfoAndVerify('mock1234'.repeat(5)); - configPage.fillPipelineToolFieldsInfoAndVerify('mock1234'.repeat(5)) + configPage.fillSourceControlFieldsInfoAndVerify(`${GITHUB_TOKEN}`); - configPage.fillSourceControlFieldsInfoAndVerify(`${GITHUB_TOKEN}`) + configPage.nextStepButton.should('be.enabled'); - configPage.nextStepButton.should('be.enabled') + configPage.CancelBackToHomePage(); - configPage.CancelBackToHomePage() + configPage.goMetricsStep(); - configPage.goMetricsStep() + configPage.nextStepButton.should('be.disabled'); - configPage.nextStepButton.should('be.disabled') + checkCycleTimeTooltip(); - checkCycleTimeTooltip() + metricsPage.checkCycleTime(); - metricsPage.checkCycleTime() + cy.contains('Real done').should('exist'); - cy.contains('Real done').should('exist') + metricsPage.clickRealDone(); - metricsPage.clickRealDone() + metricsPage.clickClassification(); - metricsPage.clickClassification() + metricsPage.pipelineSettingTitle.should('be.exist'); - metricsPage.pipelineSettingTitle.should('be.exist') + metricsPage.addOneCorrectPipelineConfig(0); + metricsPage.selectBranchOption(); - metricsPage.addOneCorrectPipelineConfig(0) - metricsPage.selectBranchOption() + metricsPage.addOnePipelineButton.click(); + metricsPage.addOneErrorPipelineConfig(1); + metricsPage.buildKiteStepNotFoundTips.should('exist'); + metricsPage.pipelineRemoveButton.click(); - metricsPage.addOnePipelineButton.click() - metricsPage.addOneErrorPipelineConfig(1) - metricsPage.buildKiteStepNotFoundTips.should('exist') - metricsPage.pipelineRemoveButton.click() + metricsPage.addOnePipelineButton.click(); + metricsPage.addOneCorrectPipelineConfig(1); + cy.contains('This pipeline is the same as another one!').should('exist'); + metricsPage.pipelineRemoveButton.click(); - metricsPage.addOnePipelineButton.click() - metricsPage.addOneCorrectPipelineConfig(1) - cy.contains('This pipeline is the same as another one!').should('exist') - metricsPage.pipelineRemoveButton.click() + configPage.nextStepButton.should('be.enabled'); - configPage.nextStepButton.should('be.enabled') + metricsPage.goReportStep(); - metricsPage.goReportStep() + reportPage.pageIndicator.should('be.visible'); - reportPage.pageIndicator.should('be.visible') + reportPage.firstNotification.should('exist'); - reportPage.firstNotification.should('exist') + checkMetricsCalculation(`[data-test-id="${METRICS_TITLE.VELOCITY}"]`, velocityData); - checkMetricsCalculation(`[data-test-id="${METRICS_TITLE.VELOCITY}"]`, velocityData) + checkMetricsCalculation(`[data-test-id="${METRICS_TITLE.CYCLE_TIME}"]`, cycleTimeData); - checkMetricsCalculation(`[data-test-id="${METRICS_TITLE.CYCLE_TIME}"]`, cycleTimeData) + checkMetricsCalculation(`[data-test-id="${METRICS_TITLE.DEPLOYMENT_FREQUENCY}"]`, deploymentFrequencyData); - checkMetricsCalculation(`[data-test-id="${METRICS_TITLE.DEPLOYMENT_FREQUENCY}"]`, deploymentFrequencyData) + checkMetricsCalculation(`[data-test-id="${METRICS_TITLE.MEAN_TIME_TO_RECOVERY}"]`, meanTimeToRecoveryData); - checkMetricsCalculation(`[data-test-id="${METRICS_TITLE.MEAN_TIME_TO_RECOVERY}"]`, meanTimeToRecoveryData) + checkMetricsCalculation(`[data-test-id="${METRICS_TITLE.LEAD_TIME_FOR_CHANGES}"]`, leadTimeForChangeData); - checkMetricsCalculation(`[data-test-id="${METRICS_TITLE.LEAD_TIME_FOR_CHANGES}"]`, leadTimeForChangeData) + checkMetricsCalculation(`[data-test-id="${METRICS_TITLE.CHANGE_FAILURE_RATE}"]`, changeFailureRateData); - checkMetricsCalculation(`[data-test-id="${METRICS_TITLE.CHANGE_FAILURE_RATE}"]`, changeFailureRateData) + clearDownloadFile(); - clearDownloadFile() + reportPage.exportMetricDataButton.should('be.enabled'); - reportPage.exportMetricDataButton.should('be.enabled') + reportPage.exportMetricData(); - reportPage.exportMetricData() + checkMetricCSV(); - checkMetricCSV() + reportPage.exportPipelineDataButton.should('be.enabled'); - reportPage.exportPipelineDataButton.should('be.enabled') + reportPage.exportPipelineData(); - reportPage.exportPipelineData() + checkPipelineCSV(); - checkPipelineCSV() + reportPage.exportBoardDataButton.should('be.enabled'); - reportPage.exportBoardDataButton.should('be.enabled') + reportPage.exportBoardData(); - reportPage.exportBoardData() + checkBoardCSV(); - checkBoardCSV() + reportPage.firstNotification.should('not.exist'); - reportPage.firstNotification.should('not.exist') + checkBoardShowMore(); + checkDoraShowMore(); // checkpoint back to metrics step - reportPage.backToMetricsStep() + reportPage.backToMetricsStep(); - checkFieldsExist(metricsTextList) - checkPipelineSettingsAutoCompleteFields(pipelineSettingsAutoCompleteTextList) - checkCycleTimeSettingsAutoCompleteFields(cycleTimeSettingsAutoCompleteTextList) + checkFieldsExist(metricsTextList); + checkPipelineSettingsAutoCompleteFields(pipelineSettingsAutoCompleteTextList); + checkCycleTimeSettingsAutoCompleteFields(cycleTimeSettingsAutoCompleteTextList); // checkpoint back to config step - metricsPage.BackToConfigStep() + metricsPage.BackToConfigStep(); - checkFieldsExist(configTextList) - checkTextInputValuesExist(textInputValues) - checkTokenInputValuesExist(tokenInputValues) - }) -}) + checkFieldsExist(configTextList); + checkTextInputValuesExist(textInputValues); + checkTokenInputValuesExist(tokenInputValues); + }); +}); diff --git a/frontend/cypress/e2e/importAProject.cy.ts b/frontend/cypress/e2e/importAProject.cy.ts index f70122dc44..f4e64a863f 100644 --- a/frontend/cypress/e2e/importAProject.cy.ts +++ b/frontend/cypress/e2e/importAProject.cy.ts @@ -1,9 +1,9 @@ -import homePage from '../pages/home' -import configPage from '../pages/metrics/config' -import metricsPage from '../pages/metrics/metrics' -import reportPage from '../pages/metrics/report' -import { GITHUB_TOKEN } from '../fixtures/fixtures' -import { Metrics } from '../pages/metrics/metrics' +import homePage from '../pages/home'; +import configPage from '../pages/metrics/config'; +import metricsPage from '../pages/metrics/metrics'; +import reportPage from '../pages/metrics/report'; +import { GITHUB_TOKEN } from '../fixtures/fixtures'; +import { Metrics } from '../pages/metrics/metrics'; const metricsTextList = [ 'Board configuration', @@ -21,12 +21,12 @@ const metricsTextList = [ 'FS R&D Classification', 'Parent', 'Pipeline settings', -] +]; const pipelineSettingsAutoCompleteTextList = [ { name: 'Organization', value: 'XXXX' }, { name: 'Step', value: 'publish gradle-cache image to cloudsmith' }, -] +]; const cycleTimeSettingsAutoCompleteTextList = [ { name: 'In Analysis', value: 'Analysis' }, @@ -37,7 +37,7 @@ const cycleTimeSettingsAutoCompleteTextList = [ { name: 'In Test', value: 'Testing' }, { name: 'Ready to Deploy', value: 'Review' }, { name: 'Done', value: 'Done' }, -] +]; const configTextList = [ 'Project name *', @@ -45,7 +45,7 @@ const configTextList = [ 'Classic Jira', 'BuildKite', 'GitHub', -] +]; const textInputValues = [ { index: 0, value: 'ConfigFileForImporting' }, @@ -55,84 +55,84 @@ const textInputValues = [ { index: 4, value: 'test@test.com' }, { index: 5, value: 'PLL' }, { index: 6, value: 'mockSite' }, -] +]; const tokenInputValues = [ { index: 0, value: 'mockToken' }, { index: 1, value: 'mockToken' }, { index: 2, value: `${GITHUB_TOKEN}` }, -] +]; const checkFieldsExist = (fields: string[]) => { fields.forEach((item) => { - cy.contains(item).should('exist') - }) -} + cy.contains(item).should('exist'); + }); +}; const checkPipelineSettingsAutoCompleteFields = (fields: { name: string; value: string }[]) => { fields.forEach((item) => { - metricsPage.getPipelineSettingsAutoCompleteField(item.name).find('input').should('have.value', item.value) - }) -} + metricsPage.getPipelineSettingsAutoCompleteField(item.name).find('input').should('have.value', item.value); + }); +}; const checkCycleTimeSettingsAutoCompleteFields = (fields: { name: string; value: string }[]) => { fields.forEach((item) => { - metricsPage.getCycleTimeSettingsAutoCompleteField(item.name).find('input').should('have.value', item.value) - }) -} + metricsPage.getCycleTimeSettingsAutoCompleteField(item.name).find('input').should('have.value', item.value); + }); +}; const checkTextInputValuesExist = (fields: { index: number; value: string }[]) => { fields.forEach(({ index, value }) => { - cy.get('.MuiInputBase-root input[type="text"]').eq(index).should('have.value', value) - }) -} + cy.get('.MuiInputBase-root input[type="text"]').eq(index).should('have.value', value); + }); +}; const checkTokenInputValuesExist = (fields: { index: number; value: string }[]) => { fields.forEach(({ index, value }) => { - cy.get('[type="password"]').eq(index).should('have.value', value) - }) -} + cy.get('[type="password"]').eq(index).should('have.value', value); + }); +}; const checkMeanTimeToRecovery = () => { - reportPage.meanTimeToRecoveryTitle.should('exist') -} + reportPage.meanTimeToRecoveryTitle.should('exist'); +}; const checkPipelineToolExist = () => { - cy.contains('Pipeline Tool').should('exist') -} + cy.contains('Pipeline Tool').should('exist'); +}; const checkInputValue = (selector, expectedValue) => { cy.get(selector) .invoke('val') .then((value) => { - expect(value).to.equal(expectedValue) - }) -} + expect(value).to.equal(expectedValue); + }); +}; const checkRequiredFields = () => { - metricsPage.chooseDropdownOption(Metrics.CYCLE_TIME_LABEL.doneLabel, Metrics.CYCLE_TIME_VALUE.noneValue) - metricsPage.nextButton.should('be.disabled') - metricsPage.chooseDropdownOption(Metrics.CYCLE_TIME_LABEL.doneLabel, Metrics.CYCLE_TIME_VALUE.doneValue) - metricsPage.clickRealDone() - metricsPage.nextButton.should('be.enabled') - - metricsPage.classificationClear.click({ force: true }) - metricsPage.nextButton.should('be.disabled') - metricsPage.clickClassification() - metricsPage.nextButton.should('be.enabled') -} + metricsPage.chooseDropdownOption(Metrics.CYCLE_TIME_LABEL.doneLabel, Metrics.CYCLE_TIME_VALUE.noneValue); + metricsPage.nextButton.should('be.disabled'); + metricsPage.chooseDropdownOption(Metrics.CYCLE_TIME_LABEL.doneLabel, Metrics.CYCLE_TIME_VALUE.doneValue); + metricsPage.clickRealDone(); + metricsPage.nextButton.should('be.enabled'); + + metricsPage.classificationClear.click({ force: true }); + metricsPage.nextButton.should('be.disabled'); + metricsPage.clickClassification(); + metricsPage.nextButton.should('be.enabled'); +}; const checkProjectConfig = () => { - cy.wait(2000) + cy.wait(2000); cy.fixture('config.json').then((localFileContent) => { cy.readFile('cypress/downloads/config.json').then((fileContent) => { - expect(fileContent.sourceControl.token).to.eq(GITHUB_TOKEN) + expect(fileContent.sourceControl.token).to.eq(GITHUB_TOKEN); for (const key in localFileContent) { - expect(fileContent[key]).to.deep.eq(localFileContent[key]) + expect(fileContent[key]).to.deep.eq(localFileContent[key]); } - }) - }) -} + }); + }); +}; describe('Import project from file', () => { beforeEach(() => { @@ -140,88 +140,88 @@ describe('Import project from file', () => { method: '*', pattern: '/api/**', alias: 'api', - }) - }) + }); + }); it('Should import a new config project manually', () => { - homePage.navigate() + homePage.navigate(); - homePage.importProjectFromFile('NewConfigFileForImporting.json') - cy.url().should('include', '/metrics') - checkPipelineToolExist() - checkInputValue('.MuiInput-input', 'ConfigFileForImporting') + homePage.importProjectFromFile('NewConfigFileForImporting.json'); + cy.url().should('include', '/metrics'); + checkPipelineToolExist(); + checkInputValue('.MuiInput-input', 'ConfigFileForImporting'); - cy.waitForNetworkIdle('@api', 2000) - configPage.verifyAndClickNextToMetrics() + cy.waitForNetworkIdle('@api', 2000); + configPage.verifyAndClickNextToMetrics(); - configPage.goMetricsStep() + configPage.goMetricsStep(); - checkFieldsExist(metricsTextList) - checkPipelineSettingsAutoCompleteFields(pipelineSettingsAutoCompleteTextList) - checkCycleTimeSettingsAutoCompleteFields(cycleTimeSettingsAutoCompleteTextList) + checkFieldsExist(metricsTextList); + checkPipelineSettingsAutoCompleteFields(pipelineSettingsAutoCompleteTextList); + checkCycleTimeSettingsAutoCompleteFields(cycleTimeSettingsAutoCompleteTextList); - checkRequiredFields() + checkRequiredFields(); - metricsPage.goReportStep() + metricsPage.goReportStep(); - reportPage.pageIndicator.should('exist') + reportPage.pageIndicator.should('exist'); - checkMeanTimeToRecovery() + checkMeanTimeToRecovery(); - reportPage.exportProjectConfig() + reportPage.exportProjectConfig(); - checkProjectConfig() + checkProjectConfig(); - reportPage.backToMetricsStep() + reportPage.backToMetricsStep(); - checkFieldsExist(metricsTextList) - checkPipelineSettingsAutoCompleteFields(pipelineSettingsAutoCompleteTextList) - checkCycleTimeSettingsAutoCompleteFields(cycleTimeSettingsAutoCompleteTextList) + checkFieldsExist(metricsTextList); + checkPipelineSettingsAutoCompleteFields(pipelineSettingsAutoCompleteTextList); + checkCycleTimeSettingsAutoCompleteFields(cycleTimeSettingsAutoCompleteTextList); - metricsPage.BackToConfigStep() + metricsPage.BackToConfigStep(); - checkFieldsExist(configTextList) + checkFieldsExist(configTextList); - checkTextInputValuesExist(textInputValues) + checkTextInputValuesExist(textInputValues); - checkTokenInputValuesExist(tokenInputValues) - }) + checkTokenInputValuesExist(tokenInputValues); + }); it('Should import a old config project manually', () => { - homePage.navigate() + homePage.navigate(); - homePage.importProjectFromFile('OldConfigFileForImporting.json') - cy.url().should('include', '/metrics') - checkPipelineToolExist() - checkInputValue('.MuiInput-input', 'ConfigFileForImporting') + homePage.importProjectFromFile('OldConfigFileForImporting.json'); + cy.url().should('include', '/metrics'); + checkPipelineToolExist(); + checkInputValue('.MuiInput-input', 'ConfigFileForImporting'); - cy.waitForNetworkIdle('@api', 2000) - configPage.verifyAndClickNextToMetrics() + cy.waitForNetworkIdle('@api', 2000); + configPage.verifyAndClickNextToMetrics(); - configPage.goMetricsStep() + configPage.goMetricsStep(); - checkFieldsExist(metricsTextList) - checkPipelineSettingsAutoCompleteFields(pipelineSettingsAutoCompleteTextList) - checkCycleTimeSettingsAutoCompleteFields(cycleTimeSettingsAutoCompleteTextList) + checkFieldsExist(metricsTextList); + checkPipelineSettingsAutoCompleteFields(pipelineSettingsAutoCompleteTextList); + checkCycleTimeSettingsAutoCompleteFields(cycleTimeSettingsAutoCompleteTextList); - metricsPage.goReportStep() + metricsPage.goReportStep(); - reportPage.pageIndicator.should('exist') + reportPage.pageIndicator.should('exist'); - checkMeanTimeToRecovery() + checkMeanTimeToRecovery(); - reportPage.backToMetricsStep() + reportPage.backToMetricsStep(); - checkFieldsExist(metricsTextList) - checkPipelineSettingsAutoCompleteFields(pipelineSettingsAutoCompleteTextList) - checkCycleTimeSettingsAutoCompleteFields(cycleTimeSettingsAutoCompleteTextList) + checkFieldsExist(metricsTextList); + checkPipelineSettingsAutoCompleteFields(pipelineSettingsAutoCompleteTextList); + checkCycleTimeSettingsAutoCompleteFields(cycleTimeSettingsAutoCompleteTextList); - metricsPage.BackToConfigStep() + metricsPage.BackToConfigStep(); - checkFieldsExist(configTextList) + checkFieldsExist(configTextList); - checkTextInputValuesExist(textInputValues) + checkTextInputValuesExist(textInputValues); - checkTokenInputValuesExist(tokenInputValues) - }) -}) + checkTokenInputValuesExist(tokenInputValues); + }); +}); diff --git a/frontend/cypress/fixtures/fixtures.ts b/frontend/cypress/fixtures/fixtures.ts index b538d0f724..a5fae2d818 100644 --- a/frontend/cypress/fixtures/fixtures.ts +++ b/frontend/cypress/fixtures/fixtures.ts @@ -1,9 +1,9 @@ -export const MOCK_EMAIL = 'test@test.com' -export const BOARD_TOKEN = 'mockToken' -export const WEB_SITE = 'https://url.com' -export const BOARD_PROJECT_KEY = 'mockProjectKey' +export const MOCK_EMAIL = 'test@test.com'; +export const BOARD_TOKEN = 'mockToken'; +export const WEB_SITE = 'https://url.com'; +export const BOARD_PROJECT_KEY = 'mockProjectKey'; -export const GITHUB_TOKEN = `ghp_${'Abc123'.repeat(6)}` +export const GITHUB_TOKEN = `ghp_${'Abc123'.repeat(6)}`; export enum METRICS_TITLE { VELOCITY = 'Velocity', diff --git a/frontend/cypress/pages/home.ts b/frontend/cypress/pages/home.ts index f7b713bb4c..5daaa0c4d8 100644 --- a/frontend/cypress/pages/home.ts +++ b/frontend/cypress/pages/home.ts @@ -1,46 +1,46 @@ -import { GITHUB_TOKEN } from '../fixtures/fixtures' +import { GITHUB_TOKEN } from '../fixtures/fixtures'; class Home { get createANewProjectButton() { - return cy.contains('Create a new project') + return cy.contains('Create a new project'); } get importProjectFromFileButton() { - return cy.contains('Import project from file') + return cy.contains('Import project from file'); } get headerVersion() { - return cy.get('span[title="Heartbeat"]').parent().next() + return cy.get('span[title="Heartbeat"]').parent().next(); } navigate() { - cy.visit('/index.html') + cy.visit('/index.html'); } createANewProject() { - this.createANewProjectButton.click() + this.createANewProjectButton.click(); } importProjectFromFile(configFixtureName) { - this.importProjectFromFileButton.click() + this.importProjectFromFileButton.click(); cy.fixture(configFixtureName).then((fileContent) => { // Add Randomly generated token - fileContent.sourceControl.token = GITHUB_TOKEN + fileContent.sourceControl.token = GITHUB_TOKEN; cy.get('#importJson').then((e) => { const testFile = new File([JSON.stringify(fileContent)], configFixtureName, { type: 'application/json', - }) - const dataTransfer = new DataTransfer() - dataTransfer.items.add(testFile) - const input = e[0] - input.files = dataTransfer.files - cy.wrap(input).trigger('change', { force: true }) - }) - }) + }); + const dataTransfer = new DataTransfer(); + dataTransfer.items.add(testFile); + const input = e[0]; + input.files = dataTransfer.files; + cy.wrap(input).trigger('change', { force: true }); + }); + }); } } -const homePage = new Home() +const homePage = new Home(); -export default homePage +export default homePage; diff --git a/frontend/cypress/pages/metrics/config.ts b/frontend/cypress/pages/metrics/config.ts index 4b209da4d2..2607d088f1 100644 --- a/frontend/cypress/pages/metrics/config.ts +++ b/frontend/cypress/pages/metrics/config.ts @@ -1,139 +1,139 @@ class Config { get backButton() { - return cy.contains('button', 'Previous') + return cy.contains('button', 'Previous'); } get yesButton() { - return cy.contains('button', 'Yes') + return cy.contains('button', 'Yes'); } get cancelButton() { - return cy.contains('button', 'Cancel') + return cy.contains('button', 'Cancel'); } get projectNameInput() { - return this.basicInformationConfigSection.contains('label', 'Project name').parent() + return this.basicInformationConfigSection.contains('label', 'Project name').parent(); } get collectionDateFrom() { - return this.basicInformationConfigSection.contains('label', 'From').parent() + return this.basicInformationConfigSection.contains('label', 'From').parent(); } get requiredDataSelect() { - return this.basicInformationConfigSection.contains('label', 'Required metrics').parent() + return this.basicInformationConfigSection.contains('label', 'Required metrics').parent(); } get requiredDataAllSelectOption() { - return cy.contains('All') + return cy.contains('All'); } get requiredDataModelCloseElement() { - return cy.get('div.MuiBackdrop-root.MuiBackdrop-invisible.MuiModal-backdrop') + return cy.get('div.MuiBackdrop-root.MuiBackdrop-invisible.MuiModal-backdrop'); } get boardInfoSelectionJira() { - return cy.contains('Jira') + return cy.contains('Jira'); } get boardInfoSelectionClassicJira() { - return cy.contains('Classic Jira') + return cy.contains('Classic Jira'); } get boardInfoBoardIdInput() { - return this.boardConfigSection.contains('label', 'Board Id').parent() + return this.boardConfigSection.contains('label', 'Board Id').parent(); } get boardInfoEmailInput() { - return this.boardConfigSection.contains('label', 'Email').parent() + return this.boardConfigSection.contains('label', 'Email').parent(); } get boardInfoProjectKeyInput() { - return this.boardConfigSection.contains('label', 'Project Key').parent() + return this.boardConfigSection.contains('label', 'Project Key').parent(); } get boardInfoSiteInput() { - return this.boardConfigSection.contains('label', 'Site').parent() + return this.boardConfigSection.contains('label', 'Site').parent(); } get boardInfoTokenInput() { - return this.boardConfigSection.contains('label', 'Token').parent() + return this.boardConfigSection.contains('label', 'Token').parent(); } get basicInformationConfigSection() { - return cy.get('[aria-label="Basic information"]') + return cy.get('[aria-label="Basic information"]'); } get boardConfigSection() { - return cy.get('[aria-label="Board Config"]') + return cy.get('[aria-label="Board Config"]'); } get pipelineToolConfigSection() { - return cy.get('[aria-label="Pipeline Tool Config"]') + return cy.get('[aria-label="Pipeline Tool Config"]'); } get sourceControlConfigSection() { - return cy.get('[aria-label="Source Control Config"]') + return cy.get('[aria-label="Source Control Config"]'); } get nextStepButton() { - return cy.contains('button', 'Next') + return cy.contains('button', 'Next'); } get boardVerifyButton() { - return this.getVerifyButton(this.boardConfigSection) + return this.getVerifyButton(this.boardConfigSection); } get pipelineToolTokenInput() { - return this.pipelineToolConfigSection.contains('label', 'Token').parent() + return this.pipelineToolConfigSection.contains('label', 'Token').parent(); } get pipelineToolVerifyButton() { - return this.getVerifyButton(this.pipelineToolConfigSection) + return this.getVerifyButton(this.pipelineToolConfigSection); } get sourceControlTokenInput() { - return this.sourceControlConfigSection.contains('label', 'Token').parent() + return this.sourceControlConfigSection.contains('label', 'Token').parent(); } get sourceControlVerifyButton() { - return this.getVerifyButton(this.sourceControlConfigSection) + return this.getVerifyButton(this.sourceControlConfigSection); } getVerifyButton(section: Cypress.Chainable) { - return section.contains('button', 'Verify') + return section.contains('button', 'Verify'); } getVerifiedButton(section: Cypress.Chainable) { - return section.contains('button', 'Verified') + return section.contains('button', 'Verified'); } getResetButton(section: Cypress.Chainable) { - return section.contains('button', 'Reset') + return section.contains('button', 'Reset'); } navigate() { - cy.visit(Cypress.env('url') + '/metrics') + cy.visit(Cypress.env('url') + '/metrics'); } goHomePage() { - this.backButton.click() - this.yesButton.click() + this.backButton.click(); + this.yesButton.click(); } typeProjectName(projectName: string) { - this.projectNameInput.type(projectName) + this.projectNameInput.type(projectName); } selectDateRange() { - this.collectionDateFrom.type('09012022') + this.collectionDateFrom.type('09012022'); } selectMetricsData() { - this.requiredDataSelect.click() + this.requiredDataSelect.click(); - this.requiredDataAllSelectOption.click() + this.requiredDataAllSelectOption.click(); - this.requiredDataModelCloseElement.click({ force: true }) + this.requiredDataModelCloseElement.click({ force: true }); } fillBoardInfoAndVerifyWithClassicJira( @@ -143,44 +143,44 @@ class Config { site: string, token: string ) { - this.boardInfoSelectionJira.click() - this.boardInfoSelectionClassicJira.click() + this.boardInfoSelectionJira.click(); + this.boardInfoSelectionClassicJira.click(); - this.boardInfoBoardIdInput.type(boardId) - this.boardInfoEmailInput.type(email) - this.boardInfoProjectKeyInput.type(projectKey) - this.boardInfoSiteInput.type(site) - this.boardInfoTokenInput.type(token) - this.getVerifyButton(this.boardConfigSection).click() + this.boardInfoBoardIdInput.type(boardId); + this.boardInfoEmailInput.type(email); + this.boardInfoProjectKeyInput.type(projectKey); + this.boardInfoSiteInput.type(site); + this.boardInfoTokenInput.type(token); + this.getVerifyButton(this.boardConfigSection).click(); } fillPipelineToolFieldsInfoAndVerify(token: string) { - this.pipelineToolTokenInput.type(token) + this.pipelineToolTokenInput.type(token); - this.pipelineToolVerifyButton.click() + this.pipelineToolVerifyButton.click(); } fillSourceControlFieldsInfoAndVerify(token: string) { - this.sourceControlTokenInput.type(token) + this.sourceControlTokenInput.type(token); - this.sourceControlVerifyButton.click() + this.sourceControlVerifyButton.click(); } verifyAndClickNextToMetrics() { - this.boardVerifyButton.click() - this.pipelineToolVerifyButton.click() - this.sourceControlVerifyButton.click() + this.boardVerifyButton.click(); + this.pipelineToolVerifyButton.click(); + this.sourceControlVerifyButton.click(); } CancelBackToHomePage() { - this.backButton.click() - this.cancelButton.click() + this.backButton.click(); + this.cancelButton.click(); } goMetricsStep() { - this.nextStepButton.click() + this.nextStepButton.click(); } } -const configPage = new Config() -export default configPage +const configPage = new Config(); +export default configPage; diff --git a/frontend/cypress/pages/metrics/metrics.ts b/frontend/cypress/pages/metrics/metrics.ts index f670dbd648..eec8632dee 100644 --- a/frontend/cypress/pages/metrics/metrics.ts +++ b/frontend/cypress/pages/metrics/metrics.ts @@ -8,7 +8,7 @@ export class Metrics { testingLabel: 'In Test', reviewLabel: 'Ready to Deploy', doneLabel: 'Done', - } + }; static CYCLE_TIME_VALUE = { analysisValue: 'Analysis', todoValue: 'To do', @@ -19,180 +19,180 @@ export class Metrics { reviewValue: 'Review', doneValue: 'Done', noneValue: '----', - } + }; get realDoneSelect() { - return cy.contains('label', 'Consider as Done').parent() + return cy.contains('label', 'Consider as Done').parent(); } get RealDoneSelectAllOption() { - return cy.contains('All') + return cy.contains('All'); } get closeModelElement() { return cy.get( '.Mui-expanded > .MuiFormControl-root > .MuiInputBase-root > .MuiAutocomplete-endAdornment > .MuiAutocomplete-popupIndicator > [data-testid="ArrowDropDownIcon"] > path' - ) + ); } get classificationSelect() { - return cy.contains('label', 'Distinguished By').parent() + return cy.contains('label', 'Distinguished By').parent(); } get classificationSelectAllOption() { - return cy.contains('All') + return cy.contains('All'); } get pipelineSettingTitle() { - return cy.contains('Pipeline settings') + return cy.contains('Pipeline settings'); } get pipelineOfOrgXXXX() { - return cy.get('li[role="option"]').contains('XXXX') + return cy.get('li[role="option"]').contains('XXXX'); } get pipelineSelectOneOption() { - return cy.get('li[role="option"]').contains('fs-platform-payment-selector') + return cy.get('li[role="option"]').contains('fs-platform-payment-selector'); } get stepSelectSomeOption() { - return cy.get('li[role="option"]').contains('RECORD RELEASE TO PROD') + return cy.get('li[role="option"]').contains('RECORD RELEASE TO PROD'); } get addOnePipelineButton() { - return cy.get('[data-testid="AddIcon"]:first') + return cy.get('[data-testid="AddIcon"]:first'); } get classificationClear() { - return this.classificationSelect.find('[aria-label="Clear"]') + return this.classificationSelect.find('[aria-label="Clear"]'); } get pipelineSelectOnboardingOption() { - return cy.get('[data-test-id="single-selection-pipeline-name"]:contains("fs-platform-onboarding")') + return cy.get('[data-test-id="single-selection-pipeline-name"]:contains("fs-platform-onboarding")'); } get pipelineSelectUIOption() { - return cy.get('li[role="option"]').contains('payment-selector-ui') + return cy.get('li[role="option"]').contains('payment-selector-ui'); } get buildKiteStepNotFoundTips() { - return cy.contains('BuildKite get steps failed: 404 Not Found') + return cy.contains('BuildKite get steps failed: 404 Not Found'); } get pipelineRemoveButton() { - return cy.get('[data-test-id="remove-button"]').eq(1) + return cy.get('[data-test-id="remove-button"]').eq(1); } get branchSelect() { - return cy.contains('Branches').eq(0).siblings() + return cy.contains('Branches').eq(0).siblings(); } get branchSelectSomeOption() { - return cy.contains('All') + return cy.contains('All'); } get pipelineStepSelectXXOption() { - return cy.get('[data-test-id="single-selection-step"]:contains("RECORD RELEASE TO UAT"):last') + return cy.get('[data-test-id="single-selection-step"]:contains("RECORD RELEASE TO UAT"):last'); } get pipelineStepXXOption() { - return cy.get('[data-test-id="single-selection-pipeline-name"]:contains("payment-selector-ui")') + return cy.get('[data-test-id="single-selection-pipeline-name"]:contains("payment-selector-ui")'); } get leadTimeForChangeAddOneButton() { - return cy.get('[data-testid="AddIcon"]:last') + return cy.get('[data-testid="AddIcon"]:last'); } get headerBar() { - return cy.get('[data-test-id="Header"]') + return cy.get('[data-test-id="Header"]'); } get nextButton() { - return cy.contains('Next') + return cy.contains('Next'); } get backButton() { - return cy.contains('Previous') + return cy.contains('Previous'); } get cycleTimeTitleTooltip() { - return cy.get('[data-test-id="tooltip') + return cy.get('[data-test-id="tooltip'); } chooseDropdownOption = (label: string, value: string) => { - this.getCycleTimeSettingsAutoCompleteField(label).click() - cy.get('li[role="option"]').contains(value).click() - } + this.getCycleTimeSettingsAutoCompleteField(label).click(); + cy.get('li[role="option"]').contains(value).click(); + }; - getStepOfSomePipelineSelect = (i: number) => cy.get('[data-test-id="single-selection-step"]').eq(i) + getStepOfSomePipelineSelect = (i: number) => cy.get('[data-test-id="single-selection-step"]').eq(i); - getOrganizationSelect = (i: number) => cy.get('[data-test-id="single-selection-organization"]').eq(i) + getOrganizationSelect = (i: number) => cy.get('[data-test-id="single-selection-organization"]').eq(i); - getPipelineSelect = (i: number) => cy.get('[data-test-id="single-selection-pipeline-name"]').eq(i) - getCycleTimeSettingsAutoCompleteField = (name: string) => cy.get(`[aria-label="Cycle time select for ${name}"]`) - getPipelineSettingsAutoCompleteField = (name: string) => cy.contains(name).siblings().eq(0) + getPipelineSelect = (i: number) => cy.get('[data-test-id="single-selection-pipeline-name"]').eq(i); + getCycleTimeSettingsAutoCompleteField = (name: string) => cy.get(`[aria-label="Cycle time select for ${name}"]`); + getPipelineSettingsAutoCompleteField = (name: string) => cy.contains(name).siblings().eq(0); checkCycleTime() { - this.chooseDropdownOption(Metrics.CYCLE_TIME_LABEL.analysisLabel, Metrics.CYCLE_TIME_VALUE.analysisValue) - this.chooseDropdownOption(Metrics.CYCLE_TIME_LABEL.todoLabel, Metrics.CYCLE_TIME_VALUE.todoValue) - this.chooseDropdownOption(Metrics.CYCLE_TIME_LABEL.inDevLabel, Metrics.CYCLE_TIME_VALUE.inDevValue) - this.chooseDropdownOption(Metrics.CYCLE_TIME_LABEL.blockLabel, Metrics.CYCLE_TIME_VALUE.blockValue) - this.chooseDropdownOption(Metrics.CYCLE_TIME_LABEL.waitingLabel, Metrics.CYCLE_TIME_VALUE.waitingValue) - this.chooseDropdownOption(Metrics.CYCLE_TIME_LABEL.testingLabel, Metrics.CYCLE_TIME_VALUE.testingValue) - this.chooseDropdownOption(Metrics.CYCLE_TIME_LABEL.reviewLabel, Metrics.CYCLE_TIME_VALUE.reviewValue) - this.chooseDropdownOption(Metrics.CYCLE_TIME_LABEL.doneLabel, Metrics.CYCLE_TIME_VALUE.doneValue) + this.chooseDropdownOption(Metrics.CYCLE_TIME_LABEL.analysisLabel, Metrics.CYCLE_TIME_VALUE.analysisValue); + this.chooseDropdownOption(Metrics.CYCLE_TIME_LABEL.todoLabel, Metrics.CYCLE_TIME_VALUE.todoValue); + this.chooseDropdownOption(Metrics.CYCLE_TIME_LABEL.inDevLabel, Metrics.CYCLE_TIME_VALUE.inDevValue); + this.chooseDropdownOption(Metrics.CYCLE_TIME_LABEL.blockLabel, Metrics.CYCLE_TIME_VALUE.blockValue); + this.chooseDropdownOption(Metrics.CYCLE_TIME_LABEL.waitingLabel, Metrics.CYCLE_TIME_VALUE.waitingValue); + this.chooseDropdownOption(Metrics.CYCLE_TIME_LABEL.testingLabel, Metrics.CYCLE_TIME_VALUE.testingValue); + this.chooseDropdownOption(Metrics.CYCLE_TIME_LABEL.reviewLabel, Metrics.CYCLE_TIME_VALUE.reviewValue); + this.chooseDropdownOption(Metrics.CYCLE_TIME_LABEL.doneLabel, Metrics.CYCLE_TIME_VALUE.doneValue); } clickRealDone() { - this.realDoneSelect.click() + this.realDoneSelect.click(); - this.RealDoneSelectAllOption.click() - this.closeModelElement.click({ force: true }) + this.RealDoneSelectAllOption.click(); + this.closeModelElement.click({ force: true }); } clickClassification() { - this.classificationSelect.click() + this.classificationSelect.click(); - this.classificationSelectAllOption.click() - this.closeModelElement.click({ force: true }) + this.classificationSelectAllOption.click(); + this.closeModelElement.click({ force: true }); } addOneCorrectPipelineConfig(position = 0) { - this.getOrganizationSelect(position).click() - this.pipelineOfOrgXXXX.click() - this.getPipelineSelect(position).click() - cy.waitForNetworkIdle('@api', 2000) - this.pipelineSelectOneOption.click() - this.getStepOfSomePipelineSelect(position).click() - this.stepSelectSomeOption.click() + this.getOrganizationSelect(position).click(); + this.pipelineOfOrgXXXX.click(); + this.getPipelineSelect(position).click(); + cy.waitForNetworkIdle('@api', 2000); + this.pipelineSelectOneOption.click(); + this.getStepOfSomePipelineSelect(position).click(); + this.stepSelectSomeOption.click(); } selectBranchOption() { - this.branchSelect.click() - this.branchSelectSomeOption.click() - this.closeOptions() + this.branchSelect.click(); + this.branchSelectSomeOption.click(); + this.closeOptions(); } addOneErrorPipelineConfig(position = 0) { - this.getOrganizationSelect(position).click() - this.pipelineOfOrgXXXX.click() - this.getPipelineSelect(position).click() - cy.waitForNetworkIdle('@api', 2000) - this.pipelineSelectUIOption.click() + this.getOrganizationSelect(position).click(); + this.pipelineOfOrgXXXX.click(); + this.getPipelineSelect(position).click(); + cy.waitForNetworkIdle('@api', 2000); + this.pipelineSelectUIOption.click(); } closeOptions() { - this.headerBar.click() + this.headerBar.click(); } goReportStep() { - this.nextButton.click() + this.nextButton.click(); } BackToConfigStep() { - this.backButton.click() + this.backButton.click(); } } -const metricsPage = new Metrics() -export default metricsPage +const metricsPage = new Metrics(); +export default metricsPage; diff --git a/frontend/cypress/pages/metrics/report.ts b/frontend/cypress/pages/metrics/report.ts index c0085891af..42ce6dfe76 100644 --- a/frontend/cypress/pages/metrics/report.ts +++ b/frontend/cypress/pages/metrics/report.ts @@ -1,56 +1,84 @@ class Report { get pageIndicator() { - return cy.get('[data-test-id="report-section"]', { timeout: 60000 }) + return cy.get('[data-test-id="report-section"]', { timeout: 60000 }); } get meanTimeToRecoveryTitle() { - return cy.contains('Mean Time To Recovery') + return cy.contains('Mean Time To Recovery'); } get backButton() { - return cy.contains('Previous') + return cy.contains('Previous'); } get saveButton() { - return cy.contains('Save') + return cy.contains('Save'); } get exportMetricDataButton() { - return cy.contains('Export metric data') + return cy.contains('Export metric data'); } get exportPipelineDataButton() { - return cy.contains('Export pipeline data') + return cy.contains('Export pipeline data'); } get exportBoardDataButton() { - return cy.contains('Export board data') + return cy.contains('Export board data'); } get firstNotification() { - return cy.contains('The file needs to be exported within 30 minutes, otherwise it will expire.') + return cy.contains('The file needs to be exported within 30 minutes, otherwise it will expire.'); + } + + get showMoreBoardButton() { + return cy.contains('Board Metrics').parent().siblings().eq(0); + } + + get showMoreDoraButton() { + return cy.contains('DORA Metrics').parent().siblings().eq(0); + } + + get topBackButton() { + return cy.contains('Back'); + } + + boardGoToReportPage() { + this.topBackButton.click(); + } + + doraGoToReportPage() { + this.backButton.click(); + } + + goToBoardDetailPage() { + this.showMoreBoardButton.click(); + } + + goToDoraDetailPage() { + this.showMoreDoraButton.click(); } backToMetricsStep() { - this.backButton.click({ force: true }) + this.backButton.click({ force: true }); } exportMetricData() { - this.exportMetricDataButton.click() + this.exportMetricDataButton.click(); } exportPipelineData() { - this.exportPipelineDataButton.click() + this.exportPipelineDataButton.click(); } exportBoardData() { - this.exportBoardDataButton.click() + this.exportBoardDataButton.click(); } exportProjectConfig() { - this.saveButton.click({ force: true }) + this.saveButton.click({ force: true }); } } -const reportPage = new Report() -export default reportPage +const reportPage = new Report(); +export default reportPage; diff --git a/frontend/cypress/plugins/clearDownloadFile.ts b/frontend/cypress/plugins/clearDownloadFile.ts index d013968649..6c7b665bc1 100644 --- a/frontend/cypress/plugins/clearDownloadFile.ts +++ b/frontend/cypress/plugins/clearDownloadFile.ts @@ -1,24 +1,24 @@ // -import fs = require('fs') +import fs = require('fs'); -module.exports = (on, config) => { +module.exports = (on) => { on('task', { clearDownloads: () => { - const downloadsFolder = 'cypress/downloads' - clearDownloadsFolder(downloadsFolder) - return null + const downloadsFolder = 'cypress/downloads'; + clearDownloadsFolder(downloadsFolder); + return null; }, - }) -} + }); +}; function clearDownloadsFolder(folder) { try { fs.readdirSync(folder).forEach((file) => { - const filePath = `${folder}/${file}` - fs.unlinkSync(filePath) - console.log(`Deleted: ${filePath}`) - }) + const filePath = `${folder}/${file}`; + fs.unlinkSync(filePath); + console.log(`Deleted: ${filePath}`); + }); } catch (error) { - console.error('Error clearing downloads folder:', error) + console.error('Error clearing downloads folder:', error); } } diff --git a/frontend/cypress/plugins/readDir.ts b/frontend/cypress/plugins/readDir.ts index 01f3c4768c..90fba84308 100644 --- a/frontend/cypress/plugins/readDir.ts +++ b/frontend/cypress/plugins/readDir.ts @@ -1,5 +1,5 @@ // -import fs = require('fs') +import fs = require('fs'); module.exports = (on) => { on('task', { @@ -7,12 +7,12 @@ module.exports = (on) => { return new Promise((resolve, reject) => { fs.readdir(path, (err, files) => { if (err) { - reject(err) + reject(err); } else { - resolve(files) + resolve(files); } - }) - }) + }); + }); }, - }) -} + }); +}; diff --git a/frontend/cypress/support/e2e.ts b/frontend/cypress/support/e2e.ts index a3a96a3a26..5561fe41c4 100644 --- a/frontend/cypress/support/e2e.ts +++ b/frontend/cypress/support/e2e.ts @@ -12,10 +12,10 @@ // You can read more here: // https://on.cypress.io/configuration // *********************************************************** -import 'cypress-mochawesome-reporter/register' -import 'cypress-network-idle' +import 'cypress-mochawesome-reporter/register'; +import 'cypress-network-idle'; // Import commands.js using ES2015 syntax: -import './commands' +import './commands'; // Alternatively you can use CommonJS syntax: // require('./commands') diff --git a/frontend/global-setup.ts b/frontend/global-setup.ts index 859542ed27..5a75a6f367 100644 --- a/frontend/global-setup.ts +++ b/frontend/global-setup.ts @@ -1,5 +1,5 @@ -export {} +export {}; module.exports = async () => { - process.env.TZ = 'PRC' -} + process.env.TZ = 'PRC'; +}; diff --git a/frontend/scripts/runE2eWithServer.ts b/frontend/scripts/runE2eWithServer.ts index b5a7637276..929fa2da06 100644 --- a/frontend/scripts/runE2eWithServer.ts +++ b/frontend/scripts/runE2eWithServer.ts @@ -1,143 +1,143 @@ -import path from 'path' -import process from 'process' -import fetch from 'node-fetch' +import path from 'path'; +import process from 'process'; +import fetch from 'node-fetch'; -const LOCALHOST = 'http://127.0.0.1' -const WAIT_TIMEOUT = 30000 -const WAIT_INTERVAL = 3000 +const LOCALHOST = 'http://127.0.0.1'; +const WAIT_TIMEOUT = 30000; +const WAIT_INTERVAL = 3000; const HEALTH_ENDPOINT = { FRONT_END: `${LOCALHOST}:4321/`, BACK_END: `${LOCALHOST}:4322/api/v1/health`, STUB: `${LOCALHOST}:4323/health`, -} +}; const DIR = { FRONT_END: path.resolve(__dirname, '../'), BACK_END: path.resolve(__dirname, '../../backend'), STUB: path.resolve(__dirname, '../../stubs'), -} +}; const main = async (args: string[]) => { - const { $ } = await import('execa') + const { $ } = await import('execa'); const healthCheck = (url: string) => new Promise((resolve) => fetch(url) .then((response) => { if (response.ok) { - console.log(`Successfully detected service health at ${url}`) - resolve(true) + console.log(`Successfully detected service health at ${url}`); + resolve(true); } else { - resolve(false) + resolve(false); } }) .catch(() => resolve(false)) - ) + ); const checkEndpoints = async () => { - const endpoints = Object.values(HEALTH_ENDPOINT) - const checkEndpoints = endpoints.map((url) => healthCheck(url)) - const checkingResult = await Promise.all(checkEndpoints) - const healthyEndpoints = endpoints.filter((_, idx) => checkingResult[idx]) - const unhealthyEndpoints = endpoints.filter((name) => !healthyEndpoints.includes(name)) + const endpoints = Object.values(HEALTH_ENDPOINT); + const checkEndpoints = endpoints.map((url) => healthCheck(url)); + const checkingResult = await Promise.all(checkEndpoints); + const healthyEndpoints = endpoints.filter((_, idx) => checkingResult[idx]); + const unhealthyEndpoints = endpoints.filter((name) => !healthyEndpoints.includes(name)); return { unhealthyEndpoints, healthyEndpoints, - } - } + }; + }; - const startFE = () => $({ cwd: DIR.FRONT_END, stderr: 'inherit', shell: true })`pnpm run start` + const startFE = () => $({ cwd: DIR.FRONT_END, stderr: 'inherit', shell: true })`pnpm run start`; const startBE = () => $({ cwd: DIR.BACK_END, stderr: 'inherit', shell: true, - })`./gradlew bootRun --args='--spring.profiles.active=local --MOCK_SERVER_URL=${LOCALHOST}:4323'` + })`./gradlew bootRun --args='--spring.profiles.active=local --MOCK_SERVER_URL=${LOCALHOST}:4323'`; const startSTUB = () => $({ cwd: DIR.STUB, stderr: 'inherit', shell: true, - })`docker-compose up` + })`docker-compose up`; const waitForUrl = (url: string) => new Promise((resolve) => { - const startTime = Date.now() + const startTime = Date.now(); const check = () => { healthCheck(url).then((result) => { if (result) { - resolve(true) - return + resolve(true); + return; } - const elapsedTime = Date.now() - startTime + const elapsedTime = Date.now() - startTime; if (elapsedTime < WAIT_TIMEOUT) { - setTimeout(check, WAIT_INTERVAL) + setTimeout(check, WAIT_INTERVAL); } else { - console.error(`Failed to detect service health at ${url}`) - process.exit(1) + console.error(`Failed to detect service health at ${url}`); + process.exit(1); } - }) - } + }); + }; - check() - }) + check(); + }); const startServices = async (unhealthyEndpoints: string[]) => { if (unhealthyEndpoints.length <= 0) { - return [] + return []; } const processes = unhealthyEndpoints.map((name) => { switch (name) { case HEALTH_ENDPOINT.BACK_END: - console.log(`Start to run back-end service`) - return startBE() + console.log(`Start to run back-end service`); + return startBE(); case HEALTH_ENDPOINT.FRONT_END: - console.log(`Start to run front-end service`) - return startFE() + console.log(`Start to run front-end service`); + return startFE(); case HEALTH_ENDPOINT.STUB: - console.log(`Start to run stub service`) - return startSTUB() + console.log(`Start to run stub service`); + return startSTUB(); default: - console.error(`Failed to run service: ${name}`) + console.error(`Failed to run service: ${name}`); } - }) + }); - await Promise.all(unhealthyEndpoints.map((url) => waitForUrl(url))) + await Promise.all(unhealthyEndpoints.map((url) => waitForUrl(url))); - return processes - } + return processes; + }; - const e2eCommand = args[0] + const e2eCommand = args[0]; - console.log('Start to check E2E rely on services') - const { unhealthyEndpoints } = await checkEndpoints() - const newlyStartProcesses = await startServices(unhealthyEndpoints) + console.log('Start to check E2E rely on services'); + const { unhealthyEndpoints } = await checkEndpoints(); + const newlyStartProcesses = await startServices(unhealthyEndpoints); - console.log('Start to run E2E') - const e2eProcess = $({ cwd: DIR.FRONT_END, stdout: 'inherit', stderr: 'inherit', shell: true })`${e2eCommand}` + console.log('Start to run E2E'); + const e2eProcess = $({ cwd: DIR.FRONT_END, stdout: 'inherit', stderr: 'inherit', shell: true })`${e2eCommand}`; e2eProcess.on('close', (code: number, signal: string) => { if (code === 0) { - console.log(`Successfully finish E2E testing. Code: ${code}; signal: ${signal}.`) + console.log(`Successfully finish E2E testing. Code: ${code}; signal: ${signal}.`); } else { - console.error(`Failed to run E2E testing. Code: ${code}; signal: ${signal}.`) + console.error(`Failed to run E2E testing. Code: ${code}; signal: ${signal}.`); } newlyStartProcesses.forEach((pro) => { - console.log(`Start to clean up process ${pro?.pid}`) - pro?.kill() - }) + console.log(`Start to clean up process ${pro?.pid}`); + pro?.kill(); + }); - process.exit(code) - }) -} + process.exit(code); + }); +}; -const [_, __, ...args] = process.argv +const args = process.argv.slice(2); -main(args) +main(args); diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 3df619cca8..4cc4bf0a52 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -1,13 +1,13 @@ -import './App.css' -import { BrowserRouter } from 'react-router-dom' -import Router from './router' -import styled from '@emotion/styled' +import './App.css'; +import { BrowserRouter } from 'react-router-dom'; +import Router from './router'; +import styled from '@emotion/styled'; const AppContainer = styled.div({ display: 'flex', flexDirection: 'column', height: '100%', -}) +}); function App() { return ( @@ -16,7 +16,7 @@ function App() { - ) + ); } -export default App +export default App; diff --git a/frontend/src/clients/Httpclient.ts b/frontend/src/clients/Httpclient.ts index bf2ecaa5f9..e4abd7a14b 100644 --- a/frontend/src/clients/Httpclient.ts +++ b/frontend/src/clients/Httpclient.ts @@ -1,48 +1,48 @@ -import axios, { AxiosInstance, HttpStatusCode } from 'axios' -import { BadRequestException } from '@src/exceptions/BadRequestException' -import { UnauthorizedException } from '@src/exceptions/UnauthorizedException' -import { InternalServerException } from '@src/exceptions/InternalServerException' -import { UnknownException } from '@src/exceptions/UnkonwException' -import { NotFoundException } from '@src/exceptions/NotFoundException' -import { ForbiddenException } from '@src/exceptions/ForbiddenException' -import { TimeoutException } from '@src/exceptions/TimeoutException' +import axios, { AxiosInstance, HttpStatusCode } from 'axios'; +import { BadRequestException } from '@src/exceptions/BadRequestException'; +import { UnauthorizedException } from '@src/exceptions/UnauthorizedException'; +import { InternalServerException } from '@src/exceptions/InternalServerException'; +import { UnknownException } from '@src/exceptions/UnkonwException'; +import { NotFoundException } from '@src/exceptions/NotFoundException'; +import { ForbiddenException } from '@src/exceptions/ForbiddenException'; +import { TimeoutException } from '@src/exceptions/TimeoutException'; export class HttpClient { - protected httpTimeout = 300000 - protected axiosInstance: AxiosInstance + protected httpTimeout = 300000; + protected axiosInstance: AxiosInstance; constructor() { this.axiosInstance = axios.create({ baseURL: '/api/v1', timeout: this.httpTimeout, - }) + }); this.axiosInstance.interceptors.response.use( (res) => res, (error) => { - const { response } = error + const { response } = error; if (response && response.status) { - const { status, data, statusText } = response - const errorMessage = data?.hintInfo ?? statusText + const { status, data, statusText } = response; + const errorMessage = data?.hintInfo ?? statusText; switch (status) { case HttpStatusCode.BadRequest: - throw new BadRequestException(errorMessage, status) + throw new BadRequestException(errorMessage, status); case HttpStatusCode.Unauthorized: - throw new UnauthorizedException(errorMessage, status) + throw new UnauthorizedException(errorMessage, status); case HttpStatusCode.NotFound: - throw new NotFoundException(errorMessage, status) + throw new NotFoundException(errorMessage, status); case HttpStatusCode.Forbidden: - throw new ForbiddenException(errorMessage, status) + throw new ForbiddenException(errorMessage, status); case HttpStatusCode.InternalServerError: - throw new InternalServerException(errorMessage, status) + throw new InternalServerException(errorMessage, status); case HttpStatusCode.ServiceUnavailable: - throw new TimeoutException(errorMessage, status) + throw new TimeoutException(errorMessage, status); default: - throw new UnknownException() + throw new UnknownException(); } } else { - throw new UnknownException() + throw new UnknownException(); } } - ) + ); } } diff --git a/frontend/src/clients/MetricsClient.ts b/frontend/src/clients/MetricsClient.ts index b59e625c1a..83a7f77ca6 100644 --- a/frontend/src/clients/MetricsClient.ts +++ b/frontend/src/clients/MetricsClient.ts @@ -1,19 +1,19 @@ -import { HttpClient } from '@src/clients/Httpclient' -import { HttpStatusCode } from 'axios' +import { HttpClient } from '@src/clients/Httpclient'; +import { HttpStatusCode } from 'axios'; export interface getStepsParams { - pipelineName: string - repository: string - orgName: string - startTime: number - endTime: number + pipelineName: string; + repository: string; + orgName: string; + startTime: number; + endTime: number; } export class MetricsClient extends HttpClient { - steps: string[] = [] - haveStep = true - branches: string[] = [] - pipelineCrews: string[] = [] + steps: string[] = []; + haveStep = true; + branches: string[] = []; + pipelineCrews: string[] = []; getSteps = async ( params: getStepsParams, @@ -22,8 +22,8 @@ export class MetricsClient extends HttpClient { pipelineType: string, token: string ) => { - this.steps = [] - this.haveStep = true + this.steps = []; + this.haveStep = true; const result = await this.axiosInstance.get( `/pipelines/${pipelineType}/${organizationId}/pipelines/${buildId}/steps`, { @@ -32,22 +32,22 @@ export class MetricsClient extends HttpClient { }, params, } - ) + ); if (result.status === HttpStatusCode.NoContent) { - this.haveStep = false + this.haveStep = false; } else { - this.steps = result.data.steps - this.haveStep = true + this.steps = result.data.steps; + this.haveStep = true; } - this.branches = result.status === HttpStatusCode.NoContent ? [] : result.data.branches - this.pipelineCrews = result.status === HttpStatusCode.NoContent ? [] : result.data.pipelineCrews + this.branches = result.status === HttpStatusCode.NoContent ? [] : result.data.branches; + this.pipelineCrews = result.status === HttpStatusCode.NoContent ? [] : result.data.pipelineCrews; return { response: this.steps, haveStep: this.haveStep, branches: this.branches, pipelineCrews: this.pipelineCrews, - } - } + }; + }; } -export const metricsClient = new MetricsClient() +export const metricsClient = new MetricsClient(); diff --git a/frontend/src/clients/board/BoardClient.ts b/frontend/src/clients/board/BoardClient.ts index b72ba12bdf..577c7c28e5 100644 --- a/frontend/src/clients/board/BoardClient.ts +++ b/frontend/src/clients/board/BoardClient.ts @@ -1,42 +1,42 @@ -import { HttpClient } from '@src/clients/Httpclient' -import { HttpStatusCode } from 'axios' -import { BoardRequestDTO } from '@src/clients/board/dto/request' +import { HttpClient } from '@src/clients/Httpclient'; +import { HttpStatusCode } from 'axios'; +import { BoardRequestDTO } from '@src/clients/board/dto/request'; export class BoardClient extends HttpClient { - isBoardVerify = false - haveDoneCard = true - response = {} + isBoardVerify = false; + haveDoneCard = true; + response = {}; getVerifyBoard = async (params: BoardRequestDTO) => { - this.isBoardVerify = false - this.haveDoneCard = true - this.response = {} + this.isBoardVerify = false; + this.haveDoneCard = true; + this.response = {}; try { - const boardType = params.type === 'Classic Jira' ? 'classic-jira' : params.type.toLowerCase() - const result = await this.axiosInstance.post(`/boards/${boardType}`, params) + const boardType = params.type === 'Classic Jira' ? 'classic-jira' : params.type.toLowerCase(); + const result = await this.axiosInstance.post(`/boards/${boardType}`, params); result.status === HttpStatusCode.NoContent ? this.handleBoardNoDoneCard() - : this.handleBoardVerifySucceed(result.data) + : this.handleBoardVerifySucceed(result.data); } catch (e) { - this.isBoardVerify = false - throw e + this.isBoardVerify = false; + throw e; } return { response: this.response, isBoardVerify: this.isBoardVerify, haveDoneCard: this.haveDoneCard, - } - } + }; + }; handleBoardNoDoneCard = () => { - this.isBoardVerify = false - this.haveDoneCard = false - } + this.isBoardVerify = false; + this.haveDoneCard = false; + }; handleBoardVerifySucceed = (res: object) => { - this.isBoardVerify = true - this.response = res - } + this.isBoardVerify = true; + this.response = res; + }; } -export const boardClient = new BoardClient() +export const boardClient = new BoardClient(); diff --git a/frontend/src/clients/board/dto/request.ts b/frontend/src/clients/board/dto/request.ts index a9c9b7c3b5..9f6557e74d 100644 --- a/frontend/src/clients/board/dto/request.ts +++ b/frontend/src/clients/board/dto/request.ts @@ -1,9 +1,9 @@ export interface BoardRequestDTO { - token: string - type: string - site: string - projectKey: string - startTime: number | null - endTime: number | null - boardId: string + token: string; + type: string; + site: string; + projectKey: string; + startTime: number | null; + endTime: number | null; + boardId: string; } diff --git a/frontend/src/clients/header/HeaderClient.ts b/frontend/src/clients/header/HeaderClient.ts index 6e09393abc..e5ddbfefb6 100644 --- a/frontend/src/clients/header/HeaderClient.ts +++ b/frontend/src/clients/header/HeaderClient.ts @@ -1,23 +1,23 @@ -import { HttpClient } from '@src/clients/Httpclient' -import { VersionResponseDTO } from '@src/clients/header/dto/request' +import { HttpClient } from '@src/clients/Httpclient'; +import { VersionResponseDTO } from '@src/clients/header/dto/request'; export class HeaderClient extends HttpClient { response: VersionResponseDTO = { version: '', - } + }; getVersion = async () => { try { - const res = await this.axiosInstance.get(`/version`) - this.response = res.data + const res = await this.axiosInstance.get(`/version`); + this.response = res.data; } catch (e) { this.response = { version: '', - } - throw e + }; + throw e; } - return this.response.version - } + return this.response.version; + }; } -export const headerClient = new HeaderClient() +export const headerClient = new HeaderClient(); diff --git a/frontend/src/clients/header/dto/request.ts b/frontend/src/clients/header/dto/request.ts index ebd423cb2e..05a6641137 100644 --- a/frontend/src/clients/header/dto/request.ts +++ b/frontend/src/clients/header/dto/request.ts @@ -1,3 +1,3 @@ export interface VersionResponseDTO { - version: string + version: string; } diff --git a/frontend/src/clients/pipeline/PipelineToolClient.ts b/frontend/src/clients/pipeline/PipelineToolClient.ts index ab1b37d5f8..33a626a1b0 100644 --- a/frontend/src/clients/pipeline/PipelineToolClient.ts +++ b/frontend/src/clients/pipeline/PipelineToolClient.ts @@ -1,27 +1,27 @@ -import { HttpClient } from '@src/clients/Httpclient' -import { PipelineRequestDTO } from '@src/clients/pipeline/dto/request' +import { HttpClient } from '@src/clients/Httpclient'; +import { PipelineRequestDTO } from '@src/clients/pipeline/dto/request'; export class PipelineToolClient extends HttpClient { - isPipelineToolVerified = false - response = {} + isPipelineToolVerified = false; + response = {}; verifyPipelineTool = async (params: PipelineRequestDTO) => { try { - const result = await this.axiosInstance.post(`/pipelines/${params.type}`, params) - this.handlePipelineToolVerifySucceed(result.data) + const result = await this.axiosInstance.post(`/pipelines/${params.type}`, params); + this.handlePipelineToolVerifySucceed(result.data); } catch (e) { - this.isPipelineToolVerified = false - throw e + this.isPipelineToolVerified = false; + throw e; } return { response: this.response, isPipelineToolVerified: this.isPipelineToolVerified, - } - } + }; + }; handlePipelineToolVerifySucceed = (res: object) => { - this.isPipelineToolVerified = true - this.response = res - } + this.isPipelineToolVerified = true; + this.response = res; + }; } -export const pipelineToolClient = new PipelineToolClient() +export const pipelineToolClient = new PipelineToolClient(); diff --git a/frontend/src/clients/pipeline/dto/request.ts b/frontend/src/clients/pipeline/dto/request.ts index 51e35fd4aa..5916deab31 100644 --- a/frontend/src/clients/pipeline/dto/request.ts +++ b/frontend/src/clients/pipeline/dto/request.ts @@ -1,6 +1,6 @@ export interface PipelineRequestDTO { - type: string - token: string - startTime: string | number | null - endTime: string | number | null + type: string; + token: string; + startTime: string | number | null; + endTime: string | number | null; } diff --git a/frontend/src/clients/report/CSVClient.ts b/frontend/src/clients/report/CSVClient.ts index bc5766c96c..ac5fe126c9 100644 --- a/frontend/src/clients/report/CSVClient.ts +++ b/frontend/src/clients/report/CSVClient.ts @@ -1,11 +1,11 @@ -import { HttpClient } from '@src/clients/Httpclient' -import { CSVReportRequestDTO } from '@src/clients/report/dto/request' -import dayjs from 'dayjs' -import { downloadCSV } from '@src/utils/util' +import { HttpClient } from '@src/clients/Httpclient'; +import { CSVReportRequestDTO } from '@src/clients/report/dto/request'; +import dayjs from 'dayjs'; +import { downloadCSV } from '@src/utils/util'; export class CSVClient extends HttpClient { - parseTimeStampToHumanDate = (csvTimeStamp: number | undefined): string => dayjs(csvTimeStamp).format('HHmmSSS') - parseCollectionDateToHumanDate = (date: string) => dayjs(date).format('YYYYMMDD') + parseTimeStampToHumanDate = (csvTimeStamp: number | undefined): string => dayjs(csvTimeStamp).format('HHmmSSS'); + parseCollectionDateToHumanDate = (date: string) => dayjs(date).format('YYYYMMDD'); exportCSVData = async (params: CSVReportRequestDTO) => { await this.axiosInstance @@ -15,13 +15,13 @@ export class CSVClient extends HttpClient { params.startDate )}-${this.parseCollectionDateToHumanDate(params.endDate)}-${this.parseTimeStampToHumanDate( params.csvTimeStamp - )}.csv` - downloadCSV(exportedFilename, res.data) + )}.csv`; + downloadCSV(exportedFilename, res.data); }) .catch((e) => { - throw e - }) - } + throw e; + }); + }; } -export const csvClient = new CSVClient() +export const csvClient = new CSVClient(); diff --git a/frontend/src/clients/report/ReportClient.ts b/frontend/src/clients/report/ReportClient.ts index ec2cb059ca..822e194857 100644 --- a/frontend/src/clients/report/ReportClient.ts +++ b/frontend/src/clients/report/ReportClient.ts @@ -1,13 +1,13 @@ -import { HttpClient } from '@src/clients/Httpclient' -import { ReportRequestDTO } from '@src/clients/report/dto/request' -import { ReportCallbackResponse, ReportResponseDTO } from '@src/clients/report/dto/response' +import { HttpClient } from '@src/clients/Httpclient'; +import { ReportRequestDTO } from '@src/clients/report/dto/request'; +import { ReportCallbackResponse, ReportResponseDTO } from '@src/clients/report/dto/response'; export class ReportClient extends HttpClient { - status = 0 + status = 0; reportCallbackResponse: ReportCallbackResponse = { callbackUrl: '', interval: 0, - } + }; reportResponse: ReportResponseDTO = { velocity: { velocityForSP: 0, @@ -63,37 +63,37 @@ export class ReportClient extends HttpClient { isPipelineMetricsReady: false, isSourceControlMetricsReady: false, isAllMetricsReady: false, - } + }; retrieveReportByUrl = async (params: ReportRequestDTO, url: string) => { await this.axiosInstance .post(url, params, {}) .then((res) => { - this.reportCallbackResponse = res.data + this.reportCallbackResponse = res.data; }) .catch((e) => { - throw e - }) + throw e; + }); return { response: this.reportCallbackResponse, - } - } + }; + }; pollingReport = async (url: string) => { await this.axiosInstance .get(url) .then((res) => { - this.status = res.status - this.reportResponse = res.data + this.status = res.status; + this.reportResponse = res.data; }) .catch((e) => { - throw e - }) + throw e; + }); return { status: this.status, response: this.reportResponse, - } - } + }; + }; } -export const reportClient = new ReportClient() +export const reportClient = new ReportClient(); diff --git a/frontend/src/clients/report/dto/request.ts b/frontend/src/clients/report/dto/request.ts index 0f3c8308d3..fef6d7628d 100644 --- a/frontend/src/clients/report/dto/request.ts +++ b/frontend/src/clients/report/dto/request.ts @@ -1,77 +1,77 @@ export interface ReportRequestDTO { - metrics: string[] - startTime: string | null - endTime: string | null - considerHoliday: boolean + metrics: string[]; + startTime: string | null; + endTime: string | null; + considerHoliday: boolean; buildKiteSetting?: { - type: string - token: string - pipelineCrews: string[] + type: string; + token: string; + pipelineCrews: string[]; deploymentEnvList: | { - id: string - name: string - orgId: string - orgName: string - repository: string - step: string - branches: string[] + id: string; + name: string; + orgId: string; + orgName: string; + repository: string; + step: string; + branches: string[]; }[] - | [] - } + | []; + }; codebaseSetting?: { - type: string - token: string + type: string; + token: string; leadTime: { - id: string - name: string - orgId: string - orgName: string - repository: string - step: string - branches: string[] - }[] - } + id: string; + name: string; + orgId: string; + orgName: string; + repository: string; + step: string; + branches: string[]; + }[]; + }; jiraBoardSetting?: { - token: string - type: string - site: string - projectKey: string - boardId: string - boardColumns: { name: string; value: string }[] - treatFlagCardAsBlock: boolean - users: string[] - assigneeFilter: string - targetFields: { key: string; name: string; flag: boolean }[] - doneColumn: string[] - } - csvTimeStamp?: number + token: string; + type: string; + site: string; + projectKey: string; + boardId: string; + boardColumns: { name: string; value: string }[]; + treatFlagCardAsBlock: boolean; + users: string[]; + assigneeFilter: string; + targetFields: { key: string; name: string; flag: boolean }[]; + doneColumn: string[]; + }; + csvTimeStamp?: number; } export interface BoardReportRequestDTO { - considerHoliday: boolean - startTime: string | null - endTime: string | null - metrics: string[] + considerHoliday: boolean; + startTime: string | null; + endTime: string | null; + metrics: string[]; jiraBoardSetting?: { - token: string - type: string - site: string - projectKey: string - boardId: string - boardColumns: { name: string; value: string }[] - treatFlagCardAsBlock: boolean - users: string[] - assigneeFilter: string - targetFields: { key: string; name: string; flag: boolean }[] - doneColumn: string[] - } - csvTimeStamp?: number + token: string; + type: string; + site: string; + projectKey: string; + boardId: string; + boardColumns: { name: string; value: string }[]; + treatFlagCardAsBlock: boolean; + users: string[]; + assigneeFilter: string; + targetFields: { key: string; name: string; flag: boolean }[]; + doneColumn: string[]; + }; + csvTimeStamp?: number; } export interface CSVReportRequestDTO { - dataType: string - csvTimeStamp: number - startDate: string - endDate: string + dataType: string; + csvTimeStamp: number; + startDate: string; + endDate: string; } diff --git a/frontend/src/clients/report/dto/response.ts b/frontend/src/clients/report/dto/response.ts index c48a930754..b7df0db9e6 100644 --- a/frontend/src/clients/report/dto/response.ts +++ b/frontend/src/clients/report/dto/response.ts @@ -1,142 +1,143 @@ -import { ReportDataWithThreeColumns, ReportDataWithTwoColumns } from '@src/hooks/reportMapper/reportUIDataStructure' +import { ReportDataWithThreeColumns, ReportDataWithTwoColumns } from '@src/hooks/reportMapper/reportUIDataStructure'; +import { Nullable } from '@src/utils/types'; export interface ReportResponseDTO { - velocity: VelocityResponse | null - cycleTime: CycleTimeResponse | null - deploymentFrequency: DeploymentFrequencyResponse | null - meanTimeToRecovery: MeanTimeToRecoveryResponse | null - leadTimeForChanges: LeadTimeForChangesResponse | null - changeFailureRate: ChangeFailureRateResponse | null - classificationList: Array | null - exportValidityTime: number | null - isBoardMetricsReady: boolean | null - isPipelineMetricsReady: boolean | null - isSourceControlMetricsReady: boolean | null - isAllMetricsReady: boolean + velocity: Nullable; + cycleTime: Nullable; + classificationList: Nullable; + deploymentFrequency: Nullable; + meanTimeToRecovery: Nullable; + leadTimeForChanges: Nullable; + changeFailureRate: Nullable; + exportValidityTime: Nullable; + isBoardMetricsReady: Nullable; + isPipelineMetricsReady: Nullable; + isSourceControlMetricsReady: Nullable; + isAllMetricsReady: boolean; } export interface VelocityResponse { - velocityForSP: number - velocityForCards: number + velocityForSP: number; + velocityForCards: number; } export interface CycleTimeResponse { - totalTimeForCards: number - averageCycleTimePerCard: number - averageCycleTimePerSP: number - swimlaneList: Array + totalTimeForCards: number; + averageCycleTimePerCard: number; + averageCycleTimePerSP: number; + swimlaneList: Array; } export interface ClassificationResponse { - fieldName: string - pairList: Array + fieldName: string; + pairList: Array; } export interface DeploymentFrequencyResponse { - avgDeploymentFrequency: AVGDeploymentFrequency - deploymentFrequencyOfPipelines: DeploymentFrequencyOfPipeline[] + avgDeploymentFrequency: AVGDeploymentFrequency; + deploymentFrequencyOfPipelines: DeploymentFrequencyOfPipeline[]; } export interface LeadTimeForChangesResponse { - leadTimeForChangesOfPipelines: Array - avgLeadTimeForChanges: AvgLeadTime + leadTimeForChangesOfPipelines: Array; + avgLeadTimeForChanges: AvgLeadTime; } export interface ChangeFailureRateResponse { - avgChangeFailureRate: AvgFailureRate - changeFailureRateOfPipelines: FailureRateOfPipeline[] + avgChangeFailureRate: AvgFailureRate; + changeFailureRateOfPipelines: FailureRateOfPipeline[]; } export interface Swimlane { - optionalItemName: string - averageTimeForSP: number - averageTimeForCards: number - totalTime: number + optionalItemName: string; + averageTimeForSP: number; + averageTimeForCards: number; + totalTime: number; } export interface AVGDeploymentFrequency { - name: string - step?: string - deploymentFrequency: number + name: string; + step?: string; + deploymentFrequency: number; } export interface DeploymentDateCount { - date: string - count: number + date: string; + count: number; } export interface DeploymentFrequencyOfPipeline { - name: string - step: string - deploymentFrequency: number - dailyDeploymentCounts: DeploymentDateCount[] + name: string; + step: string; + deploymentFrequency: number; + dailyDeploymentCounts: DeploymentDateCount[]; } export interface LeadTimeOfPipeline { - name: string - step: string - prLeadTime: number - pipelineLeadTime: number - totalDelayTime: number + name: string; + step: string; + prLeadTime: number; + pipelineLeadTime: number; + totalDelayTime: number; } export interface AvgLeadTime { - name: string - step?: string - prLeadTime: number - pipelineLeadTime: number - totalDelayTime: number + name: string; + step?: string; + prLeadTime: number; + pipelineLeadTime: number; + totalDelayTime: number; } export interface FailureRateOfPipeline { - name: string - step: string - failedTimesOfPipeline: number - totalTimesOfPipeline: number - failureRate: number + name: string; + step: string; + failedTimesOfPipeline: number; + totalTimesOfPipeline: number; + failureRate: number; } export interface AvgFailureRate { - name: string - step?: string - totalTimes: number - totalFailedTimes: number - failureRate: number + name: string; + step?: string; + totalTimes: number; + totalFailedTimes: number; + failureRate: number; } export interface MeanTimeToRecoveryOfPipeline { - name: string - step: string - timeToRecovery: number + name: string; + step: string; + timeToRecovery: number; } export interface AvgMeanTimeToRecovery { - name: string - timeToRecovery: number + name: string; + timeToRecovery: number; } export interface MeanTimeToRecoveryResponse { - avgMeanTimeToRecovery: AvgMeanTimeToRecovery - meanTimeRecoveryPipelines: MeanTimeToRecoveryOfPipeline[] + avgMeanTimeToRecovery: AvgMeanTimeToRecovery; + meanTimeRecoveryPipelines: MeanTimeToRecoveryOfPipeline[]; } export interface ClassificationNameValuePair { - name: string - value: number + name: string; + value: number; } export interface ReportCallbackResponse { - callbackUrl: string - interval: number + callbackUrl: string; + interval: number; } export interface ReportResponse { - velocityList?: ReportDataWithTwoColumns[] | null - cycleTimeList?: ReportDataWithTwoColumns[] | null - classification?: ReportDataWithThreeColumns[] | null - deploymentFrequencyList?: ReportDataWithThreeColumns[] | null - meanTimeToRecoveryList?: ReportDataWithThreeColumns[] | null - leadTimeForChangesList?: ReportDataWithThreeColumns[] | null - changeFailureRateList?: ReportDataWithThreeColumns[] | null - exportValidityTimeMin?: number | null + velocityList?: ReportDataWithTwoColumns[] | null; + cycleTimeList?: ReportDataWithTwoColumns[] | null; + classification?: ReportDataWithThreeColumns[] | null; + deploymentFrequencyList?: ReportDataWithThreeColumns[] | null; + meanTimeToRecoveryList?: ReportDataWithThreeColumns[] | null; + leadTimeForChangesList?: ReportDataWithThreeColumns[] | null; + changeFailureRateList?: ReportDataWithThreeColumns[] | null; + exportValidityTimeMin?: number | null; } diff --git a/frontend/src/clients/sourceControl/SourceControlClient.ts b/frontend/src/clients/sourceControl/SourceControlClient.ts index f0a56109af..3d54b0e7f8 100644 --- a/frontend/src/clients/sourceControl/SourceControlClient.ts +++ b/frontend/src/clients/sourceControl/SourceControlClient.ts @@ -1,28 +1,28 @@ -import { HttpClient } from '@src/clients/Httpclient' -import { SourceControlRequestDTO } from '@src/clients/sourceControl/dto/request' +import { HttpClient } from '@src/clients/Httpclient'; +import { SourceControlRequestDTO } from '@src/clients/sourceControl/dto/request'; export class SourceControlClient extends HttpClient { - isSourceControlVerify = false - response = {} + isSourceControlVerify = false; + response = {}; getVerifySourceControl = async (params: SourceControlRequestDTO) => { try { - const result = await this.axiosInstance.post('/source-control', params) - this.handleSourceControlVerifySucceed(result.data) + const result = await this.axiosInstance.post('/source-control', params); + this.handleSourceControlVerifySucceed(result.data); } catch (e) { - this.isSourceControlVerify = false - throw e + this.isSourceControlVerify = false; + throw e; } return { response: this.response, isSourceControlVerify: this.isSourceControlVerify, - } - } + }; + }; handleSourceControlVerifySucceed = (res: object) => { - this.isSourceControlVerify = true - this.response = res - } + this.isSourceControlVerify = true; + this.response = res; + }; } -export const sourceControlClient = new SourceControlClient() +export const sourceControlClient = new SourceControlClient(); diff --git a/frontend/src/clients/sourceControl/dto/request.ts b/frontend/src/clients/sourceControl/dto/request.ts index d57aed0daa..2d64d5d330 100644 --- a/frontend/src/clients/sourceControl/dto/request.ts +++ b/frontend/src/clients/sourceControl/dto/request.ts @@ -1,6 +1,6 @@ export interface SourceControlRequestDTO { - type: string - token: string - startTime: string | number | null - endTime: string | number | null + type: string; + token: string; + startTime: string | number | null; + endTime: string | number | null; } diff --git a/frontend/src/components/Common/Buttons.ts b/frontend/src/components/Common/Buttons.ts index 11105283b8..d9120bf6b1 100644 --- a/frontend/src/components/Common/Buttons.ts +++ b/frontend/src/components/Common/Buttons.ts @@ -1,16 +1,16 @@ -import Button from '@mui/material/Button' -import { styled } from '@mui/material/styles' -import { theme } from '@src/theme' +import Button from '@mui/material/Button'; +import { styled } from '@mui/material/styles'; +import { theme } from '@src/theme'; export const BasicButton = styled(Button)({ width: '3rem', fontSize: '0.8rem', fontFamily: theme.main.font.secondary, fontWeight: 'bold', -}) +}); -export const VerifyButton = styled(BasicButton)({}) +export const VerifyButton = styled(BasicButton)({}); export const ResetButton = styled(BasicButton)({ color: '#f44336', marginLeft: '0.5rem', -}) +}); diff --git a/frontend/src/components/Common/CollectionDuration/index.tsx b/frontend/src/components/Common/CollectionDuration/index.tsx index 61156d8a2b..0326bad7d4 100644 --- a/frontend/src/components/Common/CollectionDuration/index.tsx +++ b/frontend/src/components/Common/CollectionDuration/index.tsx @@ -6,41 +6,41 @@ import { MonthYearText, DateTitle, ColoredTopArea, -} from '@src/components/Common/CollectionDuration/style' -import dayjs from 'dayjs' +} from '@src/components/Common/CollectionDuration/style'; +import dayjs from 'dayjs'; type Props = { - startDate: string - endDate: string -} + startDate: string; + endDate: string; +}; type DisplayedDateInfo = { - year: string - month: string - day: string -} + year: string; + month: string; + day: string; +}; type DateInformation = { - formattedDate: DisplayedDateInfo - dateTittle: string -} + formattedDate: DisplayedDateInfo; + dateTittle: string; +}; const CollectionDuration = ({ startDate, endDate }: Props) => { - const toBeFormattedStartDate = dayjs(startDate) - const toBeFormattedEndDate = dayjs(endDate) + const toBeFormattedStartDate = dayjs(startDate); + const toBeFormattedEndDate = dayjs(endDate); const startDateInfo: DisplayedDateInfo = { year: toBeFormattedStartDate.format('YY'), month: toBeFormattedStartDate.format('MMM'), day: toBeFormattedStartDate.format('DD'), - } + }; const endDateInfo: DisplayedDateInfo = { year: toBeFormattedEndDate.format('YY'), month: toBeFormattedEndDate.format('MMM'), day: toBeFormattedEndDate.format('DD'), - } + }; const DateDisplay = (props: DateInformation) => { - const { dateTittle, formattedDate } = props + const { dateTittle, formattedDate } = props; return (
{dateTittle} @@ -52,8 +52,8 @@ const CollectionDuration = ({ startDate, endDate }: Props) => {
- ) - } + ); + }; return ( @@ -61,7 +61,7 @@ const CollectionDuration = ({ startDate, endDate }: Props) => { - ) -} + ); +}; -export default CollectionDuration +export default CollectionDuration; diff --git a/frontend/src/components/Common/CollectionDuration/style.tsx b/frontend/src/components/Common/CollectionDuration/style.tsx index 14d0b5fe80..a07dc01391 100644 --- a/frontend/src/components/Common/CollectionDuration/style.tsx +++ b/frontend/src/components/Common/CollectionDuration/style.tsx @@ -1,12 +1,12 @@ -import { styled } from '@mui/material/styles' -import { Paper } from '@mui/material' -import { theme } from '@src/theme' +import { styled } from '@mui/material/styles'; +import { Paper } from '@mui/material'; +import { theme } from '@src/theme'; export const CollectionDateContainer = styled('div')({ display: 'flex', alignItems: 'flex-end', margin: '0 auto 2rem', -}) +}); export const TextBox = styled(Paper)({ width: '3rem', @@ -20,7 +20,7 @@ export const TextBox = styled(Paper)({ flexDirection: 'column', alignItems: 'center', justifyContent: 'center', -}) +}); export const GreyTransitionBox = styled('div')({ width: '10rem', @@ -28,7 +28,7 @@ export const GreyTransitionBox = styled('div')({ backgroundColor: '#CCCCCC47', border: 0, borderRadius: 0, -}) +}); export const ColoredTopArea = styled('div')((props: { isStart: boolean }) => ({ width: '100%', @@ -36,7 +36,7 @@ export const ColoredTopArea = styled('div')((props: { isStart: boolean }) => ({ backgroundColor: props.isStart ? theme.palette.info.light : theme.palette.info.dark, borderTopLeftRadius: '0.375rem', borderTopRightRadius: '0.375rem', -})) +})); export const DateTitle = styled('div')((props: { isStart: boolean }) => ({ fontSize: '0.625rem', @@ -46,14 +46,14 @@ export const DateTitle = styled('div')((props: { isStart: boolean }) => ({ padding: '0.25rem 0', fontWeight: 'bold', marginBottom: '0.125rem', -})) +})); export const DateText = styled('div')({ fontSize: '1.5rem', letterSpacing: '0.1rem', -}) +}); export const MonthYearText = styled('div')({ fontSize: '0.625rem', marginBottom: '0.3rem', -}) +}); diff --git a/frontend/src/components/Common/ConfigForms.ts b/frontend/src/components/Common/ConfigForms.ts index 347a75ff45..11a4ce2c45 100644 --- a/frontend/src/components/Common/ConfigForms.ts +++ b/frontend/src/components/Common/ConfigForms.ts @@ -1,9 +1,9 @@ -import { styled } from '@mui/material/styles' -import { FormControl, TextField } from '@mui/material' -import { theme } from '@src/theme' -import { MetricSelectionWrapper } from '@src/components/Metrics/MetricsStep/style' +import { styled } from '@mui/material/styles'; +import { FormControl, TextField } from '@mui/material'; +import { theme } from '@src/theme'; +import { MetricSelectionWrapper } from '@src/components/Metrics/MetricsStep/style'; -export const ConfigSectionContainer = styled(MetricSelectionWrapper)({}) +export const ConfigSectionContainer = styled(MetricSelectionWrapper)({}); export const StyledForm = styled('form')({ display: 'grid', @@ -14,9 +14,9 @@ export const StyledForm = styled('form')({ [theme.breakpoints.down('md')]: { gridTemplateColumns: '1fr', }, -}) +}); -export const StyledTypeSelections = styled(FormControl)({}) +export const StyledTypeSelections = styled(FormControl)({}); export const StyledTextField = styled(TextField)` input { @@ -28,9 +28,9 @@ export const StyledTextField = styled(TextField)` input:-webkit-autofill:active { transition: background-color 5000s ease-in-out 0s; } -` +`; export const StyledButtonGroup = styled('div')({ justifySelf: 'end', gridColumn: '2 / 3', -}) +}); diff --git a/frontend/src/components/Common/DateRangeViewer/index.tsx b/frontend/src/components/Common/DateRangeViewer/index.tsx index 845cada1cf..e9b127a746 100644 --- a/frontend/src/components/Common/DateRangeViewer/index.tsx +++ b/frontend/src/components/Common/DateRangeViewer/index.tsx @@ -1,10 +1,10 @@ -import { DateRangeContainer, StyledArrowForward, StyledCalendarToday } from './style' -import { formatDate } from '@src/utils/util' +import { DateRangeContainer, StyledArrowForward, StyledCalendarToday } from './style'; +import { formatDate } from '@src/utils/util'; type Props = { - startDate: string - endDate: string -} + startDate: string; + endDate: string; +}; const DateRangeViewer = ({ startDate, endDate }: Props) => { return ( @@ -14,7 +14,7 @@ const DateRangeViewer = ({ startDate, endDate }: Props) => { {formatDate(endDate)} - ) -} + ); +}; -export default DateRangeViewer +export default DateRangeViewer; diff --git a/frontend/src/components/Common/DateRangeViewer/style.tsx b/frontend/src/components/Common/DateRangeViewer/style.tsx index 0cf1ff725a..5cb4648199 100644 --- a/frontend/src/components/Common/DateRangeViewer/style.tsx +++ b/frontend/src/components/Common/DateRangeViewer/style.tsx @@ -1,6 +1,6 @@ -import styled from '@emotion/styled' -import { ArrowForward, CalendarToday } from '@mui/icons-material' -import { theme } from '@src/theme' +import styled from '@emotion/styled'; +import { ArrowForward, CalendarToday } from '@mui/icons-material'; +import { theme } from '@src/theme'; export const DateRangeContainer = styled.div({ display: 'flex', @@ -14,16 +14,16 @@ export const DateRangeContainer = styled.div({ padding: '.75rem', color: theme.palette.text.disabled, fontSize: '.875rem', -}) +}); export const StyledArrowForward = styled(ArrowForward)({ color: theme.palette.text.disabled, margin: '0 .5rem', fontSize: '.875rem', -}) +}); export const StyledCalendarToday = styled(CalendarToday)({ color: theme.palette.text.disabled, marginLeft: '1rem', fontSize: '.875rem', -}) +}); diff --git a/frontend/src/components/Common/EllipsisText/index.tsx b/frontend/src/components/Common/EllipsisText/index.tsx index 21cabb091e..46d0a1c344 100644 --- a/frontend/src/components/Common/EllipsisText/index.tsx +++ b/frontend/src/components/Common/EllipsisText/index.tsx @@ -1,10 +1,10 @@ -import React from 'react' -import { StyledText } from '@src/components/Common/EllipsisText/style' +import React from 'react'; +import { StyledText } from '@src/components/Common/EllipsisText/style'; interface IEllipsisTextProps { - children: React.ReactNode - fitContent: boolean - ref: React.ForwardedRef + children: React.ReactNode; + fitContent: boolean; + ref: React.ForwardedRef; } export default React.forwardRef(function RefWrapper( @@ -15,5 +15,5 @@ export default React.forwardRef(functi {props.children} - ) -}) + ); +}); diff --git a/frontend/src/components/Common/EllipsisText/style.tsx b/frontend/src/components/Common/EllipsisText/style.tsx index 18f8c4b2bc..82154be379 100644 --- a/frontend/src/components/Common/EllipsisText/style.tsx +++ b/frontend/src/components/Common/EllipsisText/style.tsx @@ -1,8 +1,8 @@ -import styled from '@emotion/styled' -import { ellipsisProps } from '@src/layouts/style' +import styled from '@emotion/styled'; +import { ellipsisProps } from '@src/layouts/style'; export const StyledText = styled.p(({ fitContent }: { fitContent: boolean }) => ({ maxWidth: '100%', width: fitContent ? 'fit-content' : 'auto', ...ellipsisProps, -})) +})); diff --git a/frontend/src/components/Common/MetricsSettingButton/index.tsx b/frontend/src/components/Common/MetricsSettingButton/index.tsx index 2019c2ef30..a94d1456a2 100644 --- a/frontend/src/components/Common/MetricsSettingButton/index.tsx +++ b/frontend/src/components/Common/MetricsSettingButton/index.tsx @@ -1,12 +1,12 @@ -import { Add } from '@mui/icons-material' -import React from 'react' +import { Add } from '@mui/icons-material'; +import React from 'react'; import { MetricsSettingAddButtonContainer, MetricsSettingAddButtonItem, -} from '@src/components/Common/MetricsSettingButton/style' +} from '@src/components/Common/MetricsSettingButton/style'; interface metricsSettingAddButtonProps { - onAddPipeline: () => void + onAddPipeline: () => void; } export const MetricsSettingAddButton = ({ onAddPipeline }: metricsSettingAddButtonProps) => { @@ -16,5 +16,5 @@ export const MetricsSettingAddButton = ({ onAddPipeline }: metricsSettingAddButt New Pipeline - ) -} + ); +}; diff --git a/frontend/src/components/Common/MetricsSettingButton/style.tsx b/frontend/src/components/Common/MetricsSettingButton/style.tsx index 26bf4078e6..4bf2e8fd6f 100644 --- a/frontend/src/components/Common/MetricsSettingButton/style.tsx +++ b/frontend/src/components/Common/MetricsSettingButton/style.tsx @@ -1,5 +1,5 @@ -import styled from '@emotion/styled' -import { Button } from '@mui/material' +import styled from '@emotion/styled'; +import { Button } from '@mui/material'; export const MetricsSettingAddButtonContainer = styled.div({ display: 'flex', @@ -7,8 +7,8 @@ export const MetricsSettingAddButtonContainer = styled.div({ borderRadius: '0.25rem', border: '0.07rem dashed rgba(67, 80, 175, 1)', marginBottom: '1rem', -}) +}); export const MetricsSettingAddButtonItem = styled(Button)({ width: '100%', -}) +}); diff --git a/frontend/src/components/Common/MetricsSettingTitle/index.tsx b/frontend/src/components/Common/MetricsSettingTitle/index.tsx index 273a9bde14..4fc8ab1c2e 100644 --- a/frontend/src/components/Common/MetricsSettingTitle/index.tsx +++ b/frontend/src/components/Common/MetricsSettingTitle/index.tsx @@ -1,12 +1,12 @@ -import styled from '@emotion/styled' +import styled from '@emotion/styled'; export const MetricsSettingTitleContainer = styled.div({ margin: '1.25rem 0', fontSize: '1rem', lineHeight: '1.25rem', fontWeight: '600', -}) +}); export const MetricsSettingTitle = (props: { title: string }) => ( {props.title} -) +); diff --git a/frontend/src/components/Common/MetricsSettingTitle/style.tsx b/frontend/src/components/Common/MetricsSettingTitle/style.tsx index 421c8e9bd2..b178f959a9 100644 --- a/frontend/src/components/Common/MetricsSettingTitle/style.tsx +++ b/frontend/src/components/Common/MetricsSettingTitle/style.tsx @@ -1,8 +1,8 @@ -import { styled } from '@mui/material/styles' -import { theme } from '@src/theme' +import { styled } from '@mui/material/styles'; +import { theme } from '@src/theme'; export const Divider = styled('div')({ paddingLeft: '0.5rem', borderLeft: `0.4rem solid ${theme.main.backgroundColor}`, margin: '0', -}) +}); diff --git a/frontend/src/components/Common/MultiAutoComplete/index.tsx b/frontend/src/components/Common/MultiAutoComplete/index.tsx index eb26c558bf..95f60e059e 100644 --- a/frontend/src/components/Common/MultiAutoComplete/index.tsx +++ b/frontend/src/components/Common/MultiAutoComplete/index.tsx @@ -1,19 +1,19 @@ -import { Checkbox, createFilterOptions, TextField } from '@mui/material' -import React from 'react' -import { StyledAutocompleted } from './styles' -import { Z_INDEX } from '@src/constants/commons' +import { Checkbox, createFilterOptions, TextField } from '@mui/material'; +import React from 'react'; +import { StyledAutocompleted } from './styles'; +import { Z_INDEX } from '@src/constants/commons'; type Props = { - optionList: string[] - selectedOption: string[] + optionList: string[]; + selectedOption: string[]; // There is an any because m-ui strictly define its type, but the parameters are not that strict. Maybe because of version diff - onChangeHandler: any - isSelectAll: boolean - textFieldLabel: string - isError: boolean - testId?: string - isBoardCrews?: boolean -} + onChangeHandler: any; + isSelectAll: boolean; + textFieldLabel: string; + isError: boolean; + testId?: string; + isBoardCrews?: boolean; +}; const MultiAutoComplete = ({ optionList, selectedOption, @@ -24,7 +24,7 @@ const MultiAutoComplete = ({ testId, isBoardCrews = true, }: Props) => { - const filter = createFilterOptions() + const filter = createFilterOptions(); return ( { - const filtered = filter(options, params) - return ['All', ...filtered] + const filtered = filter(options, params); + return ['All', ...filtered]; }} getOptionLabel={(option) => option as string} onChange={onChangeHandler} renderOption={(props, option, { selected }) => { - const selectAllProps = option === 'All' ? { checked: isSelectAll } : {} + const selectAllProps = option === 'All' ? { checked: isSelectAll } : {}; return (
  • {option as string}
  • - ) + ); }} renderInput={(params) => ( - ) -} + ); +}; -export default MultiAutoComplete +export default MultiAutoComplete; diff --git a/frontend/src/components/Common/MultiAutoComplete/styles.ts b/frontend/src/components/Common/MultiAutoComplete/styles.ts index c5ca5912e3..de46257b6f 100644 --- a/frontend/src/components/Common/MultiAutoComplete/styles.ts +++ b/frontend/src/components/Common/MultiAutoComplete/styles.ts @@ -1,5 +1,5 @@ -import { styled } from '@mui/material/styles' -import { Autocomplete } from '@mui/material' +import { styled } from '@mui/material/styles'; +import { Autocomplete } from '@mui/material'; export const StyledAutocompleted = styled(Autocomplete)` & .MuiAutocomplete-tag { @@ -7,4 +7,4 @@ export const StyledAutocompleted = styled(Autocomplete)` border: 0.05rem solid rgba(0, 0, 0, 0.26); font-size: 0.9rem; } -` +`; diff --git a/frontend/src/components/Common/NotificationButton/index.tsx b/frontend/src/components/Common/NotificationButton/index.tsx index 487e65f06f..b013035326 100644 --- a/frontend/src/components/Common/NotificationButton/index.tsx +++ b/frontend/src/components/Common/NotificationButton/index.tsx @@ -1,16 +1,16 @@ -import { NotificationIconWrapper, sx } from '@src/components/Common/NotificationButton/style' -import { ClickAwayListener, Tooltip } from '@mui/material' -import { useNotificationLayoutEffectInterface } from '@src/hooks/useNotificationLayoutEffect' +import { NotificationIconWrapper, sx } from '@src/components/Common/NotificationButton/style'; +import { ClickAwayListener, Tooltip } from '@mui/material'; +import { useNotificationLayoutEffectInterface } from '@src/hooks/useNotificationLayoutEffect'; export const NotificationButton = ({ notificationProps, updateProps }: useNotificationLayoutEffectInterface) => { const handleTooltipClose = () => { - if (notificationProps === undefined) return + if (notificationProps === undefined) return; updateProps?.({ title: notificationProps.title, open: false, closeAutomatically: false, - }) - } + }); + }; return ( <> @@ -38,5 +38,5 @@ export const NotificationButton = ({ notificationProps, updateProps }: useNotifi - ) -} + ); +}; diff --git a/frontend/src/components/Common/NotificationButton/style.tsx b/frontend/src/components/Common/NotificationButton/style.tsx index d4a15a1cfe..6f6d38b114 100644 --- a/frontend/src/components/Common/NotificationButton/style.tsx +++ b/frontend/src/components/Common/NotificationButton/style.tsx @@ -1,12 +1,12 @@ -import { styled } from '@mui/material/styles' -import { theme } from '@src/theme' -import { Z_INDEX } from '@src/constants/commons' -import { NotificationsRounded } from '@mui/icons-material' +import { styled } from '@mui/material/styles'; +import { theme } from '@src/theme'; +import { Z_INDEX } from '@src/constants/commons'; +import { NotificationsRounded } from '@mui/icons-material'; export const NotificationIconWrapper = styled(NotificationsRounded)({ color: theme.main.color, marginRight: '0.5rem', -}) +}); export const sx = { width: '12rem', @@ -19,4 +19,4 @@ export const sx = { color: theme.components!.tip.color, }, zIndex: Z_INDEX.TOOLTIP, -} +}; diff --git a/frontend/src/components/Common/ReportForThreeColumns/index.tsx b/frontend/src/components/Common/ReportForThreeColumns/index.tsx index 17c02c924d..babb754824 100644 --- a/frontend/src/components/Common/ReportForThreeColumns/index.tsx +++ b/frontend/src/components/Common/ReportForThreeColumns/index.tsx @@ -1,31 +1,31 @@ -import { Table, TableBody, TableHead, TableRow } from '@mui/material' +import { Table, TableBody, TableHead, TableRow } from '@mui/material'; import { BorderTableCell, ColumnTableCell, Container, Row, StyledTableCell, -} from '@src/components/Common/ReportForTwoColumns/style' -import React, { Fragment } from 'react' -import { ReportDataWithThreeColumns } from '@src/hooks/reportMapper/reportUIDataStructure' -import { AVERAGE_FIELD, REPORT_SUFFIX_UNITS } from '@src/constants/resources' -import { getEmojiUrls, removeExtraEmojiName } from '@src/emojis/emoji' -import { EmojiWrap, StyledAvatar, StyledTypography } from '@src/emojis/style' -import { ReportSelectionTitle } from '@src/components/Metrics/MetricsStep/style' +} from '@src/components/Common/ReportForTwoColumns/style'; +import React, { Fragment } from 'react'; +import { ReportDataWithThreeColumns } from '@src/hooks/reportMapper/reportUIDataStructure'; +import { AVERAGE_FIELD, REPORT_SUFFIX_UNITS } from '@src/constants/resources'; +import { getEmojiUrls, removeExtraEmojiName } from '@src/emojis/emoji'; +import { EmojiWrap, StyledAvatar, StyledTypography } from '@src/emojis/style'; +import { ReportSelectionTitle } from '@src/components/Metrics/MetricsStep/style'; interface ReportForThreeColumnsProps { - title: string - fieldName: string - listName: string - data: ReportDataWithThreeColumns[] + title: string; + fieldName: string; + listName: string; + data: ReportDataWithThreeColumns[]; } export const ReportForThreeColumns = ({ title, fieldName, listName, data }: ReportForThreeColumnsProps) => { const emojiRow = (row: ReportDataWithThreeColumns) => { - const { name } = row - const emojiUrls: string[] = getEmojiUrls(name) + const { name } = row; + const emojiUrls: string[] = getEmojiUrls(name); if (name.includes(':') && emojiUrls.length > 0) { - const [prefix, suffix] = row.name.split('/') + const [prefix, suffix] = row.name.split('/'); return ( {prefix}/ @@ -34,31 +34,31 @@ export const ReportForThreeColumns = ({ title, fieldName, listName, data }: Repo ))} {removeExtraEmojiName(suffix)} - ) + ); } - return {name} - } + return {name}; + }; const renderRows = () => data.slice(0, data.length === 2 && data[1].name === AVERAGE_FIELD ? 1 : data.length).map((row) => ( - + {emojiRow(row)} {row.valuesList.map((valuesList) => ( - + {valuesList.name} {valuesList.value} ))} - )) + )); return ( <> {title} - +
    {fieldName} @@ -75,7 +75,7 @@ export const ReportForThreeColumns = ({ title, fieldName, listName, data }: Repo
    - ) -} + ); +}; -export default ReportForThreeColumns +export default ReportForThreeColumns; diff --git a/frontend/src/components/Common/ReportForTwoColumns/index.tsx b/frontend/src/components/Common/ReportForTwoColumns/index.tsx index 9bbc207f4d..38f1341766 100644 --- a/frontend/src/components/Common/ReportForTwoColumns/index.tsx +++ b/frontend/src/components/Common/ReportForTwoColumns/index.tsx @@ -1,44 +1,44 @@ -import { Table, TableBody, TableHead, TableRow } from '@mui/material' +import { Table, TableBody, TableHead, TableRow } from '@mui/material'; import { BorderTableCell, ColumnTableCell, Container, Row, StyledTableCell, -} from '@src/components/Common/ReportForTwoColumns/style' -import { Fragment } from 'react' -import { ReportDataWithTwoColumns } from '@src/hooks/reportMapper/reportUIDataStructure' -import { ReportSelectionTitle } from '@src/components/Metrics/MetricsStep/style' +} from '@src/components/Common/ReportForTwoColumns/style'; +import React, { Fragment } from 'react'; +import { ReportDataWithTwoColumns } from '@src/hooks/reportMapper/reportUIDataStructure'; +import { ReportSelectionTitle } from '@src/components/Metrics/MetricsStep/style'; interface ReportForTwoColumnsProps { - title: string - data: ReportDataWithTwoColumns[] + title: string; + data: ReportDataWithTwoColumns[]; } export const ReportForTwoColumns = ({ title, data }: ReportForTwoColumnsProps) => { const renderRows = () => { return data.map((row) => ( - + {row.name} {row.valueList[0]?.unit ? `${row.valueList[0].value}${row.valueList[0].unit}` : row.valueList[0].value} {row.valueList.slice(1).map((data) => ( - + {`${data.value}${data.unit}`} ))} - )) - } + )); + }; return ( <> {title} - +
    Name @@ -49,7 +49,7 @@ export const ReportForTwoColumns = ({ title, data }: ReportForTwoColumnsProps) =
    - ) -} + ); +}; -export default ReportForTwoColumns +export default ReportForTwoColumns; diff --git a/frontend/src/components/Common/ReportForTwoColumns/style.tsx b/frontend/src/components/Common/ReportForTwoColumns/style.tsx index edcb6921a5..2058951209 100644 --- a/frontend/src/components/Common/ReportForTwoColumns/style.tsx +++ b/frontend/src/components/Common/ReportForTwoColumns/style.tsx @@ -1,25 +1,25 @@ -import { styled } from '@mui/material/styles' -import { TableCell, TableRow } from '@mui/material' -import { MetricSelectionWrapper } from '@src/components/Metrics/MetricsStep/style' -import { tableCellClasses } from '@mui/material/TableCell' -import { theme } from '@src/theme' +import { styled } from '@mui/material/styles'; +import { TableCell, TableRow } from '@mui/material'; +import { MetricSelectionWrapper } from '@src/components/Metrics/MetricsStep/style'; +import { tableCellClasses } from '@mui/material/TableCell'; +import { theme } from '@src/theme'; -export const Container = styled(MetricSelectionWrapper)({}) +export const Container = styled(MetricSelectionWrapper)({}); -export const Row = styled(TableRow)({}) +export const Row = styled(TableRow)({}); export const StyledTableCell = styled(TableCell)(() => ({ [`&.${tableCellClasses.head}`]: { backgroundColor: theme.palette.secondary.dark, fontWeight: 600, }, -})) +})); export const BorderTableCell = styled(TableCell)(() => ({ border: '0.06rem solid #E0E0E0', borderRight: 'none', color: theme.palette.secondary.contrastText, -})) +})); export const ColumnTableCell = styled(BorderTableCell)(() => ({ borderLeft: 'none', -})) +})); diff --git a/frontend/src/components/Common/ReportGrid/ReportCard/index.tsx b/frontend/src/components/Common/ReportGrid/ReportCard/index.tsx index 6a646c2edf..7096386401 100644 --- a/frontend/src/components/Common/ReportGrid/ReportCard/index.tsx +++ b/frontend/src/components/Common/ReportGrid/ReportCard/index.tsx @@ -2,43 +2,43 @@ import { StyledItemSection, StyledReportCard, StyledReportCardTitle, -} from '@src/components/Common/ReportGrid/ReportCard/style' -import React, { HTMLAttributes } from 'react' -import { ReportCardItem, ReportCardItemProps } from '@src/components/Common/ReportGrid/ReportCardItem' -import { GRID_CONFIG } from '@src/constants/commons' -import { Loading } from '@src/components/Loading' +} from '@src/components/Common/ReportGrid/ReportCard/style'; +import React, { HTMLAttributes } from 'react'; +import { ReportCardItem, ReportCardItemProps } from '@src/components/Common/ReportGrid/ReportCardItem'; +import { GRID_CONFIG } from '@src/constants/commons'; +import { Loading } from '@src/components/Loading'; interface ReportCardProps extends HTMLAttributes { - title: string - items?: ReportCardItemProps[] | null - xs: number + title: string; + items?: ReportCardItemProps[] | null; + xs: number; } export const ReportCard = ({ title, items, xs }: ReportCardProps) => { - const defaultFlex = 1 + const defaultFlex = 1; const getReportItems = () => { - let style = GRID_CONFIG.FULL + let style = GRID_CONFIG.FULL; switch (xs) { case GRID_CONFIG.HALF.XS: - style = GRID_CONFIG.HALF - break + style = GRID_CONFIG.HALF; + break; case GRID_CONFIG.FULL.XS: - style = GRID_CONFIG.FULL - break + style = GRID_CONFIG.FULL; + break; } const getFlex = (length: number) => { if (length <= 1) { - return defaultFlex + return defaultFlex; } else { switch (xs) { case GRID_CONFIG.FULL.XS: - return GRID_CONFIG.FULL.FLEX + return GRID_CONFIG.FULL.FLEX; case GRID_CONFIG.HALF.XS: - return GRID_CONFIG.HALF.FLEX + return GRID_CONFIG.HALF.FLEX; } } - } + }; return ( @@ -58,8 +58,8 @@ export const ReportCard = ({ title, items, xs }: ReportCardProps) => { ) )} - ) - } + ); + }; return ( @@ -67,5 +67,5 @@ export const ReportCard = ({ title, items, xs }: ReportCardProps) => { {title} {items && getReportItems()} - ) -} + ); +}; diff --git a/frontend/src/components/Common/ReportGrid/ReportCard/style.tsx b/frontend/src/components/Common/ReportGrid/ReportCard/style.tsx index 887ee9ab55..0099582117 100644 --- a/frontend/src/components/Common/ReportGrid/ReportCard/style.tsx +++ b/frontend/src/components/Common/ReportGrid/ReportCard/style.tsx @@ -1,6 +1,6 @@ -import { theme } from '@src/theme' -import styled from '@emotion/styled' -import { Typography } from '@mui/material' +import { theme } from '@src/theme'; +import styled from '@emotion/styled'; +import { Typography } from '@mui/material'; export const StyledReportCard = styled.div({ position: 'relative', @@ -10,16 +10,16 @@ export const StyledReportCard = styled.div({ border: theme.main.cardBorder, background: theme.main.color, boxShadow: theme.main.cardShadow, -}) +}); export const StyledItemSection = styled.div({ display: 'flex', alignItems: 'center', minWidth: '25%', padding: '0.75rem 0', -}) +}); export const StyledReportCardTitle = styled(Typography)({ fontWeight: 500, fontSize: '1rem', -}) +}); diff --git a/frontend/src/components/Common/ReportGrid/ReportCardItem/index.tsx b/frontend/src/components/Common/ReportGrid/ReportCardItem/index.tsx index 150eebb7bf..51c7c5e45d 100644 --- a/frontend/src/components/Common/ReportGrid/ReportCardItem/index.tsx +++ b/frontend/src/components/Common/ReportGrid/ReportCardItem/index.tsx @@ -7,17 +7,17 @@ import { StyledValue, StyledValueSection, StyledWrapper, -} from '@src/components/Common/ReportGrid/ReportCardItem/style' -import DividingLine from '@src/assets/DividingLine.svg' -import React, { HTMLAttributes } from 'react' -import { Tooltip } from '@mui/material' +} from '@src/components/Common/ReportGrid/ReportCardItem/style'; +import DividingLine from '@src/assets/DividingLine.svg'; +import React, { HTMLAttributes } from 'react'; +import { Tooltip } from '@mui/material'; export interface ReportCardItemProps extends HTMLAttributes { - value: number - isToFixed?: boolean - extraValue?: string - subtitle: string - showDividingLine?: boolean + value: number; + isToFixed?: boolean; + extraValue?: string; + subtitle: string; + showDividingLine?: boolean; } export const ReportCardItem = ({ @@ -43,5 +43,5 @@ export const ReportCardItem = ({ - ) -} + ); +}; diff --git a/frontend/src/components/Common/ReportGrid/ReportCardItem/style.tsx b/frontend/src/components/Common/ReportGrid/ReportCardItem/style.tsx index 8600c4237a..a43565e11e 100644 --- a/frontend/src/components/Common/ReportGrid/ReportCardItem/style.tsx +++ b/frontend/src/components/Common/ReportGrid/ReportCardItem/style.tsx @@ -1,24 +1,24 @@ -import styled from '@emotion/styled' -import { Typography } from '@mui/material' -import { theme } from '@src/theme' +import styled from '@emotion/styled'; +import { Typography } from '@mui/material'; +import { theme } from '@src/theme'; export const StyledItem = styled.div({ display: 'flex', alignItems: 'center', width: '100%', overflow: 'hidden', -}) +}); export const StyledContent = styled('div')({ width: '100%', display: 'flex', alignItems: 'end', justifyContent: 'space-between', -}) +}); export const StyledWrapper = styled('div')({ width: '100%', -}) +}); export const StyledValue = styled(Typography)({ fontSize: '2rem', @@ -29,7 +29,7 @@ export const StyledValue = styled(Typography)({ [theme.breakpoints.down('lg')]: { fontSize: '1.8rem', }, -}) +}); export const StyledSubtitle = styled('div')({ width: '90%', @@ -43,7 +43,7 @@ export const StyledSubtitle = styled('div')({ fontStyle: 'normal', color: theme.main.secondColor, opacity: 0.65, -}) +}); export const StyledDividingLine = styled.img({ marginRight: '2rem', @@ -52,7 +52,7 @@ export const StyledDividingLine = styled.img({ [theme.breakpoints.down('lg')]: { marginRight: '1.5rem', }, -}) +}); export const StyledValueSection = styled.div({ width: '100%', @@ -60,7 +60,7 @@ export const StyledValueSection = styled.div({ flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between', -}) +}); export const StyledExtraValue = styled.div({ width: '100%', @@ -69,4 +69,4 @@ export const StyledExtraValue = styled.div({ paddingTop: '0.4rem', marginLeft: '0.8rem', whiteSpace: 'nowrap', -}) +}); diff --git a/frontend/src/components/Common/ReportGrid/ReportTitle/index.tsx b/frontend/src/components/Common/ReportGrid/ReportTitle/index.tsx index f2f14f1473..2564b8dcaf 100644 --- a/frontend/src/components/Common/ReportGrid/ReportTitle/index.tsx +++ b/frontend/src/components/Common/ReportGrid/ReportTitle/index.tsx @@ -1,12 +1,12 @@ -import React from 'react' +import React from 'react'; import { StyledMetricsSign, StyledMetricsTitle, StyledMetricsTitleSection, -} from '@src/components/Common/ReportGrid/ReportTitle/style' +} from '@src/components/Common/ReportGrid/ReportTitle/style'; interface ReportTitleProps { - title: string + title: string; } export const ReportTitle = ({ title }: ReportTitleProps) => { return ( @@ -14,5 +14,5 @@ export const ReportTitle = ({ title }: ReportTitleProps) => { {title} - ) -} + ); +}; diff --git a/frontend/src/components/Common/ReportGrid/ReportTitle/style.tsx b/frontend/src/components/Common/ReportGrid/ReportTitle/style.tsx index 987d6ab8eb..b03f9f4e54 100644 --- a/frontend/src/components/Common/ReportGrid/ReportTitle/style.tsx +++ b/frontend/src/components/Common/ReportGrid/ReportTitle/style.tsx @@ -1,24 +1,24 @@ -import { styled } from '@mui/material/styles' -import { theme } from '@src/theme' -import { Typography } from '@mui/material' +import { styled } from '@mui/material/styles'; +import { theme } from '@src/theme'; +import { Typography } from '@mui/material'; export const StyledMetricsTitleSection = styled('div')({ display: 'flex', -}) + alignItems: 'center', +}); export const StyledMetricsSign = styled('canvas')({ - margin: '0.2rem 0.5rem 0 0.5rem', + margin: '0.2rem 0.5rem', height: '1rem', width: '0.3rem', background: theme.main.backgroundColor, -}) +}); export const StyledMetricsTitle = styled(Typography)({ fontWeight: 600, fontSize: '1rem', fontFamily: theme.main.font.secondary, textAlign: 'start', - marginBottom: '1rem', textOverflow: 'ellipsis', whiteSpace: 'nowrap', -}) +}); diff --git a/frontend/src/components/Common/ReportGrid/index.tsx b/frontend/src/components/Common/ReportGrid/index.tsx index 58d267f372..edf8538afd 100644 --- a/frontend/src/components/Common/ReportGrid/index.tsx +++ b/frontend/src/components/Common/ReportGrid/index.tsx @@ -1,46 +1,46 @@ -import React from 'react' -import { Grid } from '@mui/material' -import { ReportCard } from '@src/components/Common/ReportGrid/ReportCard' -import { GRID_CONFIG } from '@src/constants/commons' -import { ReportCardItemProps } from '@src/components/Common/ReportGrid/ReportCardItem' +import React from 'react'; +import { Grid } from '@mui/material'; +import { ReportCard } from '@src/components/Common/ReportGrid/ReportCard'; +import { GRID_CONFIG } from '@src/constants/commons'; +import { ReportCardItemProps } from '@src/components/Common/ReportGrid/ReportCardItem'; export interface ReportGridProps { - lastGrid?: boolean - reportDetails: ReportDetailProps[] + lastGrid?: boolean; + reportDetails: ReportDetailProps[]; } export interface ReportDetailProps { - title: string - items?: ReportCardItemProps[] | null + title: string; + items?: ReportCardItemProps[] | null; } export const ReportGrid = ({ lastGrid, reportDetails }: ReportGridProps) => { const getXS = (index: number) => { if (needTakeUpAWholeLine(index)) { - return GRID_CONFIG.FULL.XS + return GRID_CONFIG.FULL.XS; } else if (reportDetails.length > 1) { - return GRID_CONFIG.HALF.XS + return GRID_CONFIG.HALF.XS; } else { - return GRID_CONFIG.FULL.XS + return GRID_CONFIG.FULL.XS; } - } + }; const needTakeUpAWholeLine = (index: number) => { - const size = reportDetails.length - const isOddSize = size % 2 === 1 - return isOddSize && lastGrid && size - 1 == index - } + const size = reportDetails.length; + const isOddSize = size % 2 === 1; + return isOddSize && lastGrid && size - 1 == index; + }; return ( {reportDetails.map((detail, index) => { - const xs = getXS(index) + const xs = getXS(index); return ( - ) + ); })} - ) -} + ); +}; diff --git a/frontend/src/components/Common/WarningNotification/index.tsx b/frontend/src/components/Common/WarningNotification/index.tsx index d0ade48fdf..a4b9e30532 100644 --- a/frontend/src/components/Common/WarningNotification/index.tsx +++ b/frontend/src/components/Common/WarningNotification/index.tsx @@ -1,22 +1,22 @@ -import { useEffect, useState } from 'react' -import { StyledAlert, WarningBar } from './style' -import { DURATION } from '@src/constants/commons' +import { useEffect, useState } from 'react'; +import { StyledAlert, WarningBar } from './style'; +import { DURATION } from '@src/constants/commons'; export const WarningNotification = (props: { message: string }) => { - const { message } = props - const [open, setOpen] = useState(true) + const { message } = props; + const [open, setOpen] = useState(true); useEffect(() => { const timer = setTimeout(() => { - setOpen(false) - }, DURATION.ERROR_MESSAGE_TIME) + setOpen(false); + }, DURATION.ERROR_MESSAGE_TIME); return () => { - clearTimeout(timer) - } - }, []) + clearTimeout(timer); + }; + }, []); return ( {message} - ) -} + ); +}; diff --git a/frontend/src/components/Common/WarningNotification/style.tsx b/frontend/src/components/Common/WarningNotification/style.tsx index 0dd6fc6fec..1d5153871a 100644 --- a/frontend/src/components/Common/WarningNotification/style.tsx +++ b/frontend/src/components/Common/WarningNotification/style.tsx @@ -1,21 +1,21 @@ -import { Snackbar } from '@mui/material' -import MuiAlert, { AlertProps } from '@mui/material/Alert' -import { styled } from '@mui/material/styles' -import { forwardRef } from 'react' -import { Z_INDEX } from '@src/constants/commons' +import { Snackbar } from '@mui/material'; +import MuiAlert, { AlertProps } from '@mui/material/Alert'; +import { styled } from '@mui/material/styles'; +import { forwardRef } from 'react'; +import { Z_INDEX } from '@src/constants/commons'; export const WarningBar = styled(Snackbar)({ position: 'relative', display: 'flex', justifyContent: 'center', zIndex: Z_INDEX.SNACKBARS, -}) +}); const Alert = forwardRef(function Alert(props, ref) { - return -}) + return ; +}); export const StyledAlert = styled(Alert)({ position: 'absolute', marginTop: '6rem', -}) +}); diff --git a/frontend/src/components/ErrorContent/index.tsx b/frontend/src/components/ErrorContent/index.tsx index b313a9419d..a32dacf8f9 100644 --- a/frontend/src/components/ErrorContent/index.tsx +++ b/frontend/src/components/ErrorContent/index.tsx @@ -1,6 +1,6 @@ -import { useNavigate } from 'react-router-dom' -import ErrorIcon from '@src/assets/ErrorIcon.svg' -import React from 'react' +import { useNavigate } from 'react-router-dom'; +import ErrorIcon from '@src/assets/ErrorIcon.svg'; +import React from 'react'; import { Container, ErrorImg, @@ -12,16 +12,16 @@ import { Okay, ErrorInfo, RetryButton, -} from '@src/components/ErrorContent/style' -import { ROUTE } from '@src/constants/router' -import { MESSAGE } from '@src/constants/resources' +} from '@src/components/ErrorContent/style'; +import { ROUTE } from '@src/constants/router'; +import { MESSAGE } from '@src/constants/resources'; export const ErrorContent = () => { - const navigate = useNavigate() + const navigate = useNavigate(); const backToHomePage = () => { - navigate(ROUTE.BASE_PAGE) - } + navigate(ROUTE.BASE_PAGE); + }; return ( @@ -39,5 +39,5 @@ export const ErrorContent = () => { Go to homepage - ) -} + ); +}; diff --git a/frontend/src/components/ErrorContent/style.tsx b/frontend/src/components/ErrorContent/style.tsx index 8eb8325d59..4f2fb23ed2 100644 --- a/frontend/src/components/ErrorContent/style.tsx +++ b/frontend/src/components/ErrorContent/style.tsx @@ -1,7 +1,7 @@ -import styled from '@emotion/styled' -import { theme } from '@src/theme' -import { Button } from '@mui/material' -import { basicButtonStyle } from '@src/components/Metrics/MetricsStepper/style' +import styled from '@emotion/styled'; +import { theme } from '@src/theme'; +import { Button } from '@mui/material'; +import { basicButtonStyle } from '@src/components/Metrics/MetricsStepper/style'; export const Container = styled.div({ display: 'flex', @@ -12,23 +12,23 @@ export const Container = styled.div({ padding: '6rem', minHeight: '30rem', minWidth: '60rem', -}) +}); export const ErrorTitle = styled.div({ paddingLeft: '6rem', position: 'relative', -}) +}); export const ErrorImg = styled.img({ height: '7rem', -}) +}); export const Some = styled.text({ fontSize: '2.5rem', fontWeight: '600', fontFamily: 'system-ui', paddingLeft: '1rem', -}) +}); export const Error = styled.text({ paddingLeft: '1rem', @@ -37,20 +37,20 @@ export const Error = styled.text({ fontFamily: 'system-ui', letterSpacing: '-0.5rem', color: 'firebrick', -}) +}); export const ErrorMessage = styled.div({ display: 'flex', flexDirection: 'row', paddingRight: '12rem', -}) +}); export const OhNo = styled.text({ fontSize: '7rem', fontWeight: '750', fontFamily: 'system-ui', letterSpacing: '0.2rem', -}) +}); export const Okay = styled.text({ margin: 'auto 0', @@ -59,14 +59,14 @@ export const Okay = styled.text({ fontWeight: '250', fontFamily: 'system-ui', color: theme.main.backgroundColor, -}) +}); export const ErrorInfo = styled.div({ padding: '2rem', fontSize: '1rem', fontWeight: '250', fontFamily: 'system-ui', -}) +}); export const RetryButton = styled(Button)({ ...basicButtonStyle, @@ -78,4 +78,4 @@ export const RetryButton = styled(Button)({ '&:hover': { background: theme.main.backgroundColor, }, -}) +}); diff --git a/frontend/src/components/ErrorNotification/index.tsx b/frontend/src/components/ErrorNotification/index.tsx index 67119778ee..a318c11fd5 100644 --- a/frontend/src/components/ErrorNotification/index.tsx +++ b/frontend/src/components/ErrorNotification/index.tsx @@ -1,10 +1,10 @@ -import { ErrorBar, StyledAlert } from './style' +import { ErrorBar, StyledAlert } from './style'; export const ErrorNotification = (props: { message: string }) => { - const { message } = props + const { message } = props; return ( {message} - ) -} + ); +}; diff --git a/frontend/src/components/ErrorNotification/style.tsx b/frontend/src/components/ErrorNotification/style.tsx index 08266bad78..730ffce63b 100644 --- a/frontend/src/components/ErrorNotification/style.tsx +++ b/frontend/src/components/ErrorNotification/style.tsx @@ -1,21 +1,21 @@ -import { Snackbar } from '@mui/material' -import { styled } from '@mui/material/styles' -import { forwardRef } from 'react' -import MuiAlert, { AlertProps } from '@mui/material/Alert' -import { Z_INDEX } from '@src/constants/commons' +import { Snackbar } from '@mui/material'; +import { styled } from '@mui/material/styles'; +import { forwardRef } from 'react'; +import MuiAlert, { AlertProps } from '@mui/material/Alert'; +import { Z_INDEX } from '@src/constants/commons'; export const ErrorBar = styled(Snackbar)({ position: 'relative', display: 'flex', justifyContent: 'center', zIndex: Z_INDEX.SNACKBARS, -}) +}); const Alert = forwardRef(function Alert(props, ref) { - return -}) + return ; +}); export const StyledAlert = styled(Alert)({ position: 'absolute', marginTop: '6rem', -}) +}); diff --git a/frontend/src/components/HomeGuide/index.tsx b/frontend/src/components/HomeGuide/index.tsx index 872c19f79a..4048239244 100644 --- a/frontend/src/components/HomeGuide/index.tsx +++ b/frontend/src/components/HomeGuide/index.tsx @@ -1,71 +1,71 @@ -import { useNavigate } from 'react-router-dom' -import { useAppDispatch } from '@src/hooks/useAppDispatch' -import { resetImportedData, updateBasicConfigState, updateProjectCreatedState } from '@src/context/config/configSlice' -import React, { useState } from 'react' -import { updateMetricsImportedData } from '@src/context/Metrics/metricsSlice' -import { resetStep } from '@src/context/stepper/StepperSlice' -import { WarningNotification } from '@src/components/Common/WarningNotification' -import { convertToNewFileConfig, NewFileConfig, OldFileConfig } from '@src/fileConfig/fileConfig' -import { GuideButton, HomeGuideContainer, StyledStack } from '@src/components/HomeGuide/style' -import { MESSAGE } from '@src/constants/resources' -import { ROUTE } from '@src/constants/router' +import { useNavigate } from 'react-router-dom'; +import { useAppDispatch } from '@src/hooks/useAppDispatch'; +import { resetImportedData, updateBasicConfigState, updateProjectCreatedState } from '@src/context/config/configSlice'; +import React, { useState } from 'react'; +import { updateMetricsImportedData } from '@src/context/Metrics/metricsSlice'; +import { resetStep } from '@src/context/stepper/StepperSlice'; +import { WarningNotification } from '@src/components/Common/WarningNotification'; +import { convertToNewFileConfig, NewFileConfig, OldFileConfig } from '@src/fileConfig/fileConfig'; +import { GuideButton, HomeGuideContainer, StyledStack } from '@src/components/HomeGuide/style'; +import { MESSAGE } from '@src/constants/resources'; +import { ROUTE } from '@src/constants/router'; export const HomeGuide = () => { - const navigate = useNavigate() - const dispatch = useAppDispatch() - const [validConfig, setValidConfig] = useState(true) + const navigate = useNavigate(); + const dispatch = useAppDispatch(); + const [validConfig, setValidConfig] = useState(true); - const getImportFileElement = () => document.getElementById('importJson') as HTMLInputElement + const getImportFileElement = () => document.getElementById('importJson') as HTMLInputElement; const isValidImportedConfig = (config: NewFileConfig) => { try { const { projectName, metrics, dateRange: { startDate, endDate }, - } = config - return projectName || startDate || endDate || metrics.length > 0 + } = config; + return projectName || startDate || endDate || metrics.length > 0; } catch { - return false + return false; } - } + }; const handleChange = (e: React.ChangeEvent) => { - const input = e.target.files?.[0] - const reader = new FileReader() + const input = e.target.files?.[0]; + const reader = new FileReader(); if (input) { reader.onload = () => { if (reader.result && typeof reader.result === 'string') { - const importedConfig: OldFileConfig | NewFileConfig = JSON.parse(reader.result) - const config: NewFileConfig = convertToNewFileConfig(importedConfig) + const importedConfig: OldFileConfig | NewFileConfig = JSON.parse(reader.result); + const config: NewFileConfig = convertToNewFileConfig(importedConfig); if (isValidImportedConfig(config)) { - dispatch(updateProjectCreatedState(false)) - dispatch(updateBasicConfigState(config)) - dispatch(updateMetricsImportedData(config)) - navigate(ROUTE.METRICS_PAGE) + dispatch(updateProjectCreatedState(false)); + dispatch(updateBasicConfigState(config)); + dispatch(updateMetricsImportedData(config)); + navigate(ROUTE.METRICS_PAGE); } else { - setValidConfig(false) + setValidConfig(false); } } - const fileInput = getImportFileElement() - fileInput.value = '' - } - reader.readAsText(input, 'utf-8') + const fileInput = getImportFileElement(); + fileInput.value = ''; + }; + reader.readAsText(input, 'utf-8'); } - } + }; const openFileImportBox = () => { - setValidConfig(true) - dispatch(resetImportedData()) - dispatch(resetStep()) - const fileInput = getImportFileElement() - fileInput.click() - } + setValidConfig(true); + dispatch(resetImportedData()); + dispatch(resetStep()); + const fileInput = getImportFileElement(); + fileInput.click(); + }; const createNewProject = () => { - dispatch(resetStep()) - dispatch(resetImportedData()) - navigate(ROUTE.METRICS_PAGE) - } + dispatch(resetStep()); + dispatch(resetImportedData()); + navigate(ROUTE.METRICS_PAGE); + }; return ( @@ -76,5 +76,5 @@ export const HomeGuide = () => { Create a new project - ) -} + ); +}; diff --git a/frontend/src/components/HomeGuide/style.tsx b/frontend/src/components/HomeGuide/style.tsx index 4c5deb8e13..d62e97b986 100644 --- a/frontend/src/components/HomeGuide/style.tsx +++ b/frontend/src/components/HomeGuide/style.tsx @@ -1,7 +1,7 @@ -import { theme } from '@src/theme' -import Button, { ButtonProps } from '@mui/material/Button' -import styled from '@emotion/styled' -import Stack from '@mui/material/Stack' +import { theme } from '@src/theme'; +import Button, { ButtonProps } from '@mui/material/Button'; +import styled from '@emotion/styled'; +import Stack from '@mui/material/Stack'; export const basicStyle = { backgroundColor: theme.main.backgroundColor, @@ -14,7 +14,7 @@ export const basicStyle = { width: '80%', maxWidth: '15rem', }, -} +}; export const GuideButton = styled(Button)({ ...basicStyle, '&:hover': { @@ -26,16 +26,16 @@ export const GuideButton = styled(Button)({ '&:focus': { ...basicStyle, }, -}) +}); export const StyledStack = styled(Stack)({ position: 'absolute', top: '50%', left: '50%', transform: 'translate(-50%,-50%)', -}) +}); export const HomeGuideContainer = styled.div({ height: '44rem', position: 'relative', -}) +}); diff --git a/frontend/src/components/Loading/index.tsx b/frontend/src/components/Loading/index.tsx index fefd9904d7..1b95071d3c 100644 --- a/frontend/src/components/Loading/index.tsx +++ b/frontend/src/components/Loading/index.tsx @@ -1,10 +1,10 @@ -import { CircularProgress } from '@mui/material' -import { LoadingDrop, LoadingTypography } from './style' +import { CircularProgress } from '@mui/material'; +import { LoadingDrop, LoadingTypography } from './style'; export interface LoadingProps { - message?: string - size?: string - backgroundColor?: string + message?: string; + size?: string; + backgroundColor?: string; } export const Loading = ({ message, size = '8rem', backgroundColor }: LoadingProps) => { @@ -13,5 +13,5 @@ export const Loading = ({ message, size = '8rem', backgroundColor }: LoadingProp {message && {message}} - ) -} + ); +}; diff --git a/frontend/src/components/Loading/style.tsx b/frontend/src/components/Loading/style.tsx index 2e76bb00c6..736ad78df8 100644 --- a/frontend/src/components/Loading/style.tsx +++ b/frontend/src/components/Loading/style.tsx @@ -1,7 +1,7 @@ -import { styled } from '@mui/material/styles' -import { Backdrop, Typography } from '@mui/material' -import { theme } from '@src/theme' -import { Z_INDEX } from '@src/constants/commons' +import { styled } from '@mui/material/styles'; +import { Backdrop, Typography } from '@mui/material'; +import { theme } from '@src/theme'; +import { Z_INDEX } from '@src/constants/commons'; export const LoadingDrop = styled(Backdrop)({ position: 'absolute', @@ -12,10 +12,10 @@ export const LoadingDrop = styled(Backdrop)({ flexDirection: 'column', alignItems: 'center', justifyContent: 'center', -}) +}); export const LoadingTypography = styled(Typography)({ fontSize: '1rem', marginTop: '2rem', color: theme.main.secondColor, -}) +}); diff --git a/frontend/src/components/Metrics/ConfigStep/BasicInfo/RequiredMetrics/index.tsx b/frontend/src/components/Metrics/ConfigStep/BasicInfo/RequiredMetrics/index.tsx index d3e82f9d51..d9155bbd80 100644 --- a/frontend/src/components/Metrics/ConfigStep/BasicInfo/RequiredMetrics/index.tsx +++ b/frontend/src/components/Metrics/ConfigStep/BasicInfo/RequiredMetrics/index.tsx @@ -1,53 +1,53 @@ -import { Checkbox, FormHelperText, InputLabel, ListItemText, MenuItem, Select, SelectChangeEvent } from '@mui/material' -import { SELECTED_VALUE_SEPARATOR } from '@src/constants/commons' -import { REQUIRED_DATA } from '@src/constants/resources' -import { useEffect, useState } from 'react' -import { RequireDataSelections } from '@src/components/Metrics/ConfigStep/BasicInfo/RequiredMetrics/style' -import { useAppDispatch, useAppSelector } from '@src/hooks/useAppDispatch' -import { selectConfig, updateMetrics } from '@src/context/config/configSlice' +import { Checkbox, FormHelperText, InputLabel, ListItemText, MenuItem, Select, SelectChangeEvent } from '@mui/material'; +import { SELECTED_VALUE_SEPARATOR } from '@src/constants/commons'; +import { REQUIRED_DATA } from '@src/constants/resources'; +import { useEffect, useState } from 'react'; +import { RequireDataSelections } from '@src/components/Metrics/ConfigStep/BasicInfo/RequiredMetrics/style'; +import { useAppDispatch, useAppSelector } from '@src/hooks/useAppDispatch'; +import { selectConfig, updateMetrics } from '@src/context/config/configSlice'; export const RequiredMetrics = () => { - const dispatch = useAppDispatch() - const configData = useAppSelector(selectConfig) - const { metrics } = configData.basic - const [isEmptyRequireData, setIsEmptyProjectData] = useState(false) + const dispatch = useAppDispatch(); + const configData = useAppSelector(selectConfig); + const { metrics } = configData.basic; + const [isEmptyRequireData, setIsEmptyProjectData] = useState(false); useEffect(() => { - metrics && dispatch(updateMetrics(metrics)) - }, [metrics, dispatch]) + metrics && dispatch(updateMetrics(metrics)); + }, [metrics, dispatch]); - const [isAllSelected, setIsAllSelected] = useState(false) + const [isAllSelected, setIsAllSelected] = useState(false); const handleSelectOptionsChange = (selectOptions: string | string[]) => { if (selectOptions.includes(REQUIRED_DATA.All) && !isAllSelected) { - setIsAllSelected(true) - selectOptions = Object.values(REQUIRED_DATA) + setIsAllSelected(true); + selectOptions = Object.values(REQUIRED_DATA); } else if (selectOptions.length == Object.values(REQUIRED_DATA).length - 1 && !isAllSelected) { - setIsAllSelected(true) - selectOptions = Object.values(REQUIRED_DATA) + setIsAllSelected(true); + selectOptions = Object.values(REQUIRED_DATA); } else if (selectOptions.includes(REQUIRED_DATA.All)) { - setIsAllSelected(false) - selectOptions = selectOptions.slice(1) + setIsAllSelected(false); + selectOptions = selectOptions.slice(1); } else if (!selectOptions.includes(REQUIRED_DATA.All) && isAllSelected) { - setIsAllSelected(false) - selectOptions = [] + setIsAllSelected(false); + selectOptions = []; } - return selectOptions - } + return selectOptions; + }; const handleRequireDataChange = (event: SelectChangeEvent) => { const { target: { value }, - } = event + } = event; - dispatch(updateMetrics(handleSelectOptionsChange(value))) - handleSelectOptionsChange(value).length === 0 ? setIsEmptyProjectData(true) : setIsEmptyProjectData(false) - } + dispatch(updateMetrics(handleSelectOptionsChange(value))); + handleSelectOptionsChange(value).length === 0 ? setIsEmptyProjectData(true) : setIsEmptyProjectData(false); + }; const handleRenderSelectOptions = (selected: string[]) => { if (selected.includes(REQUIRED_DATA.All)) { - return selected.slice(1).join(SELECTED_VALUE_SEPARATOR) + return selected.slice(1).join(SELECTED_VALUE_SEPARATOR); } - return selected.join(SELECTED_VALUE_SEPARATOR) - } + return selected.join(SELECTED_VALUE_SEPARATOR); + }; return ( <> @@ -70,5 +70,5 @@ export const RequiredMetrics = () => { {isEmptyRequireData && Metrics is required} - ) -} + ); +}; diff --git a/frontend/src/components/Metrics/ConfigStep/BasicInfo/RequiredMetrics/style.tsx b/frontend/src/components/Metrics/ConfigStep/BasicInfo/RequiredMetrics/style.tsx index 1bcb1d6b7e..d4de907ef2 100644 --- a/frontend/src/components/Metrics/ConfigStep/BasicInfo/RequiredMetrics/style.tsx +++ b/frontend/src/components/Metrics/ConfigStep/BasicInfo/RequiredMetrics/style.tsx @@ -1,8 +1,8 @@ -import { styled } from '@mui/material/styles' -import { FormControl } from '@mui/material' +import { styled } from '@mui/material/styles'; +import { FormControl } from '@mui/material'; export const RequireDataSelections = styled(FormControl)({ width: '100%', paddingBottom: '1rem', marginTop: '1.25rem', -}) +}); diff --git a/frontend/src/components/Metrics/ConfigStep/BasicInfo/index.tsx b/frontend/src/components/Metrics/ConfigStep/BasicInfo/index.tsx index 5260507d14..bcf5e13d4e 100644 --- a/frontend/src/components/Metrics/ConfigStep/BasicInfo/index.tsx +++ b/frontend/src/components/Metrics/ConfigStep/BasicInfo/index.tsx @@ -1,10 +1,10 @@ -import { Radio, RadioGroup } from '@mui/material' -import { useState } from 'react' -import { CALENDAR } from '@src/constants/resources' -import { DEFAULT_HELPER_TEXT } from '@src/constants/commons' -import { DateRangePicker } from '@src/components/Metrics/ConfigStep/DateRangePicker' -import { CollectionDateLabel, ProjectNameInput, StyledFormControlLabel } from './style' -import { useAppDispatch, useAppSelector } from '@src/hooks/useAppDispatch' +import { Radio, RadioGroup } from '@mui/material'; +import { useState } from 'react'; +import { CALENDAR } from '@src/constants/resources'; +import { DEFAULT_HELPER_TEXT } from '@src/constants/commons'; +import { DateRangePicker } from '@src/components/Metrics/ConfigStep/DateRangePicker'; +import { CollectionDateLabel, ProjectNameInput, StyledFormControlLabel } from './style'; +import { useAppDispatch, useAppSelector } from '@src/hooks/useAppDispatch'; import { selectCalendarType, selectProjectName, @@ -14,18 +14,18 @@ import { updatePipelineToolVerifyState, updateProjectName, updateSourceControlVerifyState, -} from '@src/context/config/configSlice' -import { WarningNotification } from '@src/components/Common/WarningNotification' -import { ConfigSectionContainer } from '@src/components/Common/ConfigForms' -import { ConfigSelectionTitle } from '@src/components/Metrics/MetricsStep/style' -import { RequiredMetrics } from '@src/components/Metrics/ConfigStep/BasicInfo/RequiredMetrics' +} from '@src/context/config/configSlice'; +import { WarningNotification } from '@src/components/Common/WarningNotification'; +import { ConfigSectionContainer } from '@src/components/Common/ConfigForms'; +import { ConfigSelectionTitle } from '@src/components/Metrics/MetricsStep/style'; +import { RequiredMetrics } from '@src/components/Metrics/ConfigStep/BasicInfo/RequiredMetrics'; const BasicInfo = () => { - const dispatch = useAppDispatch() - const projectName = useAppSelector(selectProjectName) - const calendarType = useAppSelector(selectCalendarType) - const warningMessage = useAppSelector(selectWarningMessage) - const [isEmptyProjectName, setIsEmptyProjectName] = useState(false) + const dispatch = useAppDispatch(); + const projectName = useAppSelector(selectProjectName); + const calendarType = useAppSelector(selectCalendarType); + const warningMessage = useAppSelector(selectWarningMessage); + const [isEmptyProjectName, setIsEmptyProjectName] = useState(false); return ( <> @@ -38,11 +38,11 @@ const BasicInfo = () => { variant='standard' value={projectName} onFocus={(e) => { - setIsEmptyProjectName(e.target.value === '') + setIsEmptyProjectName(e.target.value === ''); }} onChange={(e) => { - dispatch(updateProjectName(e.target.value)) - setIsEmptyProjectName(e.target.value === '') + dispatch(updateProjectName(e.target.value)); + setIsEmptyProjectName(e.target.value === ''); }} error={isEmptyProjectName} helperText={isEmptyProjectName ? 'Project name is required' : DEFAULT_HELPER_TEXT} @@ -51,10 +51,10 @@ const BasicInfo = () => { { - dispatch(updateBoardVerifyState(false)) - dispatch(updatePipelineToolVerifyState(false)) - dispatch(updateSourceControlVerifyState(false)) - dispatch(updateCalendarType(e.target.value)) + dispatch(updateBoardVerifyState(false)); + dispatch(updatePipelineToolVerifyState(false)); + dispatch(updateSourceControlVerifyState(false)); + dispatch(updateCalendarType(e.target.value)); }} > } label={CALENDAR.REGULAR} /> @@ -64,7 +64,7 @@ const BasicInfo = () => { - ) -} + ); +}; -export default BasicInfo +export default BasicInfo; diff --git a/frontend/src/components/Metrics/ConfigStep/BasicInfo/style.tsx b/frontend/src/components/Metrics/ConfigStep/BasicInfo/style.tsx index 3b76b1df39..d23aa90e08 100644 --- a/frontend/src/components/Metrics/ConfigStep/BasicInfo/style.tsx +++ b/frontend/src/components/Metrics/ConfigStep/BasicInfo/style.tsx @@ -1,10 +1,10 @@ -import { css, styled } from '@mui/material/styles' -import { FormControlLabel, TextField } from '@mui/material' -import { theme } from '@src/theme' +import { css, styled } from '@mui/material/styles'; +import { FormControlLabel, TextField } from '@mui/material'; +import { theme } from '@src/theme'; export const ProjectNameInput = styled(TextField)({ width: '100%', -}) +}); export const StyledFormControlLabel = styled(FormControlLabel)` ${css` @@ -14,7 +14,7 @@ export const StyledFormControlLabel = styled(FormControlLabel)` } } `} -` +`; export const CollectionDateLabel = styled('div')({ width: '100%', @@ -22,4 +22,4 @@ export const CollectionDateLabel = styled('div')({ fontSize: '0.8rem', lineHeight: '2em', boxSizing: 'border-box', -}) +}); diff --git a/frontend/src/components/Metrics/ConfigStep/Board/index.tsx b/frontend/src/components/Metrics/ConfigStep/Board/index.tsx index 56ef9c1117..710a055db5 100644 --- a/frontend/src/components/Metrics/ConfigStep/Board/index.tsx +++ b/frontend/src/components/Metrics/ConfigStep/Board/index.tsx @@ -1,9 +1,9 @@ -import { InputLabel, ListItemText, MenuItem, Select } from '@mui/material' -import { REGEX } from '@src/constants/regex' -import { DEFAULT_HELPER_TEXT, EMPTY_STRING } from '@src/constants/commons' -import { BOARD_TYPES, CONFIG_TITLE, EMAIL, BOARD_TOKEN } from '@src/constants/resources' -import { FormEvent, useEffect, useState } from 'react' -import { useAppDispatch, useAppSelector } from '@src/hooks/useAppDispatch' +import { InputLabel, ListItemText, MenuItem, Select } from '@mui/material'; +import { REGEX } from '@src/constants/regex'; +import { DEFAULT_HELPER_TEXT, EMPTY_STRING } from '@src/constants/commons'; +import { BOARD_TYPES, CONFIG_TITLE, EMAIL, BOARD_TOKEN } from '@src/constants/resources'; +import { FormEvent, useEffect, useState } from 'react'; +import { useAppDispatch, useAppSelector } from '@src/hooks/useAppDispatch'; import { selectBoard, selectDateRange, @@ -12,33 +12,33 @@ import { updateBoard, updateBoardVerifyState, updateJiraVerifyResponse, -} from '@src/context/config/configSlice' -import { useVerifyBoardEffect } from '@src/hooks/useVerifyBoardEffect' -import { ErrorNotification } from '@src/components/ErrorNotification' -import { NoCardPop } from '@src/components/Metrics/ConfigStep/NoDoneCardPop' -import { Loading } from '@src/components/Loading' -import { ResetButton, VerifyButton } from '@src/components/Common/Buttons' +} from '@src/context/config/configSlice'; +import { useVerifyBoardEffect } from '@src/hooks/useVerifyBoardEffect'; +import { ErrorNotification } from '@src/components/ErrorNotification'; +import { NoCardPop } from '@src/components/Metrics/ConfigStep/NoDoneCardPop'; +import { Loading } from '@src/components/Loading'; +import { ResetButton, VerifyButton } from '@src/components/Common/Buttons'; import { ConfigSectionContainer, StyledButtonGroup, StyledForm, StyledTextField, StyledTypeSelections, -} from '@src/components/Common/ConfigForms' -import dayjs from 'dayjs' -import { updateMetricsState, updateTreatFlagCardAsBlock } from '@src/context/Metrics/metricsSlice' -import { ConfigSelectionTitle } from '@src/components/Metrics/MetricsStep/style' -import { findCaseInsensitiveType } from '@src/utils/util' +} from '@src/components/Common/ConfigForms'; +import dayjs from 'dayjs'; +import { updateMetricsState, updateTreatFlagCardAsBlock } from '@src/context/Metrics/metricsSlice'; +import { ConfigSelectionTitle } from '@src/components/Metrics/MetricsStep/style'; +import { findCaseInsensitiveType } from '@src/utils/util'; export const Board = () => { - const dispatch = useAppDispatch() - const isVerified = useAppSelector(selectIsBoardVerified) - const boardFields = useAppSelector(selectBoard) - const DateRange = useAppSelector(selectDateRange) - const isProjectCreated = useAppSelector(selectIsProjectCreated) - const [isShowNoDoneCard, setIsNoDoneCard] = useState(false) - const { verifyJira, isLoading, errorMessage } = useVerifyBoardEffect() - const type = findCaseInsensitiveType(Object.values(BOARD_TYPES), boardFields.type) + const dispatch = useAppDispatch(); + const isVerified = useAppSelector(selectIsBoardVerified); + const boardFields = useAppSelector(selectBoard); + const DateRange = useAppSelector(selectDateRange); + const isProjectCreated = useAppSelector(selectIsProjectCreated); + const [isShowNoDoneCard, setIsNoDoneCard] = useState(false); + const { verifyJira, isLoading, errorMessage } = useVerifyBoardEffect(); + const type = findCaseInsensitiveType(Object.values(BOARD_TYPES), boardFields.type); const [fields, setFields] = useState([ { key: 'Board', @@ -76,19 +76,19 @@ export const Board = () => { isRequired: true, isValid: true, }, - ]) + ]); const [isDisableVerifyButton, setIsDisableVerifyButton] = useState( !fields.every((field) => field.value && field.isValid) - ) + ); const initBoardFields = () => { const newFields = fields.map((field, index) => { - field.value = !index ? BOARD_TYPES.JIRA : EMPTY_STRING - return field - }) - setFields(newFields) - dispatch(updateBoardVerifyState(false)) - } + field.value = !index ? BOARD_TYPES.JIRA : EMPTY_STRING; + return field; + }); + setFields(newFields); + dispatch(updateBoardVerifyState(false)); + }; const updateFields = ( fields: { key: string; value: string; isRequired: boolean; isValid: boolean }[], @@ -97,33 +97,33 @@ export const Board = () => { ) => { return fields.map((field, fieldIndex) => { if (fieldIndex !== index) { - return field + return field; } - const newValue = value.trim() - const isValueEmpty = !!newValue + const newValue = value.trim(); + const isValueEmpty = !!newValue; const isValueValid = field.key === EMAIL ? REGEX.EMAIL.test(newValue) : field.key === BOARD_TOKEN ? REGEX.BOARD_TOKEN.test(newValue) - : true + : true; return { ...field, value: newValue, isRequired: isValueEmpty, isValid: isValueValid, - } - }) - } + }; + }); + }; useEffect(() => { const isFieldInvalid = (field: { key: string; value: string; isRequired: boolean; isValid: boolean }) => - field.isRequired && field.isValid && !!field.value + field.isRequired && field.isValid && !!field.value; const isAllFieldsValid = (fields: { key: string; value: string; isRequired: boolean; isValid: boolean }[]) => - fields.some((field) => !isFieldInvalid(field)) - setIsDisableVerifyButton(isAllFieldsValid(fields)) - }, [fields]) + fields.some((field) => !isFieldInvalid(field)); + setIsDisableVerifyButton(isAllFieldsValid(fields)); + }, [fields]); const onFormUpdate = (index: number, value: string) => { const newFieldsValue = !index @@ -133,15 +133,15 @@ export const Board = () => { value: !index ? value : EMPTY_STRING, isValid: true, isRequired: true, - } + }; }) - : updateFields(fields, index, value) - setFields(newFieldsValue) - dispatch(updateBoardVerifyState(false)) - } + : updateFields(fields, index, value); + setFields(newFieldsValue); + dispatch(updateBoardVerifyState(false)); + }; const updateBoardFields = (e: FormEvent) => { - e.preventDefault() + e.preventDefault(); dispatch( updateBoard({ type: fields[0].value, @@ -151,14 +151,14 @@ export const Board = () => { site: fields[4].value, token: fields[5].value, }) - ) - } + ); + }; const handleSubmitBoardFields = async (e: FormEvent) => { - dispatch(updateTreatFlagCardAsBlock(true)) - updateBoardFields(e) - const msg = `${fields[2].value}:${fields[5].value}` - const encodeToken = `Basic ${btoa(msg)}` + dispatch(updateTreatFlagCardAsBlock(true)); + updateBoardFields(e); + const msg = `${fields[2].value}:${fields[5].value}`; + const encodeToken = `Basic ${btoa(msg)}`; const params = { type: fields[0].value, boardId: fields[1].value, @@ -167,33 +167,33 @@ export const Board = () => { token: encodeToken, startTime: dayjs(DateRange.startDate).valueOf(), endTime: dayjs(DateRange.endDate).valueOf(), - } + }; await verifyJira(params).then((res) => { if (res) { - dispatch(updateBoardVerifyState(res.isBoardVerify)) - dispatch(updateJiraVerifyResponse(res.response)) - res.isBoardVerify && dispatch(updateMetricsState({ ...res.response, isProjectCreated })) - setIsNoDoneCard(!res.haveDoneCard) + dispatch(updateBoardVerifyState(res.isBoardVerify)); + dispatch(updateJiraVerifyResponse(res.response)); + res.isBoardVerify && dispatch(updateMetricsState({ ...res.response, isProjectCreated })); + setIsNoDoneCard(!res.haveDoneCard); } - }) - } + }); + }; const handleResetBoardFields = () => { - initBoardFields() - setIsDisableVerifyButton(true) - dispatch(updateBoardVerifyState(false)) - } + initBoardFields(); + setIsDisableVerifyButton(true); + dispatch(updateBoardVerifyState(false)); + }; const updateFieldHelpText = (field: { key: string; isRequired: boolean; isValid: boolean }) => { - const { key, isRequired, isValid } = field + const { key, isRequired, isValid } = field; if (!isRequired) { - return `${key} is required` + return `${key} is required`; } if ((key === EMAIL || key === BOARD_TOKEN) && !isValid) { - return `${key} is invalid` + return `${key} is invalid`; } - return DEFAULT_HELPER_TEXT - } + return DEFAULT_HELPER_TEXT; + }; return ( @@ -214,7 +214,7 @@ export const Board = () => { labelId='board-type-checkbox-label' value={field.value} onChange={(e) => { - onFormUpdate(index, e.target.value) + onFormUpdate(index, e.target.value); }} > {Object.values(BOARD_TYPES).map((data) => ( @@ -233,7 +233,7 @@ export const Board = () => { variant='standard' value={field.value} onChange={(e) => { - onFormUpdate(index, e.target.value) + onFormUpdate(index, e.target.value); }} error={!field.isRequired || !field.isValid} type={field.key === 'Token' ? 'password' : 'text'} @@ -254,5 +254,5 @@ export const Board = () => { - ) -} + ); +}; diff --git a/frontend/src/components/Metrics/ConfigStep/BranchSelection/index.tsx b/frontend/src/components/Metrics/ConfigStep/BranchSelection/index.tsx index 3772c1360c..183e4bd34d 100644 --- a/frontend/src/components/Metrics/ConfigStep/BranchSelection/index.tsx +++ b/frontend/src/components/Metrics/ConfigStep/BranchSelection/index.tsx @@ -1,33 +1,33 @@ -import React, { useMemo } from 'react' -import _ from 'lodash' -import { BranchSelectionWrapper } from '@src/components/Metrics/MetricsStep/DeploymentFrequencySettings/PipelineMetricSelection/style' -import MultiAutoComplete from '@src/components/Common/MultiAutoComplete' -import { selectBranches } from '@src/context/config/configSlice' -import { store } from '@src/store' +import React, { useMemo } from 'react'; +import _ from 'lodash'; +import { BranchSelectionWrapper } from '@src/components/Metrics/MetricsStep/DeploymentFrequencySettings/PipelineMetricSelection/style'; +import MultiAutoComplete from '@src/components/Common/MultiAutoComplete'; +import { selectBranches } from '@src/context/config/configSlice'; +import { store } from '@src/store'; export interface BranchSelectionProps { - id: number - organization: string - pipelineName: string - branches: string[] - onUpdatePipeline: (id: number, label: string, value: any) => void + id: number; + organization: string; + pipelineName: string; + branches: string[]; + onUpdatePipeline: (id: number, label: string, value: any) => void; } export const BranchSelection = (props: BranchSelectionProps) => { - const { id, organization, pipelineName, branches, onUpdatePipeline } = props - const branchesOptions: string[] = selectBranches(store.getState(), organization, pipelineName) + const { id, organization, pipelineName, branches, onUpdatePipeline } = props; + const branchesOptions: string[] = selectBranches(store.getState(), organization, pipelineName); const isAllBranchesSelected = useMemo( () => !_.isEmpty(branchesOptions) && _.isEqual(branches.length, branchesOptions.length), [branches, branchesOptions] - ) + ); const handleBranchChange = (event: React.SyntheticEvent, value: string[]) => { - let selectBranches = value + let selectBranches = value; if (_.isEqual(selectBranches[selectBranches.length - 1], 'All')) { /* istanbul ignore next */ - selectBranches = _.isEqual(branchesOptions.length, branches.length) ? [] : branchesOptions + selectBranches = _.isEqual(branchesOptions.length, branches.length) ? [] : branchesOptions; } - onUpdatePipeline(id, 'Branches', selectBranches) - } + onUpdatePipeline(id, 'Branches', selectBranches); + }; return ( @@ -40,5 +40,5 @@ export const BranchSelection = (props: BranchSelectionProps) => { isSelectAll={isAllBranchesSelected} /> - ) -} + ); +}; diff --git a/frontend/src/components/Metrics/ConfigStep/DateRangePicker/index.tsx b/frontend/src/components/Metrics/ConfigStep/DateRangePicker/index.tsx index c510c91a96..02692d0da0 100644 --- a/frontend/src/components/Metrics/ConfigStep/DateRangePicker/index.tsx +++ b/frontend/src/components/Metrics/ConfigStep/DateRangePicker/index.tsx @@ -1,26 +1,26 @@ -import dayjs from 'dayjs' -import { LocalizationProvider } from '@mui/x-date-pickers/LocalizationProvider' -import { AdapterDayjs } from '@mui/x-date-pickers/AdapterDayjs' -import { useAppDispatch, useAppSelector } from '@src/hooks/useAppDispatch' +import dayjs from 'dayjs'; +import { LocalizationProvider } from '@mui/x-date-pickers/LocalizationProvider'; +import { AdapterDayjs } from '@mui/x-date-pickers/AdapterDayjs'; +import { useAppDispatch, useAppSelector } from '@src/hooks/useAppDispatch'; import { selectDateRange, updateBoardVerifyState, updateDateRange, updatePipelineToolVerifyState, updateSourceControlVerifyState, -} from '@src/context/config/configSlice' -import { StyledDateRangePicker, StyledDateRangePickerContainer } from './style' -import CalendarTodayIcon from '@mui/icons-material/CalendarToday' -import { Z_INDEX } from '@src/constants/commons' +} from '@src/context/config/configSlice'; +import { StyledDateRangePicker, StyledDateRangePickerContainer } from './style'; +import CalendarTodayIcon from '@mui/icons-material/CalendarToday'; +import { Z_INDEX } from '@src/constants/commons'; export const DateRangePicker = () => { - const dispatch = useAppDispatch() - const { startDate, endDate } = useAppSelector(selectDateRange) + const dispatch = useAppDispatch(); + const { startDate, endDate } = useAppSelector(selectDateRange); const updateVerifyStates = () => { - dispatch(updateBoardVerifyState(false)) - dispatch(updatePipelineToolVerifyState(false)) - dispatch(updateSourceControlVerifyState(false)) - } + dispatch(updateBoardVerifyState(false)); + dispatch(updatePipelineToolVerifyState(false)); + dispatch(updateSourceControlVerifyState(false)); + }; const changeStartDate = (value: any) => { if (value === null) { dispatch( @@ -28,17 +28,17 @@ export const DateRangePicker = () => { startDate: null, endDate: null, }) - ) + ); } else { dispatch( updateDateRange({ startDate: value.startOf('date').format('YYYY-MM-DDTHH:mm:ss.SSSZ'), endDate: value.endOf('date').add(13, 'day').format('YYYY-MM-DDTHH:mm:ss.SSSZ'), }) - ) + ); } - updateVerifyStates() - } + updateVerifyStates(); + }; const changeEndDate = (value: any) => { if (value === null) { @@ -47,14 +47,14 @@ export const DateRangePicker = () => { startDate: startDate, endDate: null, }) - ) + ); } else { dispatch( updateDateRange({ startDate: startDate, endDate: value.endOf('date').format('YYYY-MM-DDTHH:mm:ss.SSSZ') }) - ) + ); } - updateVerifyStates() - } + updateVerifyStates(); + }; return ( @@ -94,5 +94,5 @@ export const DateRangePicker = () => { /> - ) -} + ); +}; diff --git a/frontend/src/components/Metrics/ConfigStep/DateRangePicker/style.tsx b/frontend/src/components/Metrics/ConfigStep/DateRangePicker/style.tsx index 6ff55f7bff..4a00422c0c 100644 --- a/frontend/src/components/Metrics/ConfigStep/DateRangePicker/style.tsx +++ b/frontend/src/components/Metrics/ConfigStep/DateRangePicker/style.tsx @@ -1,6 +1,6 @@ -import { styled } from '@mui/material/styles' -import { theme } from '@src/theme' -import { DatePicker } from '@mui/x-date-pickers' +import { styled } from '@mui/material/styles'; +import { theme } from '@src/theme'; +import { DatePicker } from '@mui/x-date-pickers'; export const StyledDateRangePickerContainer = styled('div')({ display: 'flex', width: '100%', @@ -10,7 +10,7 @@ export const StyledDateRangePickerContainer = styled('div')({ [theme.breakpoints.down('sm')]: { flexDirection: 'column', }, -}) +}); export const StyledDateRangePicker = styled(DatePicker)({ width: '50%', @@ -20,4 +20,4 @@ export const StyledDateRangePicker = styled(DatePicker)({ [theme.breakpoints.down('sm')]: { width: '100%', }, -}) +}); diff --git a/frontend/src/components/Metrics/ConfigStep/MetricsTypeCheckbox/index.tsx b/frontend/src/components/Metrics/ConfigStep/MetricsTypeCheckbox/index.tsx index d02e8558b5..4ed86deb48 100644 --- a/frontend/src/components/Metrics/ConfigStep/MetricsTypeCheckbox/index.tsx +++ b/frontend/src/components/Metrics/ConfigStep/MetricsTypeCheckbox/index.tsx @@ -1,14 +1,14 @@ -import { Board } from '@src/components/Metrics/ConfigStep/Board' -import { useAppSelector } from '@src/hooks/useAppDispatch' -import { selectConfig } from '@src/context/config/configSlice' -import { PipelineTool } from '@src/components/Metrics/ConfigStep/PipelineTool' -import { SourceControl } from '@src/components/Metrics/ConfigStep/SourceControl' +import { Board } from '@src/components/Metrics/ConfigStep/Board'; +import { useAppSelector } from '@src/hooks/useAppDispatch'; +import { selectConfig } from '@src/context/config/configSlice'; +import { PipelineTool } from '@src/components/Metrics/ConfigStep/PipelineTool'; +import { SourceControl } from '@src/components/Metrics/ConfigStep/SourceControl'; export const MetricsTypeCheckbox = () => { - const configData = useAppSelector(selectConfig) - const { isShow: isShowBoard } = configData.board - const { isShow: isShowPipeline } = configData.pipelineTool - const { isShow: isShowSourceControl } = configData.sourceControl + const configData = useAppSelector(selectConfig); + const { isShow: isShowBoard } = configData.board; + const { isShow: isShowPipeline } = configData.pipelineTool; + const { isShow: isShowSourceControl } = configData.sourceControl; return ( <> @@ -16,5 +16,5 @@ export const MetricsTypeCheckbox = () => { {isShowPipeline && } {isShowSourceControl && } - ) -} + ); +}; diff --git a/frontend/src/components/Metrics/ConfigStep/NoDoneCardPop/index.tsx b/frontend/src/components/Metrics/ConfigStep/NoDoneCardPop/index.tsx index 14d40c2638..3d33002864 100644 --- a/frontend/src/components/Metrics/ConfigStep/NoDoneCardPop/index.tsx +++ b/frontend/src/components/Metrics/ConfigStep/NoDoneCardPop/index.tsx @@ -1,13 +1,13 @@ -import { DialogContent } from '@mui/material' -import { OkButton, StyledDialog } from '@src/components/Metrics/ConfigStep/NoDoneCardPop/style' +import { DialogContent } from '@mui/material'; +import { OkButton, StyledDialog } from '@src/components/Metrics/ConfigStep/NoDoneCardPop/style'; interface NoDoneCardPopProps { - isOpen: boolean - onClose: () => void + isOpen: boolean; + onClose: () => void; } export const NoCardPop = (props: NoDoneCardPopProps) => { - const { isOpen, onClose } = props + const { isOpen, onClose } = props; return ( @@ -15,5 +15,5 @@ export const NoCardPop = (props: NoDoneCardPopProps) => { Ok - ) -} + ); +}; diff --git a/frontend/src/components/Metrics/ConfigStep/NoDoneCardPop/style.tsx b/frontend/src/components/Metrics/ConfigStep/NoDoneCardPop/style.tsx index eabcc5f634..a9dbd58e25 100644 --- a/frontend/src/components/Metrics/ConfigStep/NoDoneCardPop/style.tsx +++ b/frontend/src/components/Metrics/ConfigStep/NoDoneCardPop/style.tsx @@ -1,6 +1,6 @@ -import { styled } from '@mui/material/styles' -import { theme } from '@src/theme' -import { Dialog } from '@mui/material' +import { styled } from '@mui/material/styles'; +import { theme } from '@src/theme'; +import { Dialog } from '@mui/material'; export const StyledDialog = styled(Dialog)` & .MuiDialog-paper { @@ -10,7 +10,7 @@ export const StyledDialog = styled(Dialog)` align-items: center; font-size: 1rem; } -` +`; export const OkButton = styled('button')({ width: '4rem', @@ -21,4 +21,4 @@ export const OkButton = styled('button')({ borderRadius: '0.3rem', color: 'White', backgroundColor: theme.main.backgroundColor, -}) +}); diff --git a/frontend/src/components/Metrics/ConfigStep/PipelineTool/index.tsx b/frontend/src/components/Metrics/ConfigStep/PipelineTool/index.tsx index 039b6b18f9..01b1c0ab07 100644 --- a/frontend/src/components/Metrics/ConfigStep/PipelineTool/index.tsx +++ b/frontend/src/components/Metrics/ConfigStep/PipelineTool/index.tsx @@ -1,15 +1,15 @@ -import { InputLabel, ListItemText, MenuItem, Select } from '@mui/material' -import { DEFAULT_HELPER_TEXT, EMPTY_STRING, ZERO } from '@src/constants/commons' -import { CONFIG_TITLE, PIPELINE_TOOL_TYPES, TOKEN_HELPER_TEXT } from '@src/constants/resources' -import { FormEvent, useEffect, useState } from 'react' +import { InputLabel, ListItemText, MenuItem, Select } from '@mui/material'; +import { DEFAULT_HELPER_TEXT, EMPTY_STRING, ZERO } from '@src/constants/commons'; +import { CONFIG_TITLE, PIPELINE_TOOL_TYPES, TOKEN_HELPER_TEXT } from '@src/constants/resources'; +import { FormEvent, useEffect, useState } from 'react'; import { ConfigSectionContainer, StyledButtonGroup, StyledForm, StyledTextField, StyledTypeSelections, -} from '@src/components/Common/ConfigForms' -import { useAppDispatch, useAppSelector } from '@src/hooks/useAppDispatch' +} from '@src/components/Common/ConfigForms'; +import { useAppDispatch, useAppSelector } from '@src/hooks/useAppDispatch'; import { isPipelineToolVerified, selectDateRange, @@ -18,24 +18,24 @@ import { updatePipelineTool, updatePipelineToolVerifyResponse, updatePipelineToolVerifyState, -} from '@src/context/config/configSlice' -import { useVerifyPipelineToolEffect } from '@src/hooks/useVerifyPipelineToolEffect' -import { ErrorNotification } from '@src/components/ErrorNotification' -import { Loading } from '@src/components/Loading' -import { ResetButton, VerifyButton } from '@src/components/Common/Buttons' -import { initDeploymentFrequencySettings, updatePipelineSettings } from '@src/context/Metrics/metricsSlice' -import { ConfigSelectionTitle } from '@src/components/Metrics/MetricsStep/style' -import { findCaseInsensitiveType } from '@src/utils/util' -import { REGEX } from '@src/constants/regex' +} from '@src/context/config/configSlice'; +import { useVerifyPipelineToolEffect } from '@src/hooks/useVerifyPipelineToolEffect'; +import { ErrorNotification } from '@src/components/ErrorNotification'; +import { Loading } from '@src/components/Loading'; +import { ResetButton, VerifyButton } from '@src/components/Common/Buttons'; +import { initDeploymentFrequencySettings, updatePipelineSettings } from '@src/context/Metrics/metricsSlice'; +import { ConfigSelectionTitle } from '@src/components/Metrics/MetricsStep/style'; +import { findCaseInsensitiveType } from '@src/utils/util'; +import { REGEX } from '@src/constants/regex'; export const PipelineTool = () => { - const dispatch = useAppDispatch() - const pipelineToolFields = useAppSelector(selectPipelineTool) - const DateRange = useAppSelector(selectDateRange) - const isVerified = useAppSelector(isPipelineToolVerified) - const isProjectCreated = useAppSelector(selectIsProjectCreated) - const { verifyPipelineTool, isLoading, errorMessage } = useVerifyPipelineToolEffect() - const type = findCaseInsensitiveType(Object.values(PIPELINE_TOOL_TYPES), pipelineToolFields.type) + const dispatch = useAppDispatch(); + const pipelineToolFields = useAppSelector(selectPipelineTool); + const DateRange = useAppSelector(selectDateRange); + const isVerified = useAppSelector(isPipelineToolVerified); + const isProjectCreated = useAppSelector(selectIsProjectCreated); + const { verifyPipelineTool, isLoading, errorMessage } = useVerifyPipelineToolEffect(); + const type = findCaseInsensitiveType(Object.values(PIPELINE_TOOL_TYPES), pipelineToolFields.type); const [fields, setFields] = useState([ { key: 'PipelineTool', @@ -49,96 +49,96 @@ export const PipelineTool = () => { isValid: true, isRequired: true, }, - ]) - const [isDisableVerifyButton, setIsDisableVerifyButton] = useState(!(fields[1].isValid && fields[1].value)) + ]); + const [isDisableVerifyButton, setIsDisableVerifyButton] = useState(!(fields[1].isValid && fields[1].value)); const initPipeLineFields = () => { const newFields = fields.map((field, index) => { - field.value = !index ? PIPELINE_TOOL_TYPES.BUILD_KITE : EMPTY_STRING - return field - }) - setFields(newFields) - dispatch(updatePipelineToolVerifyState(false)) - } + field.value = !index ? PIPELINE_TOOL_TYPES.BUILD_KITE : EMPTY_STRING; + return field; + }); + setFields(newFields); + dispatch(updatePipelineToolVerifyState(false)); + }; useEffect(() => { const isFieldInvalid = (field: { key: string; value: string; isRequired: boolean; isValid: boolean }) => - field.isRequired && field.isValid && !!field.value + field.isRequired && field.isValid && !!field.value; const isAllFieldsValid = (fields: { key: string; value: string; isRequired: boolean; isValid: boolean }[]) => - fields.some((field) => !isFieldInvalid(field)) - setIsDisableVerifyButton(isAllFieldsValid(fields)) - }, [fields]) + fields.some((field) => !isFieldInvalid(field)); + setIsDisableVerifyButton(isAllFieldsValid(fields)); + }, [fields]); const onFormUpdate = (index: number, value: string) => { const newFieldsValue = fields.map((field, fieldIndex) => { if (!index) { - field.value = !fieldIndex ? value : EMPTY_STRING + field.value = !fieldIndex ? value : EMPTY_STRING; } else if (!!index && !!fieldIndex) { return { ...field, value, isRequired: !!value, isValid: REGEX.BUILDKITE_TOKEN.test(value), - } + }; } - return field - }) - setFields(newFieldsValue) - dispatch(updatePipelineToolVerifyState(false)) + return field; + }); + setFields(newFieldsValue); + dispatch(updatePipelineToolVerifyState(false)); dispatch( updatePipelineTool({ type: fields[0].value, token: fields[1].value, }) - ) - } + ); + }; const updateFieldHelpText = (field: { key: string; isRequired: boolean; isValid: boolean }) => { - const { isRequired, isValid } = field + const { isRequired, isValid } = field; if (!isRequired) { - return TOKEN_HELPER_TEXT.RequiredTokenText + return TOKEN_HELPER_TEXT.RequiredTokenText; } if (!isValid) { - return TOKEN_HELPER_TEXT.InvalidTokenText + return TOKEN_HELPER_TEXT.InvalidTokenText; } - return DEFAULT_HELPER_TEXT - } + return DEFAULT_HELPER_TEXT; + }; const updatePipelineToolFields = (e: FormEvent) => { - e.preventDefault() + e.preventDefault(); dispatch( updatePipelineTool({ type: fields[0].value, token: fields[1].value, }) - ) - } + ); + }; const handleSubmitPipelineToolFields = async (e: FormEvent) => { - updatePipelineToolFields(e) + updatePipelineToolFields(e); const params = { type: fields[0].value, token: fields[1].value, startTime: DateRange.startDate, endTime: DateRange.endDate, - } + }; await verifyPipelineTool(params).then((res) => { if (res) { - dispatch(updatePipelineToolVerifyState(res.isPipelineToolVerified)) - dispatch(updatePipelineToolVerifyResponse(res.response)) - dispatch(initDeploymentFrequencySettings()) - res.isPipelineToolVerified && dispatch(updatePipelineSettings({ ...res.response, isProjectCreated })) + dispatch(updatePipelineToolVerifyState(res.isPipelineToolVerified)); + dispatch(updatePipelineToolVerifyResponse(res.response)); + dispatch(initDeploymentFrequencySettings()); + res.isPipelineToolVerified && dispatch(updatePipelineSettings({ ...res.response, isProjectCreated })); } - }) - } + }); + }; const handleResetPipelineToolFields = () => { - initPipeLineFields() - setIsDisableVerifyButton(true) - dispatch(updatePipelineToolVerifyState(false)) - } + initPipeLineFields(); + setIsDisableVerifyButton(true); + dispatch(updatePipelineToolVerifyState(false)); + }; return ( @@ -188,5 +188,5 @@ export const PipelineTool = () => { - ) -} + ); +}; diff --git a/frontend/src/components/Metrics/ConfigStep/SourceControl/index.tsx b/frontend/src/components/Metrics/ConfigStep/SourceControl/index.tsx index 5825ba8a19..1820321a4f 100644 --- a/frontend/src/components/Metrics/ConfigStep/SourceControl/index.tsx +++ b/frontend/src/components/Metrics/ConfigStep/SourceControl/index.tsx @@ -1,37 +1,37 @@ -import { FormEvent, useEffect, useState } from 'react' -import { REGEX } from '@src/constants/regex' -import { DEFAULT_HELPER_TEXT, EMPTY_STRING } from '@src/constants/commons' -import { CONFIG_TITLE, SOURCE_CONTROL_TYPES, TOKEN_HELPER_TEXT } from '@src/constants/resources' +import { FormEvent, useEffect, useState } from 'react'; +import { REGEX } from '@src/constants/regex'; +import { DEFAULT_HELPER_TEXT, EMPTY_STRING } from '@src/constants/commons'; +import { CONFIG_TITLE, SOURCE_CONTROL_TYPES, TOKEN_HELPER_TEXT } from '@src/constants/resources'; import { ConfigSectionContainer, StyledButtonGroup, StyledForm, StyledTextField, StyledTypeSelections, -} from '@src/components/Common/ConfigForms' -import { InputLabel, ListItemText, MenuItem, Select } from '@mui/material' -import { useAppDispatch, useAppSelector } from '@src/hooks/useAppDispatch' +} from '@src/components/Common/ConfigForms'; +import { InputLabel, ListItemText, MenuItem, Select } from '@mui/material'; +import { useAppDispatch, useAppSelector } from '@src/hooks/useAppDispatch'; import { isSourceControlVerified, selectDateRange, selectSourceControl, updateSourceControl, updateSourceControlVerifyState, -} from '@src/context/config/configSlice' -import { useVerifySourceControlEffect } from '@src/hooks/useVeritySourceControlEffect' -import { ErrorNotification } from '@src/components/ErrorNotification' -import { Loading } from '@src/components/Loading' -import { VerifyButton, ResetButton } from '@src/components/Common/Buttons' -import { ConfigSelectionTitle } from '@src/components/Metrics/MetricsStep/style' -import { findCaseInsensitiveType } from '@src/utils/util' +} from '@src/context/config/configSlice'; +import { useVerifySourceControlEffect } from '@src/hooks/useVeritySourceControlEffect'; +import { ErrorNotification } from '@src/components/ErrorNotification'; +import { Loading } from '@src/components/Loading'; +import { VerifyButton, ResetButton } from '@src/components/Common/Buttons'; +import { ConfigSelectionTitle } from '@src/components/Metrics/MetricsStep/style'; +import { findCaseInsensitiveType } from '@src/utils/util'; export const SourceControl = () => { - const dispatch = useAppDispatch() - const sourceControlFields = useAppSelector(selectSourceControl) - const DateRange = useAppSelector(selectDateRange) - const isVerified = useAppSelector(isSourceControlVerified) - const { verifyGithub, isLoading, errorMessage } = useVerifySourceControlEffect() - const type = findCaseInsensitiveType(Object.values(SOURCE_CONTROL_TYPES), sourceControlFields.type) + const dispatch = useAppDispatch(); + const sourceControlFields = useAppSelector(selectSourceControl); + const DateRange = useAppSelector(selectDateRange); + const isVerified = useAppSelector(isSourceControlVerified); + const { verifyGithub, isLoading, errorMessage } = useVerifySourceControlEffect(); + const type = findCaseInsensitiveType(Object.values(SOURCE_CONTROL_TYPES), sourceControlFields.type); const [fields, setFields] = useState([ { key: 'SourceControl', @@ -45,83 +45,83 @@ export const SourceControl = () => { isValid: true, isRequired: true, }, - ]) - const [isDisableVerifyButton, setIsDisableVerifyButton] = useState(!(fields[1].isValid && fields[1].value)) - const [sourceControlHelperText, setSourceControlHelperText] = useState('') + ]); + const [isDisableVerifyButton, setIsDisableVerifyButton] = useState(!(fields[1].isValid && fields[1].value)); + const [sourceControlHelperText, setSourceControlHelperText] = useState(''); const initSourceControlFields = () => { const newFields = fields.map((field, index) => { - field.value = index === 1 ? '' : SOURCE_CONTROL_TYPES.GITHUB - return field - }) - setFields(newFields) - dispatch(updateSourceControlVerifyState(false)) - } + field.value = index === 1 ? '' : SOURCE_CONTROL_TYPES.GITHUB; + return field; + }); + setFields(newFields); + dispatch(updateSourceControlVerifyState(false)); + }; useEffect(() => { const isFieldInvalid = (field: { key: string; value: string; isRequired: boolean; isValid: boolean }) => - field.isRequired && field.isValid && !!field.value + field.isRequired && field.isValid && !!field.value; const isAllFieldsValid = (fields: { key: string; value: string; isRequired: boolean; isValid: boolean }[]) => - fields.some((field) => !isFieldInvalid(field)) - setIsDisableVerifyButton(isAllFieldsValid(fields)) - }, [fields]) + fields.some((field) => !isFieldInvalid(field)); + setIsDisableVerifyButton(isAllFieldsValid(fields)); + }, [fields]); const updateSourceControlFields = (e: FormEvent) => { - e.preventDefault() + e.preventDefault(); dispatch( updateSourceControl({ type: fields[0].value, token: fields[1].value, }) - ) - } + ); + }; const handleSubmitSourceControlFields = async (e: FormEvent) => { - updateSourceControlFields(e) + updateSourceControlFields(e); const params = { type: fields[0].value, token: fields[1].value, startTime: DateRange.startDate, endTime: DateRange.endDate, - } + }; await verifyGithub(params).then((res) => { if (res) { - dispatch(updateSourceControlVerifyState(res.isSourceControlVerify)) - dispatch(updateSourceControlVerifyState(res.response)) + dispatch(updateSourceControlVerifyState(res.isSourceControlVerify)); + dispatch(updateSourceControlVerifyState(res.response)); } - }) - } + }); + }; const handleResetSourceControlFields = () => { - initSourceControlFields() - setIsDisableVerifyButton(true) - dispatch(updateSourceControlVerifyState(false)) - } + initSourceControlFields(); + setIsDisableVerifyButton(true); + dispatch(updateSourceControlVerifyState(false)); + }; const checkFieldValid = (value: string): boolean => { - let helperText = DEFAULT_HELPER_TEXT + let helperText = DEFAULT_HELPER_TEXT; if (value === EMPTY_STRING) { - helperText = TOKEN_HELPER_TEXT.RequiredTokenText + helperText = TOKEN_HELPER_TEXT.RequiredTokenText; } else if (!REGEX.GITHUB_TOKEN.test(value)) { - helperText = TOKEN_HELPER_TEXT.InvalidTokenText + helperText = TOKEN_HELPER_TEXT.InvalidTokenText; } - setSourceControlHelperText(helperText) - return helperText === DEFAULT_HELPER_TEXT - } + setSourceControlHelperText(helperText); + return helperText === DEFAULT_HELPER_TEXT; + }; const onFormUpdate = (index: number, value: string) => { const newFieldsValue = fields.map((field, fieldIndex) => { if (fieldIndex === index) { - field.value = value - field.isValid = checkFieldValid(value) + field.value = value; + field.isValid = checkFieldValid(value); } - return field - }) - setFields(newFieldsValue) - dispatch(updateSourceControlVerifyState(false)) - } + return field; + }); + setFields(newFieldsValue); + dispatch(updateSourceControlVerifyState(false)); + }; return ( @@ -169,5 +169,5 @@ export const SourceControl = () => { - ) -} + ); +}; diff --git a/frontend/src/components/Metrics/ConfigStep/index.tsx b/frontend/src/components/Metrics/ConfigStep/index.tsx index 5ef3757b32..f16ceaa493 100644 --- a/frontend/src/components/Metrics/ConfigStep/index.tsx +++ b/frontend/src/components/Metrics/ConfigStep/index.tsx @@ -1,20 +1,20 @@ -import { ConfigStepWrapper } from './style' -import { MetricsTypeCheckbox } from '@src/components/Metrics/ConfigStep/MetricsTypeCheckbox' -import BasicInfo from '@src/components/Metrics/ConfigStep/BasicInfo' -import { useLayoutEffect } from 'react' -import { useNotificationLayoutEffectInterface } from '@src/hooks/useNotificationLayoutEffect' +import { ConfigStepWrapper } from './style'; +import { MetricsTypeCheckbox } from '@src/components/Metrics/ConfigStep/MetricsTypeCheckbox'; +import BasicInfo from '@src/components/Metrics/ConfigStep/BasicInfo'; +import { useLayoutEffect } from 'react'; +import { useNotificationLayoutEffectInterface } from '@src/hooks/useNotificationLayoutEffect'; const ConfigStep = ({ resetProps }: useNotificationLayoutEffectInterface) => { useLayoutEffect(() => { - resetProps?.() - }, []) + resetProps?.(); + }, []); return ( - ) -} + ); +}; -export default ConfigStep +export default ConfigStep; diff --git a/frontend/src/components/Metrics/ConfigStep/style.tsx b/frontend/src/components/Metrics/ConfigStep/style.tsx index e6a2ce734c..c4685c3f91 100644 --- a/frontend/src/components/Metrics/ConfigStep/style.tsx +++ b/frontend/src/components/Metrics/ConfigStep/style.tsx @@ -1,5 +1,5 @@ -import { styled } from '@mui/material/styles' +import { styled } from '@mui/material/styles'; export const ConfigStepWrapper = styled('div')({ width: '100%', -}) +}); diff --git a/frontend/src/components/Metrics/MetricsStep/Classification/index.tsx b/frontend/src/components/Metrics/MetricsStep/Classification/index.tsx index 185ca54321..89ee5d98bc 100644 --- a/frontend/src/components/Metrics/MetricsStep/Classification/index.tsx +++ b/frontend/src/components/Metrics/MetricsStep/Classification/index.tsx @@ -1,25 +1,25 @@ -import React, { useState } from 'react' -import { useAppDispatch } from '@src/hooks/useAppDispatch' -import { saveTargetFields, selectClassificationWarningMessage } from '@src/context/Metrics/metricsSlice' -import { MetricsSettingTitle } from '@src/components/Common/MetricsSettingTitle' -import { useAppSelector } from '@src/hooks' -import { WarningNotification } from '@src/components/Common/WarningNotification' -import MultiAutoComplete from '@src/components/Common/MultiAutoComplete' +import React, { useState } from 'react'; +import { useAppDispatch } from '@src/hooks/useAppDispatch'; +import { saveTargetFields, selectClassificationWarningMessage } from '@src/context/Metrics/metricsSlice'; +import { MetricsSettingTitle } from '@src/components/Common/MetricsSettingTitle'; +import { useAppSelector } from '@src/hooks'; +import { WarningNotification } from '@src/components/Common/WarningNotification'; +import MultiAutoComplete from '@src/components/Common/MultiAutoComplete'; interface classificationProps { - title: string - label: string - targetFields: { name: string; key: string; flag: boolean }[] + title: string; + label: string; + targetFields: { name: string; key: string; flag: boolean }[]; } export const Classification = ({ targetFields, title, label }: classificationProps) => { - const dispatch = useAppDispatch() - const classificationWarningMessage = useAppSelector(selectClassificationWarningMessage) + const dispatch = useAppDispatch(); + const classificationWarningMessage = useAppSelector(selectClassificationWarningMessage); const classificationSettings = targetFields .filter((targetField) => targetField.flag) - .map((targetField) => targetField.name) - const [selectedClassification, setSelectedClassification] = useState(classificationSettings) - const isAllSelected = selectedClassification.length > 0 && selectedClassification.length === targetFields.length + .map((targetField) => targetField.name); + const [selectedClassification, setSelectedClassification] = useState(classificationSettings); + const isAllSelected = selectedClassification.length > 0 && selectedClassification.length === targetFields.length; const handleChange = (event: React.SyntheticEvent, value: string[]) => { const newClassificationSettings = @@ -27,14 +27,14 @@ export const Classification = ({ targetFields, title, label }: classificationPro ? isAllSelected ? [] : targetFields.map((targetField) => targetField.name) - : [...value] + : [...value]; const updatedTargetFields = targetFields.map((targetField) => ({ ...targetField, flag: newClassificationSettings.includes(targetField.name), - })) - setSelectedClassification(newClassificationSettings) - dispatch(saveTargetFields(updatedTargetFields)) - } + })); + setSelectedClassification(newClassificationSettings); + dispatch(saveTargetFields(updatedTargetFields)); + }; return ( <> @@ -49,5 +49,5 @@ export const Classification = ({ targetFields, title, label }: classificationPro isSelectAll={isAllSelected} /> - ) -} + ); +}; diff --git a/frontend/src/components/Metrics/MetricsStep/Crews/AssigneeFilter.tsx b/frontend/src/components/Metrics/MetricsStep/Crews/AssigneeFilter.tsx index 3833ba6efd..5f446fe5fb 100644 --- a/frontend/src/components/Metrics/MetricsStep/Crews/AssigneeFilter.tsx +++ b/frontend/src/components/Metrics/MetricsStep/Crews/AssigneeFilter.tsx @@ -1,17 +1,17 @@ -import { FormControlLabel, RadioGroup, Radio } from '@mui/material' -import React from 'react' -import { useAppDispatch } from '@src/hooks/useAppDispatch' -import { useAppSelector } from '@src/hooks' -import { selectAssigneeFilter, updateAssigneeFilter } from '@src/context/Metrics/metricsSlice' -import { AssigneeFilterContainer } from '@src/components/Metrics/MetricsStep/Crews/style' +import { FormControlLabel, RadioGroup, Radio } from '@mui/material'; +import React from 'react'; +import { useAppDispatch } from '@src/hooks/useAppDispatch'; +import { useAppSelector } from '@src/hooks'; +import { selectAssigneeFilter, updateAssigneeFilter } from '@src/context/Metrics/metricsSlice'; +import { AssigneeFilterContainer } from '@src/components/Metrics/MetricsStep/Crews/style'; export const AssigneeFilter = () => { - const dispatch = useAppDispatch() - const assigneeFilter = useAppSelector(selectAssigneeFilter) + const dispatch = useAppDispatch(); + const assigneeFilter = useAppSelector(selectAssigneeFilter); const handleChange = (event: React.ChangeEvent) => { - dispatch(updateAssigneeFilter(event.target.value)) - } + dispatch(updateAssigneeFilter(event.target.value)); + }; return ( @@ -26,5 +26,5 @@ export const AssigneeFilter = () => { } label='Historical assignee' /> - ) -} + ); +}; diff --git a/frontend/src/components/Metrics/MetricsStep/Crews/index.tsx b/frontend/src/components/Metrics/MetricsStep/Crews/index.tsx index e4ad1ffa30..d7505183f9 100644 --- a/frontend/src/components/Metrics/MetricsStep/Crews/index.tsx +++ b/frontend/src/components/Metrics/MetricsStep/Crews/index.tsx @@ -1,43 +1,43 @@ -import { FormHelperText } from '@mui/material' -import React, { useEffect, useState } from 'react' -import { MetricsSettingTitle } from '@src/components/Common/MetricsSettingTitle' -import { useAppDispatch } from '@src/hooks/useAppDispatch' -import { saveUsers, selectMetricsContent, savePipelineCrews } from '@src/context/Metrics/metricsSlice' -import { useAppSelector } from '@src/hooks' -import { AssigneeFilter } from '@src/components/Metrics/MetricsStep/Crews/AssigneeFilter' -import MultiAutoComplete from '@src/components/Common/MultiAutoComplete' -import { WarningMessage } from '@src/components/Metrics/MetricsStep/Crews/style' +import { FormHelperText } from '@mui/material'; +import React, { useEffect, useState } from 'react'; +import { MetricsSettingTitle } from '@src/components/Common/MetricsSettingTitle'; +import { useAppDispatch } from '@src/hooks/useAppDispatch'; +import { saveUsers, selectMetricsContent, savePipelineCrews } from '@src/context/Metrics/metricsSlice'; +import { useAppSelector } from '@src/hooks'; +import { AssigneeFilter } from '@src/components/Metrics/MetricsStep/Crews/AssigneeFilter'; +import MultiAutoComplete from '@src/components/Common/MultiAutoComplete'; +import { WarningMessage } from '@src/components/Metrics/MetricsStep/Crews/style'; interface crewsProps { - options: string[] - title: string - label: string - type?: string + options: string[]; + title: string; + label: string; + type?: string; } export const Crews = ({ options, title, label, type = 'board' }: crewsProps) => { - const isBoardCrews = type === 'board' - const dispatch = useAppDispatch() - const [isEmptyCrewData, setIsEmptyCrewData] = useState(false) - const { users, pipelineCrews } = useAppSelector(selectMetricsContent) - const [selectedCrews, setSelectedCrews] = useState(isBoardCrews ? users : pipelineCrews) - const isAllSelected = options.length > 0 && selectedCrews.length === options.length + const isBoardCrews = type === 'board'; + const dispatch = useAppDispatch(); + const [isEmptyCrewData, setIsEmptyCrewData] = useState(false); + const { users, pipelineCrews } = useAppSelector(selectMetricsContent); + const [selectedCrews, setSelectedCrews] = useState(isBoardCrews ? users : pipelineCrews); + const isAllSelected = options.length > 0 && selectedCrews.length === options.length; useEffect(() => { - setIsEmptyCrewData(selectedCrews.length === 0) - }, [selectedCrews]) + setIsEmptyCrewData(selectedCrews.length === 0); + }, [selectedCrews]); const handleCrewChange = (event: React.SyntheticEvent, value: string[]) => { if (value[value.length - 1] === 'All') { - setSelectedCrews(selectedCrews.length === options.length ? [] : options) - return + setSelectedCrews(selectedCrews.length === options.length ? [] : options); + return; } - setSelectedCrews([...value]) - } + setSelectedCrews([...value]); + }; useEffect(() => { - dispatch(isBoardCrews ? saveUsers(selectedCrews) : savePipelineCrews(selectedCrews)) - }, [selectedCrews, dispatch]) + dispatch(isBoardCrews ? saveUsers(selectedCrews) : savePipelineCrews(selectedCrews)); + }, [selectedCrews, dispatch]); return ( <> @@ -60,5 +60,5 @@ export const Crews = ({ options, title, label, type = 'board' }: crewsProps) => )} - ) -} + ); +}; diff --git a/frontend/src/components/Metrics/MetricsStep/Crews/style.tsx b/frontend/src/components/Metrics/MetricsStep/Crews/style.tsx index 54fc7f234f..70237504f2 100644 --- a/frontend/src/components/Metrics/MetricsStep/Crews/style.tsx +++ b/frontend/src/components/Metrics/MetricsStep/Crews/style.tsx @@ -1,10 +1,10 @@ -import { styled } from '@mui/material/styles' +import { styled } from '@mui/material/styles'; export const AssigneeFilterContainer = styled('div')({ display: 'grid', gridTemplateColumns: 'repeat(2, 1fr)', paddingTop: '1rem', -}) +}); export const WarningMessage = styled('span')({ color: 'red', -}) +}); diff --git a/frontend/src/components/Metrics/MetricsStep/CycleTime/FlagCard.tsx b/frontend/src/components/Metrics/MetricsStep/CycleTime/FlagCard.tsx index 1754e11821..c05c955fc0 100644 --- a/frontend/src/components/Metrics/MetricsStep/CycleTime/FlagCard.tsx +++ b/frontend/src/components/Metrics/MetricsStep/CycleTime/FlagCard.tsx @@ -1,23 +1,23 @@ -import React from 'react' -import { FlagCardItem, ItemCheckbox, ItemText } from '@src/components/Metrics/MetricsStep/CycleTime/style' -import { useAppDispatch } from '@src/hooks/useAppDispatch' -import { selectTreatFlagCardAsBlock, updateTreatFlagCardAsBlock } from '@src/context/Metrics/metricsSlice' -import { useAppSelector } from '@src/hooks' +import React from 'react'; +import { FlagCardItem, ItemCheckbox, ItemText } from '@src/components/Metrics/MetricsStep/CycleTime/style'; +import { useAppDispatch } from '@src/hooks/useAppDispatch'; +import { selectTreatFlagCardAsBlock, updateTreatFlagCardAsBlock } from '@src/context/Metrics/metricsSlice'; +import { useAppSelector } from '@src/hooks'; const FlagCard = () => { - const dispatch = useAppDispatch() - const flagCardAsBlock = useAppSelector(selectTreatFlagCardAsBlock) + const dispatch = useAppDispatch(); + const flagCardAsBlock = useAppSelector(selectTreatFlagCardAsBlock); const handleFlagCardAsBlock = () => { - dispatch(updateTreatFlagCardAsBlock(!flagCardAsBlock)) - } + dispatch(updateTreatFlagCardAsBlock(!flagCardAsBlock)); + }; return ( Consider the "Flag" as "Block" - ) -} + ); +}; -export default FlagCard +export default FlagCard; diff --git a/frontend/src/components/Metrics/MetricsStep/CycleTime/Table/CellAutoComplete.tsx b/frontend/src/components/Metrics/MetricsStep/CycleTime/Table/CellAutoComplete.tsx index be884d69f5..48d80187fd 100644 --- a/frontend/src/components/Metrics/MetricsStep/CycleTime/Table/CellAutoComplete.tsx +++ b/frontend/src/components/Metrics/MetricsStep/CycleTime/Table/CellAutoComplete.tsx @@ -1,35 +1,35 @@ -import React, { useState, useCallback, SyntheticEvent } from 'react' -import { Autocomplete } from '@mui/material' -import { StyledTextField } from '@src/components/Metrics/MetricsStep/CycleTime/Table/style' -import { CYCLE_TIME_LIST } from '@src/constants/resources' -import { Z_INDEX } from '@src/constants/commons' +import React, { useState, useCallback, SyntheticEvent } from 'react'; +import { Autocomplete } from '@mui/material'; +import { StyledTextField } from '@src/components/Metrics/MetricsStep/CycleTime/Table/style'; +import { CYCLE_TIME_LIST } from '@src/constants/resources'; +import { Z_INDEX } from '@src/constants/commons'; interface ICellAutoCompleteProps { - name: string - defaultSelected: string - onSelect: (name: string, value: string) => void - customRenderInput?: React.FC + name: string; + defaultSelected: string; + onSelect: (name: string, value: string) => void; + customRenderInput?: React.FC; } const CellAutoComplete = ({ name, defaultSelected, onSelect, customRenderInput }: ICellAutoCompleteProps) => { - const [selectedCycleTime, setSelectedCycleTime] = useState(defaultSelected) - const [inputValue, setInputValue] = useState('') + const [selectedCycleTime, setSelectedCycleTime] = useState(defaultSelected); + const [inputValue, setInputValue] = useState(''); const handleInputOnChange = useCallback( (event: SyntheticEvent, newInputValue: string) => { - setInputValue(newInputValue) + setInputValue(newInputValue); }, [setInputValue] - ) + ); const handleSelectOnChange = useCallback( (event: SyntheticEvent, value: string) => { - onSelect(name, value) - setSelectedCycleTime(value) + onSelect(name, value); + setSelectedCycleTime(value); }, [name, onSelect, setSelectedCycleTime] - ) + ); - const renderInput = customRenderInput || ((params) => ) + const renderInput = customRenderInput || ((params) => ); return ( - ) -} + ); +}; -export default CellAutoComplete +export default CellAutoComplete; diff --git a/frontend/src/components/Metrics/MetricsStep/CycleTime/Table/index.tsx b/frontend/src/components/Metrics/MetricsStep/CycleTime/Table/index.tsx index d0123161c9..46003a8c9b 100644 --- a/frontend/src/components/Metrics/MetricsStep/CycleTime/Table/index.tsx +++ b/frontend/src/components/Metrics/MetricsStep/CycleTime/Table/index.tsx @@ -1,19 +1,19 @@ -import React, { useCallback } from 'react' -import Table from '@mui/material/Table' -import TableBody from '@mui/material/TableBody' -import TableContainer from '@mui/material/TableContainer' -import TableHead from '@mui/material/TableHead' -import TableRow from '@mui/material/TableRow' -import Tooltip from '@mui/material/Tooltip' -import { useAppSelector } from '@src/hooks' -import { useAppDispatch } from '@src/hooks/useAppDispatch' -import { saveCycleTimeSettings, saveDoneColumn, selectMetricsContent } from '@src/context/Metrics/metricsSlice' -import { selectJiraColumns } from '@src/context/config/configSlice' -import { DONE, METRICS_CYCLE_SETTING_TABLE_HEADER } from '@src/constants/resources' -import { theme } from '@src/theme' -import CellAutoComplete from '@src/components/Metrics/MetricsStep/CycleTime/Table/CellAutoComplete' -import { StyledTableHeaderCell, StyledTableRowCell } from '@src/components/Metrics/MetricsStep/CycleTime/Table/style' -import EllipsisText from '@src/components/Common/EllipsisText' +import React, { useCallback } from 'react'; +import Table from '@mui/material/Table'; +import TableBody from '@mui/material/TableBody'; +import TableContainer from '@mui/material/TableContainer'; +import TableHead from '@mui/material/TableHead'; +import TableRow from '@mui/material/TableRow'; +import Tooltip from '@mui/material/Tooltip'; +import { useAppSelector } from '@src/hooks'; +import { useAppDispatch } from '@src/hooks/useAppDispatch'; +import { saveCycleTimeSettings, saveDoneColumn, selectMetricsContent } from '@src/context/Metrics/metricsSlice'; +import { selectJiraColumns } from '@src/context/config/configSlice'; +import { DONE, METRICS_CYCLE_SETTING_TABLE_HEADER } from '@src/constants/resources'; +import { theme } from '@src/theme'; +import CellAutoComplete from '@src/components/Metrics/MetricsStep/CycleTime/Table/CellAutoComplete'; +import { StyledTableHeaderCell, StyledTableRowCell } from '@src/components/Metrics/MetricsStep/CycleTime/Table/style'; +import EllipsisText from '@src/components/Common/EllipsisText'; export const columns = METRICS_CYCLE_SETTING_TABLE_HEADER.map( (config) => @@ -25,36 +25,36 @@ export const columns = METRICS_CYCLE_SETTING_TABLE_HEADER.map( ) : ( config.text - ) + ); } -) +); const CycleTimeTable = () => { - const dispatch = useAppDispatch() - const { cycleTimeSettings } = useAppSelector(selectMetricsContent) - const jiraColumns = useAppSelector(selectJiraColumns) + const dispatch = useAppDispatch(); + const { cycleTimeSettings } = useAppSelector(selectMetricsContent); + const jiraColumns = useAppSelector(selectJiraColumns); const jiraColumnsWithValue = jiraColumns?.map( (obj: { key: string; value: { name: string; statuses: string[] } }) => obj.value - ) + ); const rows = cycleTimeSettings.map((setting) => ({ ...setting, statuses: jiraColumnsWithValue.find((columnWithValue) => columnWithValue.name === setting.name)?.statuses, - })) + })); const resetRealDoneColumn = useCallback( (name: string, value: string) => { - const optionNamesWithDone = cycleTimeSettings.filter((item) => item.value === DONE).map((item) => item.name) + const optionNamesWithDone = cycleTimeSettings.filter((item) => item.value === DONE).map((item) => item.name); if (value === DONE) { - dispatch(saveDoneColumn([])) + dispatch(saveDoneColumn([])); } if (optionNamesWithDone.includes(name)) { - dispatch(saveDoneColumn([])) + dispatch(saveDoneColumn([])); } }, [cycleTimeSettings, dispatch] - ) + ); const saveCycleTimeOptions = useCallback( (name: string, value: string) => { const newCycleTimeSettings = cycleTimeSettings.map((item) => @@ -64,13 +64,13 @@ const CycleTimeTable = () => { value, } : item - ) + ); - resetRealDoneColumn(name, value) - dispatch(saveCycleTimeSettings(newCycleTimeSettings)) + resetRealDoneColumn(name, value); + dispatch(saveCycleTimeSettings(newCycleTimeSettings)); }, [cycleTimeSettings, dispatch, resetRealDoneColumn] - ) + ); return ( @@ -86,8 +86,8 @@ const CycleTimeTable = () => { {rows.map((row, index) => { - const row1Content = row.name - const row2Content = row.statuses?.join(', ') + const row1Content = row.name; + const row2Content = row.statuses?.join(', '); return ( {row1Content} @@ -100,12 +100,12 @@ const CycleTimeTable = () => { - ) + ); })} - ) -} + ); +}; -export default CycleTimeTable +export default CycleTimeTable; diff --git a/frontend/src/components/Metrics/MetricsStep/CycleTime/Table/style.tsx b/frontend/src/components/Metrics/MetricsStep/CycleTime/Table/style.tsx index d9f87389f3..730b2c2bff 100644 --- a/frontend/src/components/Metrics/MetricsStep/CycleTime/Table/style.tsx +++ b/frontend/src/components/Metrics/MetricsStep/CycleTime/Table/style.tsx @@ -1,7 +1,7 @@ -import { styled } from '@mui/material/styles' -import TableCell from '@mui/material/TableCell' -import TextField from '@mui/material/TextField' -import { theme } from '@src/theme' +import { styled } from '@mui/material/styles'; +import TableCell from '@mui/material/TableCell'; +import TextField from '@mui/material/TextField'; +import { theme } from '@src/theme'; export const StyledTableHeaderCell = styled(TableCell)({ padding: 0, @@ -16,7 +16,7 @@ export const StyledTableHeaderCell = styled(TableCell)({ paddingLeft: '1.5rem', }, borderBottom: 0, -}) +}); export const StyledTableRowCell = styled(TableCell)({ padding: 0, @@ -27,7 +27,7 @@ export const StyledTableRowCell = styled(TableCell)({ paddingLeft: '1rem', paddingRight: '1rem', borderBottom: `0.1rem solid ${theme.palette.secondary.dark}`, -}) +}); export const StyledTextField = styled(TextField)({ borderBottom: 0, @@ -58,4 +58,4 @@ export const StyledTextField = styled(TextField)({ '& .MuiOutlinedInput-notchedOutline': { borderColor: 'transparent', }, -}) +}); diff --git a/frontend/src/components/Metrics/MetricsStep/CycleTime/index.tsx b/frontend/src/components/Metrics/MetricsStep/CycleTime/index.tsx index 82788d3e4f..60c199fbde 100644 --- a/frontend/src/components/Metrics/MetricsStep/CycleTime/index.tsx +++ b/frontend/src/components/Metrics/MetricsStep/CycleTime/index.tsx @@ -1,20 +1,20 @@ -import { MetricsSettingTitle } from '@src/components/Common/MetricsSettingTitle' -import FlagCard from '@src/components/Metrics/MetricsStep/CycleTime/FlagCard' -import { selectCycleTimeWarningMessage } from '@src/context/Metrics/metricsSlice' -import { useAppSelector } from '@src/hooks' -import { WarningNotification } from '@src/components/Common/WarningNotification' -import { TIPS } from '@src/constants/resources' +import { MetricsSettingTitle } from '@src/components/Common/MetricsSettingTitle'; +import FlagCard from '@src/components/Metrics/MetricsStep/CycleTime/FlagCard'; +import { selectCycleTimeWarningMessage } from '@src/context/Metrics/metricsSlice'; +import { useAppSelector } from '@src/hooks'; +import { WarningNotification } from '@src/components/Common/WarningNotification'; +import { TIPS } from '@src/constants/resources'; import { StyledTooltip, TitleAndTooltipContainer, TooltipContainer, -} from '@src/components/Metrics/MetricsStep/CycleTime/style' -import { IconButton } from '@mui/material' -import InfoOutlinedIcon from '@mui/icons-material/InfoOutlined' -import CycleTimeTable from '@src/components/Metrics/MetricsStep/CycleTime/Table' +} from '@src/components/Metrics/MetricsStep/CycleTime/style'; +import { IconButton } from '@mui/material'; +import InfoOutlinedIcon from '@mui/icons-material/InfoOutlined'; +import CycleTimeTable from '@src/components/Metrics/MetricsStep/CycleTime/Table'; export const CycleTime = () => { - const warningMessage = useAppSelector(selectCycleTimeWarningMessage) + const warningMessage = useAppSelector(selectCycleTimeWarningMessage); return (
    @@ -32,5 +32,5 @@ export const CycleTime = () => {
    - ) -} + ); +}; diff --git a/frontend/src/components/Metrics/MetricsStep/CycleTime/style.tsx b/frontend/src/components/Metrics/MetricsStep/CycleTime/style.tsx index 9f934886bb..723c9d95a5 100644 --- a/frontend/src/components/Metrics/MetricsStep/CycleTime/style.tsx +++ b/frontend/src/components/Metrics/MetricsStep/CycleTime/style.tsx @@ -1,36 +1,36 @@ -import { styled } from '@mui/material/styles' -import { Checkbox, Tooltip } from '@mui/material' +import { styled } from '@mui/material/styles'; +import { Checkbox, Tooltip } from '@mui/material'; export const FlagCardItem = styled('div')({ display: 'flex', margin: '0.5rem 0', -}) +}); export const ItemText = styled('div')({ padding: '0', fontSize: '1rem', lineHeight: '1.5rem', fontWeight: '400', -}) +}); export const ItemCheckbox = styled(Checkbox)({ padding: 0, marginRight: '0.5rem', -}) +}); export const TitleAndTooltipContainer = styled('div')({ display: 'flex', flexDirection: 'row', alignItems: 'center', -}) +}); export const TooltipContainer = styled('div')({ marginLeft: '0.25rem', -}) +}); export const StyledTooltip = styled(({ className, ...props }: any) => ( ))(` max-width: 31.25rem; margin-top: 0.625rem; -`) +`); diff --git a/frontend/src/components/Metrics/MetricsStep/DeploymentFrequencySettings/PipelineMetricSelection/index.tsx b/frontend/src/components/Metrics/MetricsStep/DeploymentFrequencySettings/PipelineMetricSelection/index.tsx index 172b2b1636..d5ecbb57d2 100644 --- a/frontend/src/components/Metrics/MetricsStep/DeploymentFrequencySettings/PipelineMetricSelection/index.tsx +++ b/frontend/src/components/Metrics/MetricsStep/DeploymentFrequencySettings/PipelineMetricSelection/index.tsx @@ -1,41 +1,41 @@ -import React, { useState } from 'react' -import { SingleSelection } from '@src/components/Metrics/MetricsStep/DeploymentFrequencySettings/SingleSelection' -import { useAppDispatch } from '@src/hooks' -import { ButtonWrapper, PipelineMetricSelectionWrapper, RemoveButton, WarningMessage } from './style' -import { Loading } from '@src/components/Loading' -import { useGetMetricsStepsEffect } from '@src/hooks/useGetMetricsStepsEffect' -import { ErrorNotification } from '@src/components/ErrorNotification' +import React, { useState } from 'react'; +import { SingleSelection } from '@src/components/Metrics/MetricsStep/DeploymentFrequencySettings/SingleSelection'; +import { useAppDispatch } from '@src/hooks'; +import { ButtonWrapper, PipelineMetricSelectionWrapper, RemoveButton, WarningMessage } from './style'; +import { Loading } from '@src/components/Loading'; +import { useGetMetricsStepsEffect } from '@src/hooks/useGetMetricsStepsEffect'; +import { ErrorNotification } from '@src/components/ErrorNotification'; import { selectPipelineNames, selectPipelineOrganizations, selectSteps, selectStepsParams, updatePipelineToolVerifyResponseSteps, -} from '@src/context/config/configSlice' -import { store } from '@src/store' +} from '@src/context/config/configSlice'; +import { store } from '@src/store'; import { selectOrganizationWarningMessage, selectPipelineNameWarningMessage, selectStepWarningMessage, updatePipelineStep, -} from '@src/context/Metrics/metricsSlice' -import { WarningNotification } from '@src/components/Common/WarningNotification' -import { BranchSelection } from '@src/components/Metrics/ConfigStep/BranchSelection' -import { MESSAGE } from '@src/constants/resources' +} from '@src/context/Metrics/metricsSlice'; +import { WarningNotification } from '@src/components/Common/WarningNotification'; +import { BranchSelection } from '@src/components/Metrics/ConfigStep/BranchSelection'; +import { MESSAGE } from '@src/constants/resources'; interface pipelineMetricSelectionProps { - type: string + type: string; pipelineSetting: { - id: number - organization: string - pipelineName: string - step: string - branches: string[] - } - isShowRemoveButton: boolean - onRemovePipeline: (id: number) => void - onUpdatePipeline: (id: number, label: string, value: any) => void - isDuplicated: boolean + id: number; + organization: string; + pipelineName: string; + step: string; + branches: string[]; + }; + isShowRemoveButton: boolean; + onRemovePipeline: (id: number) => void; + onUpdatePipeline: (id: number, label: string, value: any) => void; + isDuplicated: boolean; } export const PipelineMetricSelection = ({ @@ -46,34 +46,34 @@ export const PipelineMetricSelection = ({ onUpdatePipeline, isDuplicated, }: pipelineMetricSelectionProps) => { - const { id, organization, pipelineName, step } = pipelineSetting - const dispatch = useAppDispatch() - const { isLoading, errorMessage, getSteps } = useGetMetricsStepsEffect() - const organizationNameOptions = selectPipelineOrganizations(store.getState()) - const pipelineNameOptions = selectPipelineNames(store.getState(), organization) - const stepsOptions = selectSteps(store.getState(), organization, pipelineName) - const organizationWarningMessage = selectOrganizationWarningMessage(store.getState(), id, type) - const pipelineNameWarningMessage = selectPipelineNameWarningMessage(store.getState(), id, type) - const stepWarningMessage = selectStepWarningMessage(store.getState(), id, type) - const [isShowNoStepWarning, setIsShowNoStepWarning] = useState(false) + const { id, organization, pipelineName, step } = pipelineSetting; + const dispatch = useAppDispatch(); + const { isLoading, errorMessage, getSteps } = useGetMetricsStepsEffect(); + const organizationNameOptions = selectPipelineOrganizations(store.getState()); + const pipelineNameOptions = selectPipelineNames(store.getState(), organization); + const stepsOptions = selectSteps(store.getState(), organization, pipelineName); + const organizationWarningMessage = selectOrganizationWarningMessage(store.getState(), id); + const pipelineNameWarningMessage = selectPipelineNameWarningMessage(store.getState(), id); + const stepWarningMessage = selectStepWarningMessage(store.getState(), id); + const [isShowNoStepWarning, setIsShowNoStepWarning] = useState(false); const handleRemoveClick = () => { - onRemovePipeline(id) - } + onRemovePipeline(id); + }; const handleGetPipelineData = (_pipelineName: string) => { const { params, buildId, organizationId, pipelineType, token } = selectStepsParams( store.getState(), organization, _pipelineName - ) + ); getSteps(params, organizationId, buildId, pipelineType, token).then((res) => { if (res && !res.haveStep) { - isShowRemoveButton && handleRemoveClick() + isShowRemoveButton && handleRemoveClick(); } else { - const steps = res?.response ?? [] - const branches = res?.branches ?? [] - const pipelineCrews = res?.pipelineCrews ?? [] + const steps = res?.response ?? []; + const branches = res?.branches ?? []; + const pipelineCrews = res?.pipelineCrews ?? []; dispatch( updatePipelineToolVerifyResponseSteps({ organization, @@ -82,12 +82,12 @@ export const PipelineMetricSelection = ({ branches, pipelineCrews, }) - ) - res?.haveStep && dispatch(updatePipelineStep({ steps, id, type, branches, pipelineCrews })) + ); + res?.haveStep && dispatch(updatePipelineStep({ steps, id, type, branches, pipelineCrews })); } - res && setIsShowNoStepWarning(!res.haveStep) - }) - } + res && setIsShowNoStepWarning(!res.haveStep); + }); + }; return ( @@ -134,5 +134,5 @@ export const PipelineMetricSelection = ({ )} - ) -} + ); +}; diff --git a/frontend/src/components/Metrics/MetricsStep/DeploymentFrequencySettings/PipelineMetricSelection/style.tsx b/frontend/src/components/Metrics/MetricsStep/DeploymentFrequencySettings/PipelineMetricSelection/style.tsx index 4c2389cc84..beb9694ae0 100644 --- a/frontend/src/components/Metrics/MetricsStep/DeploymentFrequencySettings/PipelineMetricSelection/style.tsx +++ b/frontend/src/components/Metrics/MetricsStep/DeploymentFrequencySettings/PipelineMetricSelection/style.tsx @@ -1,6 +1,6 @@ -import { styled } from '@mui/material/styles' -import { theme } from '@src/theme' -import { BasicButton } from '@src/components/Common/Buttons' +import { styled } from '@mui/material/styles'; +import { theme } from '@src/theme'; +import { BasicButton } from '@src/components/Common/Buttons'; export const PipelineMetricSelectionWrapper = styled('div')` position: relative; @@ -11,27 +11,27 @@ export const PipelineMetricSelectionWrapper = styled('div')` margin-bottom: 1rem; line-height: '2rem'; boarder-radius: 0.25rem; -` +`; export const ButtonWrapper = styled('div')({ width: '100%', display: 'flex', justifyContent: 'flex-end', -}) +}); export const RemoveButton = styled(BasicButton)({ fontFamily: theme.main.font.secondary, marginRight: '2rem', -}) +}); export const WarningMessage = styled('p')({ fontFamily: theme.typography.fontFamily, color: '#d32f2f', margin: '0 0 0 2.5%', width: '95%', -}) +}); export const BranchSelectionWrapper = styled('div')({ margin: '0 0 1.5rem 2.5%', width: '95%', -}) +}); diff --git a/frontend/src/components/Metrics/MetricsStep/DeploymentFrequencySettings/SingleSelection/index.tsx b/frontend/src/components/Metrics/MetricsStep/DeploymentFrequencySettings/SingleSelection/index.tsx index 4c98247e78..2e3178c25a 100644 --- a/frontend/src/components/Metrics/MetricsStep/DeploymentFrequencySettings/SingleSelection/index.tsx +++ b/frontend/src/components/Metrics/MetricsStep/DeploymentFrequencySettings/SingleSelection/index.tsx @@ -1,45 +1,45 @@ -import { Autocomplete, Box, ListItemText, TextField } from '@mui/material' -import React, { useEffect, useState } from 'react' -import { FormControlWrapper } from './style' -import { getEmojiUrls, removeExtraEmojiName } from '@src/emojis/emoji' -import { EmojiWrap, StyledAvatar } from '@src/emojis/style' -import { Z_INDEX } from '@src/constants/commons' +import { Autocomplete, Box, ListItemText, TextField } from '@mui/material'; +import React, { useEffect, useState } from 'react'; +import { FormControlWrapper } from './style'; +import { getEmojiUrls, removeExtraEmojiName } from '@src/emojis/emoji'; +import { EmojiWrap, StyledAvatar } from '@src/emojis/style'; +import { Z_INDEX } from '@src/constants/commons'; interface Props { - options: string[] - label: string - value: string - id: number - onGetSteps?: (pipelineName: string) => void - step?: string - onUpDatePipeline: (id: number, label: string, value: string) => void + options: string[]; + label: string; + value: string; + id: number; + onGetSteps?: (pipelineName: string) => void; + step?: string; + onUpDatePipeline: (id: number, label: string, value: string) => void; } /* istanbul ignore next */ export const SingleSelection = ({ options, label, value, id, onGetSteps, step, onUpDatePipeline }: Props) => { - const labelId = `single-selection-${label.toLowerCase().replace(' ', '-')}` - const [selectedOptions, setSelectedOptions] = useState(value) - const [inputValue, setInputValue] = useState(value) + const labelId = `single-selection-${label.toLowerCase().replace(' ', '-')}`; + const [selectedOptions, setSelectedOptions] = useState(value); + const [inputValue, setInputValue] = useState(value); const handleSelectedOptionsChange = (value: string) => { - setSelectedOptions(value) + setSelectedOptions(value); if (onGetSteps) { - onUpDatePipeline(id, 'Step', '') - onGetSteps(value) + onUpDatePipeline(id, 'Step', ''); + onGetSteps(value); } - onUpDatePipeline(id, label, value) - } + onUpDatePipeline(id, label, value); + }; useEffect(() => { if (onGetSteps && !!selectedOptions && !step) { - onGetSteps(selectedOptions) + onGetSteps(selectedOptions); } - }, []) + }, []); const emojiView = (pipelineStepName: string) => { - const emojiUrls: string[] = getEmojiUrls(pipelineStepName) - return emojiUrls.map((url) => ) - } + const emojiUrls: string[] = getEmojiUrls(pipelineStepName); + return emojiUrls.map((url) => ); + }; return ( <> @@ -59,11 +59,11 @@ export const SingleSelection = ({ options, label, value, id, onGetSteps, step, o )} value={value} onChange={(event, newValue: string) => { - handleSelectedOptionsChange(newValue) + handleSelectedOptionsChange(newValue); }} inputValue={inputValue} onInputChange={(event, newInputValue) => { - setInputValue(newInputValue) + setInputValue(newInputValue); }} renderInput={(params) => } slotProps={{ @@ -76,5 +76,5 @@ export const SingleSelection = ({ options, label, value, id, onGetSteps, step, o /> - ) -} + ); +}; diff --git a/frontend/src/components/Metrics/MetricsStep/DeploymentFrequencySettings/SingleSelection/style.tsx b/frontend/src/components/Metrics/MetricsStep/DeploymentFrequencySettings/SingleSelection/style.tsx index 861175932a..8a87c82d85 100644 --- a/frontend/src/components/Metrics/MetricsStep/DeploymentFrequencySettings/SingleSelection/style.tsx +++ b/frontend/src/components/Metrics/MetricsStep/DeploymentFrequencySettings/SingleSelection/style.tsx @@ -1,8 +1,8 @@ -import { styled } from '@mui/material/styles' -import { FormControl } from '@mui/material' +import { styled } from '@mui/material/styles'; +import { FormControl } from '@mui/material'; export const FormControlWrapper = styled(FormControl)({ margin: '0 0 2rem 2.5%', width: '95%', height: '2.5rem', -}) +}); diff --git a/frontend/src/components/Metrics/MetricsStep/DeploymentFrequencySettings/index.tsx b/frontend/src/components/Metrics/MetricsStep/DeploymentFrequencySettings/index.tsx index b39bec8a2a..8f3442cc6d 100644 --- a/frontend/src/components/Metrics/MetricsStep/DeploymentFrequencySettings/index.tsx +++ b/frontend/src/components/Metrics/MetricsStep/DeploymentFrequencySettings/index.tsx @@ -1,37 +1,37 @@ -import React from 'react' -import { PipelineMetricSelection } from './PipelineMetricSelection' -import { useAppDispatch, useAppSelector } from '@src/hooks' -import { MetricsSettingTitle } from '@src/components/Common/MetricsSettingTitle' +import React from 'react'; +import { PipelineMetricSelection } from './PipelineMetricSelection'; +import { useAppDispatch, useAppSelector } from '@src/hooks'; +import { MetricsSettingTitle } from '@src/components/Common/MetricsSettingTitle'; import { addADeploymentFrequencySetting, deleteADeploymentFrequencySetting, selectDeploymentFrequencySettings, updateDeploymentFrequencySettings, -} from '@src/context/Metrics/metricsSlice' -import { useMetricsStepValidationCheckContext } from '@src/hooks/useMetricsStepValidationCheckContext' -import { MetricsSettingAddButton } from '@src/components/Common/MetricsSettingButton' -import { PIPELINE_SETTING_TYPES } from '@src/constants/resources' -import { selectPipelineCrews } from '@src/context/config/configSlice' -import { Crews } from '@src/components/Metrics/MetricsStep/Crews' -import _ from 'lodash' +} from '@src/context/Metrics/metricsSlice'; +import { useMetricsStepValidationCheckContext } from '@src/hooks/useMetricsStepValidationCheckContext'; +import { MetricsSettingAddButton } from '@src/components/Common/MetricsSettingButton'; +import { PIPELINE_SETTING_TYPES } from '@src/constants/resources'; +import { selectPipelineCrews } from '@src/context/config/configSlice'; +import { Crews } from '@src/components/Metrics/MetricsStep/Crews'; +import _ from 'lodash'; export const DeploymentFrequencySettings = () => { - const dispatch = useAppDispatch() - const deploymentFrequencySettings = useAppSelector(selectDeploymentFrequencySettings) - const { getDuplicatedPipeLineIds } = useMetricsStepValidationCheckContext() - const pipelineCrews = useAppSelector(selectPipelineCrews) + const dispatch = useAppDispatch(); + const deploymentFrequencySettings = useAppSelector(selectDeploymentFrequencySettings); + const { getDuplicatedPipeLineIds } = useMetricsStepValidationCheckContext(); + const pipelineCrews = useAppSelector(selectPipelineCrews); const handleAddPipeline = () => { - dispatch(addADeploymentFrequencySetting()) - } + dispatch(addADeploymentFrequencySetting()); + }; const handleRemovePipeline = (id: number) => { - dispatch(deleteADeploymentFrequencySetting(id)) - } + dispatch(deleteADeploymentFrequencySetting(id)); + }; const handleUpdatePipeline = (id: number, label: string, value: string) => { - dispatch(updateDeploymentFrequencySettings({ updateId: id, label, value })) - } + dispatch(updateDeploymentFrequencySettings({ updateId: id, label, value })); + }; return ( <> @@ -52,5 +52,5 @@ export const DeploymentFrequencySettings = () => { )} - ) -} + ); +}; diff --git a/frontend/src/components/Metrics/MetricsStep/RealDone/index.tsx b/frontend/src/components/Metrics/MetricsStep/RealDone/index.tsx index 9c47b52e04..053c31800b 100644 --- a/frontend/src/components/Metrics/MetricsStep/RealDone/index.tsx +++ b/frontend/src/components/Metrics/MetricsStep/RealDone/index.tsx @@ -1,71 +1,71 @@ -import { FormHelperText } from '@mui/material' -import React, { useEffect, useState } from 'react' +import { FormHelperText } from '@mui/material'; +import React, { useEffect, useState } from 'react'; import { saveDoneColumn, selectCycleTimeSettings, selectMetricsContent, selectRealDoneWarningMessage, -} from '@src/context/Metrics/metricsSlice' -import { useAppDispatch } from '@src/hooks/useAppDispatch' -import { MetricsSettingTitle } from '@src/components/Common/MetricsSettingTitle' -import { METRICS_CONSTANTS } from '@src/constants/resources' -import { DEFAULT_HELPER_TEXT } from '@src/constants/commons' -import { useAppSelector } from '@src/hooks' -import { WarningNotification } from '@src/components/Common/WarningNotification' -import MultiAutoComplete from '@src/components/Common/MultiAutoComplete' -import { WarningMessage } from '@src/components/Metrics/MetricsStep/Crews/style' +} from '@src/context/Metrics/metricsSlice'; +import { useAppDispatch } from '@src/hooks/useAppDispatch'; +import { MetricsSettingTitle } from '@src/components/Common/MetricsSettingTitle'; +import { METRICS_CONSTANTS } from '@src/constants/resources'; +import { DEFAULT_HELPER_TEXT } from '@src/constants/commons'; +import { useAppSelector } from '@src/hooks'; +import { WarningNotification } from '@src/components/Common/WarningNotification'; +import MultiAutoComplete from '@src/components/Common/MultiAutoComplete'; +import { WarningMessage } from '@src/components/Metrics/MetricsStep/Crews/style'; interface realDoneProps { - columns: { key: string; value: { name: string; statuses: string[] } }[] - title: string - label: string + columns: { key: string; value: { name: string; statuses: string[] } }[]; + title: string; + label: string; } const getSelectedDoneColumns = (selectedBoardColumns: { name: string; value: string }[]) => - selectedBoardColumns.filter(({ value }) => value === METRICS_CONSTANTS.doneValue).map(({ name }) => name) + selectedBoardColumns.filter(({ value }) => value === METRICS_CONSTANTS.doneValue).map(({ name }) => name); const getFilteredStatus = ( columns: { key: string; value: { name: string; statuses: string[] } }[], selectedDoneColumns: string[] ): string[] => - columns.filter(({ value }) => selectedDoneColumns.includes(value.name)).flatMap(({ value }) => value.statuses) + columns.filter(({ value }) => selectedDoneColumns.includes(value.name)).flatMap(({ value }) => value.statuses); const getDoneStatus = (columns: { key: string; value: { name: string; statuses: string[] } }[]) => - columns.find((column) => column.key === METRICS_CONSTANTS.doneKeyFromBackend)?.value.statuses ?? [] + columns.find((column) => column.key === METRICS_CONSTANTS.doneKeyFromBackend)?.value.statuses ?? []; export const RealDone = ({ columns, title, label }: realDoneProps) => { - const dispatch = useAppDispatch() - const selectedCycleTimeSettings = useAppSelector(selectCycleTimeSettings) - const savedDoneColumns = useAppSelector(selectMetricsContent).doneColumn - const realDoneWarningMessage = useAppSelector(selectRealDoneWarningMessage) - const doneStatus = getDoneStatus(columns) - const selectedDoneColumns = getSelectedDoneColumns(selectedCycleTimeSettings) - const filteredStatus = getFilteredStatus(columns, selectedDoneColumns) - const status = selectedDoneColumns.length < 1 ? doneStatus : filteredStatus - const [selectedDoneStatus, setSelectedDoneStatus] = useState([]) - const isAllSelected = savedDoneColumns.length === status.length - const [useDefaultValue, setUseDefaultValue] = useState(false) + const dispatch = useAppDispatch(); + const selectedCycleTimeSettings = useAppSelector(selectCycleTimeSettings); + const savedDoneColumns = useAppSelector(selectMetricsContent).doneColumn; + const realDoneWarningMessage = useAppSelector(selectRealDoneWarningMessage); + const doneStatus = getDoneStatus(columns); + const selectedDoneColumns = getSelectedDoneColumns(selectedCycleTimeSettings); + const filteredStatus = getFilteredStatus(columns, selectedDoneColumns); + const status = selectedDoneColumns.length < 1 ? doneStatus : filteredStatus; + const [selectedDoneStatus, setSelectedDoneStatus] = useState([]); + const isAllSelected = savedDoneColumns.length === status.length; + const [useDefaultValue, setUseDefaultValue] = useState(false); const handleRealDoneChange = (event: React.SyntheticEvent, value: string[]) => { if (value[value.length - 1] === 'All') { - setSelectedDoneStatus(selectedDoneStatus.length === status.length ? [] : status) - dispatch(saveDoneColumn(selectedDoneStatus.length === status.length ? [] : status)) - return + setSelectedDoneStatus(selectedDoneStatus.length === status.length ? [] : status); + dispatch(saveDoneColumn(selectedDoneStatus.length === status.length ? [] : status)); + return; } - setSelectedDoneStatus([...value]) - dispatch(saveDoneColumn([...value])) - } + setSelectedDoneStatus([...value]); + dispatch(saveDoneColumn([...value])); + }; useEffect(() => { if (status.length === 1) { if (savedDoneColumns.length !== 1) { - dispatch(saveDoneColumn(status)) + dispatch(saveDoneColumn(status)); } - setUseDefaultValue(true) + setUseDefaultValue(true); } else { - setUseDefaultValue(false) + setUseDefaultValue(false); } - }, [status.length, useDefaultValue]) + }, [status.length, useDefaultValue]); return useDefaultValue ? ( <> @@ -91,5 +91,5 @@ export const RealDone = ({ columns, title, label }: realDoneProps) => { )} - ) -} + ); +}; diff --git a/frontend/src/components/Metrics/MetricsStep/RealDone/style.tsx b/frontend/src/components/Metrics/MetricsStep/RealDone/style.tsx index 40518a3600..0b0285b6f0 100644 --- a/frontend/src/components/Metrics/MetricsStep/RealDone/style.tsx +++ b/frontend/src/components/Metrics/MetricsStep/RealDone/style.tsx @@ -1,6 +1,6 @@ -import { styled } from '@mui/material/styles' -import { FormControl } from '@mui/material' +import { styled } from '@mui/material/styles'; +import { FormControl } from '@mui/material'; export const FormControlWrapper = styled(FormControl)({ height: '2.5rem', -}) +}); diff --git a/frontend/src/components/Metrics/MetricsStep/index.tsx b/frontend/src/components/Metrics/MetricsStep/index.tsx index 3f1c46708a..e088062be9 100644 --- a/frontend/src/components/Metrics/MetricsStep/index.tsx +++ b/frontend/src/components/Metrics/MetricsStep/index.tsx @@ -1,37 +1,37 @@ -import { Crews } from '@src/components/Metrics/MetricsStep/Crews' -import { useAppSelector } from '@src/hooks' -import { RealDone } from '@src/components/Metrics/MetricsStep/RealDone' -import { CycleTime } from '@src/components/Metrics/MetricsStep/CycleTime' -import { Classification } from '@src/components/Metrics/MetricsStep/Classification' -import { selectDateRange, selectJiraColumns, selectMetrics, selectUsers } from '@src/context/config/configSlice' -import { REQUIRED_DATA, DONE } from '@src/constants/resources' -import { selectCycleTimeSettings, selectMetricsContent } from '@src/context/Metrics/metricsSlice' -import { DeploymentFrequencySettings } from '@src/components/Metrics/MetricsStep/DeploymentFrequencySettings' -import DateRangeViewer from '@src/components/Common/DateRangeViewer' +import { Crews } from '@src/components/Metrics/MetricsStep/Crews'; +import { useAppSelector } from '@src/hooks'; +import { RealDone } from '@src/components/Metrics/MetricsStep/RealDone'; +import { CycleTime } from '@src/components/Metrics/MetricsStep/CycleTime'; +import { Classification } from '@src/components/Metrics/MetricsStep/Classification'; +import { selectDateRange, selectJiraColumns, selectMetrics, selectUsers } from '@src/context/config/configSlice'; +import { REQUIRED_DATA, DONE } from '@src/constants/resources'; +import { selectCycleTimeSettings, selectMetricsContent } from '@src/context/Metrics/metricsSlice'; +import { DeploymentFrequencySettings } from '@src/components/Metrics/MetricsStep/DeploymentFrequencySettings'; +import DateRangeViewer from '@src/components/Common/DateRangeViewer'; import { MetricSelectionWrapper, MetricsSelectionTitle, MetricSelectionHeader, -} from '@src/components/Metrics/MetricsStep/style' -import { useLayoutEffect } from 'react' -import { useNotificationLayoutEffectInterface } from '@src/hooks/useNotificationLayoutEffect' +} from '@src/components/Metrics/MetricsStep/style'; +import { useLayoutEffect } from 'react'; +import { useNotificationLayoutEffectInterface } from '@src/hooks/useNotificationLayoutEffect'; const MetricsStep = ({ resetProps }: useNotificationLayoutEffectInterface) => { - const requiredData = useAppSelector(selectMetrics) - const users = useAppSelector(selectUsers) - const jiraColumns = useAppSelector(selectJiraColumns) - const targetFields = useAppSelector(selectMetricsContent).targetFields - const cycleTimeSettings = useAppSelector(selectCycleTimeSettings) - const { startDate, endDate } = useAppSelector(selectDateRange) + const requiredData = useAppSelector(selectMetrics); + const users = useAppSelector(selectUsers); + const jiraColumns = useAppSelector(selectJiraColumns); + const targetFields = useAppSelector(selectMetricsContent).targetFields; + const cycleTimeSettings = useAppSelector(selectCycleTimeSettings); + const { startDate, endDate } = useAppSelector(selectDateRange); const isShowCrewsAndRealDone = requiredData.includes(REQUIRED_DATA.VELOCITY) || requiredData.includes(REQUIRED_DATA.CYCLE_TIME) || - requiredData.includes(REQUIRED_DATA.CLASSIFICATION) - const isShowRealDone = cycleTimeSettings.some((e) => e.value === DONE) + requiredData.includes(REQUIRED_DATA.CLASSIFICATION); + const isShowRealDone = cycleTimeSettings.some((e) => e.value === DONE); useLayoutEffect(() => { - resetProps?.() - }, []) + resetProps?.(); + }, []); return ( <> @@ -66,7 +66,7 @@ const MetricsStep = ({ resetProps }: useNotificationLayoutEffectInterface) => { )} - ) -} + ); +}; -export default MetricsStep +export default MetricsStep; diff --git a/frontend/src/components/Metrics/MetricsStep/style.tsx b/frontend/src/components/Metrics/MetricsStep/style.tsx index 4fe4de8441..ff27366c73 100644 --- a/frontend/src/components/Metrics/MetricsStep/style.tsx +++ b/frontend/src/components/Metrics/MetricsStep/style.tsx @@ -1,11 +1,12 @@ -import { styled } from '@mui/material/styles' -import { Divider } from '@src/components/Common/MetricsSettingTitle/style' +import { styled } from '@mui/material/styles'; +import { Divider } from '@src/components/Common/MetricsSettingTitle/style'; export const MetricSelectionHeader = styled('div')({ display: 'flex', justifyContent: 'flex-end', - marginBottom: '1rem', -}) + position: 'relative', + top: '1.6rem', +}); export const MetricSelectionWrapper = styled('div')({ boxSizing: 'border-box', @@ -19,17 +20,17 @@ export const MetricSelectionWrapper = styled('div')({ border: '0.1rem', boxShadow: '0 0.25rem 1rem 0 rgba(0, 0, 0, 0.08)', position: 'relative', -}) +}); export const MetricsSelectionTitle = styled(Divider)({ fontSize: '1.2rem', lineHeight: '1.25rem', fontWeight: '600', fontFamily: 'sans-serif', -}) +}); export const ReportSelectionTitle = styled(MetricsSelectionTitle)({ marginBottom: '1.5rem', -}) +}); -export const ConfigSelectionTitle = styled(ReportSelectionTitle)({}) +export const ConfigSelectionTitle = styled(ReportSelectionTitle)({}); diff --git a/frontend/src/components/Metrics/MetricsStepper/ConfirmDialog/index.tsx b/frontend/src/components/Metrics/MetricsStepper/ConfirmDialog/index.tsx index 7af02b3a14..a2362f89f0 100644 --- a/frontend/src/components/Metrics/MetricsStepper/ConfirmDialog/index.tsx +++ b/frontend/src/components/Metrics/MetricsStepper/ConfirmDialog/index.tsx @@ -1,14 +1,14 @@ -import React from 'react' -import { Dialog } from '@mui/material' +import React from 'react'; +import { Dialog } from '@mui/material'; import { CancelButton, ConformationDialog, DialogButtonGroup, YesButton, -} from '@src/components/Metrics/MetricsStepper/ConfirmDialog/style' +} from '@src/components/Metrics/MetricsStepper/ConfirmDialog/style'; export const ConfirmDialog = (props: { onConfirm: () => void; onClose: () => void; isDialogShowing: boolean }) => { - const { onConfirm, onClose, isDialogShowing } = props + const { onConfirm, onClose, isDialogShowing } = props; return ( All the filled data will be cleared. Continue to Home page? @@ -17,5 +17,5 @@ export const ConfirmDialog = (props: { onConfirm: () => void; onClose: () => voi Cancel - ) -} + ); +}; diff --git a/frontend/src/components/Metrics/MetricsStepper/ConfirmDialog/style.tsx b/frontend/src/components/Metrics/MetricsStepper/ConfirmDialog/style.tsx index 115500fb2f..6dbf948467 100644 --- a/frontend/src/components/Metrics/MetricsStepper/ConfirmDialog/style.tsx +++ b/frontend/src/components/Metrics/MetricsStepper/ConfirmDialog/style.tsx @@ -1,17 +1,17 @@ -import { theme } from '@src/theme' -import { styled } from '@mui/material/styles' -import Button from '@mui/material/Button' -import { DialogContent } from '@mui/material' +import { theme } from '@src/theme'; +import { styled } from '@mui/material/styles'; +import Button from '@mui/material/Button'; +import { DialogContent } from '@mui/material'; export const ConformationDialog = styled(DialogContent)({ margin: '1rem 0 0 0', -}) +}); export const DialogButtonGroup = styled('div')({ display: 'flex', justifyContent: 'center', margin: '1rem 0', -}) +}); export const YesButton = styled(Button)({ boxShadow: theme.main.boxShadow, @@ -22,8 +22,8 @@ export const YesButton = styled(Button)({ '&:hover': { backgroundColor: theme.main.backgroundColor, }, -}) +}); export const CancelButton = styled(Button)({ color: theme.main.secondColor, -}) +}); diff --git a/frontend/src/components/Metrics/MetricsStepper/index.tsx b/frontend/src/components/Metrics/MetricsStepper/index.tsx index ef0d9ccc17..b6c261a543 100644 --- a/frontend/src/components/Metrics/MetricsStepper/index.tsx +++ b/frontend/src/components/Metrics/MetricsStepper/index.tsx @@ -1,4 +1,4 @@ -import React, { lazy, Suspense, useEffect, useState } from 'react' +import React, { lazy, Suspense, useEffect, useState } from 'react'; import { BackButton, ButtonContainer, @@ -8,9 +8,9 @@ import { StyledStep, StyledStepLabel, StyledStepper, -} from './style' -import { useAppDispatch, useAppSelector } from '@src/hooks/useAppDispatch' -import { backStep, nextStep, selectStepNumber, updateTimeStamp } from '@src/context/stepper/StepperSlice' +} from './style'; +import { useAppDispatch, useAppSelector } from '@src/hooks/useAppDispatch'; +import { backStep, nextStep, selectStepNumber, updateTimeStamp } from '@src/context/stepper/StepperSlice'; import { BOARD_TYPES, DONE, @@ -20,10 +20,10 @@ import { REQUIRED_DATA, SOURCE_CONTROL_TYPES, TIPS, -} from '@src/constants/resources' -import { COMMON_BUTTONS, METRICS_STEPS, STEPS } from '@src/constants/commons' -import { ConfirmDialog } from '@src/components/Metrics/MetricsStepper/ConfirmDialog' -import { useNavigate } from 'react-router-dom' +} from '@src/constants/resources'; +import { COMMON_BUTTONS, METRICS_STEPS, STEPS } from '@src/constants/commons'; +import { ConfirmDialog } from '@src/components/Metrics/MetricsStepper/ConfirmDialog'; +import { useNavigate } from 'react-router-dom'; import { selectConfig, selectMetrics, @@ -33,70 +33,70 @@ import { updatePipelineToolVerifyState, updateSourceControl, updateSourceControlVerifyState, -} from '@src/context/config/configSlice' -import { useMetricsStepValidationCheckContext } from '@src/hooks/useMetricsStepValidationCheckContext' -import { Tooltip } from '@mui/material' -import { exportToJsonFile } from '@src/utils/util' +} from '@src/context/config/configSlice'; +import { useMetricsStepValidationCheckContext } from '@src/hooks/useMetricsStepValidationCheckContext'; +import { Tooltip } from '@mui/material'; +import { exportToJsonFile } from '@src/utils/util'; import { savedMetricsSettingState, selectCycleTimeSettings, selectMetricsContent, -} from '@src/context/Metrics/metricsSlice' -import _ from 'lodash' -import SaveAltIcon from '@mui/icons-material/SaveAlt' -import { useNotificationLayoutEffectInterface } from '@src/hooks/useNotificationLayoutEffect' -import { ROUTE } from '@src/constants/router' +} from '@src/context/Metrics/metricsSlice'; +import _ from 'lodash'; +import SaveAltIcon from '@mui/icons-material/SaveAlt'; +import { useNotificationLayoutEffectInterface } from '@src/hooks/useNotificationLayoutEffect'; +import { ROUTE } from '@src/constants/router'; -const ConfigStep = lazy(() => import('@src/components/Metrics/ConfigStep')) -const MetricsStep = lazy(() => import('@src/components/Metrics/MetricsStep')) -const ReportStep = lazy(() => import('@src/components/Metrics/ReportStep')) +const ConfigStep = lazy(() => import('@src/components/Metrics/ConfigStep')); +const MetricsStep = lazy(() => import('@src/components/Metrics/MetricsStep')); +const ReportStep = lazy(() => import('@src/components/Metrics/ReportStep')); /* istanbul ignore next */ const MetricsStepper = (props: useNotificationLayoutEffectInterface) => { - const navigate = useNavigate() - const dispatch = useAppDispatch() - const activeStep = useAppSelector(selectStepNumber) - const [isDialogShowing, setIsDialogShowing] = useState(false) - const requiredData = useAppSelector(selectMetrics) - const config = useAppSelector(selectConfig) - const metricsConfig = useAppSelector(selectMetricsContent) - const [isDisableNextButton, setIsDisableNextButton] = useState(true) - const { getDuplicatedPipeLineIds } = useMetricsStepValidationCheckContext() - const cycleTimeSettings = useAppSelector(selectCycleTimeSettings) + const navigate = useNavigate(); + const dispatch = useAppDispatch(); + const activeStep = useAppSelector(selectStepNumber); + const [isDialogShowing, setIsDialogShowing] = useState(false); + const requiredData = useAppSelector(selectMetrics); + const config = useAppSelector(selectConfig); + const metricsConfig = useAppSelector(selectMetricsContent); + const [isDisableNextButton, setIsDisableNextButton] = useState(true); + const { getDuplicatedPipeLineIds } = useMetricsStepValidationCheckContext(); + const cycleTimeSettings = useAppSelector(selectCycleTimeSettings); - const { isShow: isShowBoard, isVerified: isBoardVerified } = config.board - const { isShow: isShowPipeline, isVerified: isPipelineToolVerified } = config.pipelineTool - const { isShow: isShowSourceControl, isVerified: isSourceControlVerified } = config.sourceControl - const isShowCycleTimeSettings = requiredData.includes(REQUIRED_DATA.CYCLE_TIME) - const isCycleTimeSettingsVerified = cycleTimeSettings.some((e) => e.value === DONE) - const isShowClassificationSetting = requiredData.includes(REQUIRED_DATA.CLASSIFICATION) - const isClassificationSettingVerified = metricsConfig.targetFields.some((item) => item.flag) + const { isShow: isShowBoard, isVerified: isBoardVerified } = config.board; + const { isShow: isShowPipeline, isVerified: isPipelineToolVerified } = config.pipelineTool; + const { isShow: isShowSourceControl, isVerified: isSourceControlVerified } = config.sourceControl; + const isShowCycleTimeSettings = requiredData.includes(REQUIRED_DATA.CYCLE_TIME); + const isCycleTimeSettingsVerified = cycleTimeSettings.some((e) => e.value === DONE); + const isShowClassificationSetting = requiredData.includes(REQUIRED_DATA.CLASSIFICATION); + const isClassificationSettingVerified = metricsConfig.targetFields.some((item) => item.flag); - const { metrics, projectName, dateRange } = config.basic + const { metrics, projectName, dateRange } = config.basic; - const selectedBoardColumns = useAppSelector(selectCycleTimeSettings) + const selectedBoardColumns = useAppSelector(selectCycleTimeSettings); const verifyPipeline = (type: string) => { const pipelines = type === PIPELINE_SETTING_TYPES.LEAD_TIME_FOR_CHANGES_TYPE ? metricsConfig.leadTimeForChanges - : metricsConfig.deploymentFrequencySettings + : metricsConfig.deploymentFrequencySettings; return ( pipelines.every(({ step }) => step !== '') && pipelines.every(({ branches }) => !_.isEmpty(branches)) && getDuplicatedPipeLineIds(pipelines).length === 0 - ) - } + ); + }; - const isShowCrewsSetting = isShowBoard + const isShowCrewsSetting = isShowBoard; const isShowRealDone = - isShowBoard && selectedBoardColumns.filter((column) => column.value === METRICS_CONSTANTS.doneValue).length > 0 + isShowBoard && selectedBoardColumns.filter((column) => column.value === METRICS_CONSTANTS.doneValue).length > 0; const isShowDeploymentFrequency = requiredData.includes(REQUIRED_DATA.DEPLOYMENT_FREQUENCY) || requiredData.includes(REQUIRED_DATA.CHANGE_FAILURE_RATE) || - requiredData.includes(REQUIRED_DATA.MEAN_TIME_TO_RECOVERY) - const isCrewsSettingValid = metricsConfig.users.length > 0 - const isRealDoneValid = metricsConfig.doneColumn.length > 0 - const isDeploymentFrequencyValid = verifyPipeline(PIPELINE_SETTING_TYPES.DEPLOYMENT_FREQUENCY_SETTINGS_TYPE) + requiredData.includes(REQUIRED_DATA.MEAN_TIME_TO_RECOVERY); + const isCrewsSettingValid = metricsConfig.users.length > 0; + const isRealDoneValid = metricsConfig.doneColumn.length > 0; + const isDeploymentFrequencyValid = verifyPipeline(PIPELINE_SETTING_TYPES.DEPLOYMENT_FREQUENCY_SETTINGS_TYPE); useEffect(() => { if (!activeStep) { @@ -104,11 +104,11 @@ const MetricsStepper = (props: useNotificationLayoutEffectInterface) => { { isShow: isShowBoard, isValid: isBoardVerified }, { isShow: isShowPipeline, isValid: isPipelineToolVerified }, { isShow: isShowSourceControl, isValid: isSourceControlVerified }, - ] - const activeNextButtonValidityOptions = nextButtonValidityOptions.filter(({ isShow }) => isShow) + ]; + const activeNextButtonValidityOptions = nextButtonValidityOptions.filter(({ isShow }) => isShow); projectName && dateRange.startDate && dateRange.endDate && metrics.length ? setIsDisableNextButton(!activeNextButtonValidityOptions.every(({ isValid }) => isValid)) - : setIsDisableNextButton(true) + : setIsDisableNextButton(true); } else if (activeStep === METRICS_STEPS.METRICS) { const nextButtonValidityOptions = [ { isShow: isShowCrewsSetting, isValid: isCrewsSettingValid }, @@ -116,11 +116,11 @@ const MetricsStepper = (props: useNotificationLayoutEffectInterface) => { { isShow: isShowDeploymentFrequency, isValid: isDeploymentFrequencyValid }, { isShow: isShowCycleTimeSettings, isValid: isCycleTimeSettingsVerified }, { isShow: isShowClassificationSetting, isValid: isClassificationSettingVerified }, - ] - const activeNextButtonValidityOptions = nextButtonValidityOptions.filter(({ isShow }) => isShow) + ]; + const activeNextButtonValidityOptions = nextButtonValidityOptions.filter(({ isShow }) => isShow); activeNextButtonValidityOptions.every(({ isValid }) => isValid) ? setIsDisableNextButton(false) - : setIsDisableNextButton(true) + : setIsDisableNextButton(true); } }, [ activeStep, @@ -135,7 +135,7 @@ const MetricsStepper = (props: useNotificationLayoutEffectInterface) => { dateRange, selectedBoardColumns, metricsConfig, - ]) + ]); const filterMetricsConfig = (metricsConfig: savedMetricsSettingState) => { return Object.fromEntries( @@ -146,17 +146,17 @@ const MetricsStepper = (props: useNotificationLayoutEffectInterface) => { !value.every((item) => item.organization === '') && !value.every((item) => item.flag === false) && value.length > 0 - ) + ); } else { - return true + return true; } }) - ) - } + ); + }; /* istanbul ignore next */ const handleSave = () => { - const { projectName, dateRange, calendarType, metrics } = config.basic + const { projectName, dateRange, calendarType, metrics } = config.basic; const configData = { projectName, dateRange, @@ -168,7 +168,7 @@ const MetricsStepper = (props: useNotificationLayoutEffectInterface) => { pipelineTool: isShowPipeline ? config.pipelineTool.config : undefined, /* istanbul ignore next */ sourceControl: isShowSourceControl ? config.sourceControl.config : undefined, - } + }; const { leadTimeForChanges, @@ -180,7 +180,7 @@ const MetricsStepper = (props: useNotificationLayoutEffectInterface) => { cycleTimeSettings, treatFlagCardAsBlock, assigneeFilter, - } = filterMetricsConfig(metricsConfig) + } = filterMetricsConfig(metricsConfig); /* istanbul ignore next */ const metricsData = { @@ -203,51 +203,51 @@ const MetricsStepper = (props: useNotificationLayoutEffectInterface) => { ?.map((item: { name: string; key: string; flag: boolean }) => item.key), deployment: deploymentFrequencySettings, leadTime: leadTimeForChanges, - } - const jsonData = activeStep === METRICS_STEPS.CONFIG ? configData : { ...configData, ...metricsData } - exportToJsonFile('config', jsonData) - } + }; + const jsonData = activeStep === METRICS_STEPS.CONFIG ? configData : { ...configData, ...metricsData }; + exportToJsonFile('config', jsonData); + }; const handleNext = () => { if (activeStep === METRICS_STEPS.METRICS) { - dispatch(updateTimeStamp(new Date().getTime())) + dispatch(updateTimeStamp(new Date().getTime())); } if (activeStep === METRICS_STEPS.CONFIG) { - cleanBoardState() - cleanPipelineToolConfiguration() - cleanSourceControlState() + cleanBoardState(); + cleanPipelineToolConfiguration(); + cleanSourceControlState(); } - dispatch(nextStep()) - } + dispatch(nextStep()); + }; const handleBack = () => { - setIsDialogShowing(!activeStep) - dispatch(backStep()) - } + setIsDialogShowing(!activeStep); + dispatch(backStep()); + }; const backToHomePage = () => { - navigate(ROUTE.BASE_PAGE) - setIsDialogShowing(false) - window.location.reload() - } + navigate(ROUTE.BASE_PAGE); + setIsDialogShowing(false); + window.location.reload(); + }; const CancelDialog = () => { - setIsDialogShowing(false) - } + setIsDialogShowing(false); + }; const cleanPipelineToolConfiguration = () => { - !isShowPipeline && dispatch(updatePipelineTool({ type: PIPELINE_TOOL_TYPES.BUILD_KITE, token: '' })) + !isShowPipeline && dispatch(updatePipelineTool({ type: PIPELINE_TOOL_TYPES.BUILD_KITE, token: '' })); isShowPipeline ? dispatch(updatePipelineToolVerifyState(isPipelineToolVerified)) - : dispatch(updatePipelineToolVerifyState(false)) - } + : dispatch(updatePipelineToolVerifyState(false)); + }; const cleanSourceControlState = () => { - !isShowSourceControl && dispatch(updateSourceControl({ type: SOURCE_CONTROL_TYPES.GITHUB, token: '' })) + !isShowSourceControl && dispatch(updateSourceControl({ type: SOURCE_CONTROL_TYPES.GITHUB, token: '' })); isShowSourceControl ? dispatch(updateSourceControlVerifyState(isSourceControlVerified)) - : dispatch(updateSourceControlVerifyState(false)) - } + : dispatch(updateSourceControlVerifyState(false)); + }; const cleanBoardState = () => { !isShowBoard && @@ -260,9 +260,9 @@ const MetricsStepper = (props: useNotificationLayoutEffectInterface) => { site: '', token: '', }) - ) - isShowBoard ? dispatch(updateBoardVerifyState(isBoardVerified)) : dispatch(updateBoardVerifyState(false)) - } + ); + isShowBoard ? dispatch(updateBoardVerifyState(isBoardVerified)) : dispatch(updateBoardVerifyState(false)); + }; return ( <> @@ -303,7 +303,7 @@ const MetricsStepper = (props: useNotificationLayoutEffectInterface) => { )} - ) -} + ); +}; -export default MetricsStepper +export default MetricsStepper; diff --git a/frontend/src/components/Metrics/MetricsStepper/style.tsx b/frontend/src/components/Metrics/MetricsStepper/style.tsx index a353e8bcb3..9d037bc585 100644 --- a/frontend/src/components/Metrics/MetricsStepper/style.tsx +++ b/frontend/src/components/Metrics/MetricsStepper/style.tsx @@ -1,6 +1,6 @@ -import { styled } from '@mui/material/styles' -import { Button, Step, StepLabel, Stepper } from '@mui/material' -import { theme } from '@src/theme' +import { styled } from '@mui/material/styles'; +import { Button, Step, StepLabel, Stepper } from '@mui/material'; +import { theme } from '@src/theme'; export const StyledStepper = styled(Stepper)({ display: 'flex', @@ -12,7 +12,7 @@ export const StyledStepper = styled(Stepper)({ flexDirection: 'column', fontSize: '0.5rem', }, -}) +}); export const StyledStep = styled(Step)({ svg: { @@ -22,7 +22,7 @@ export const StyledStep = styled(Step)({ [theme.breakpoints.down('md')]: { padding: '0.25rem 0', }, -}) +}); export const StyledStepLabel = styled(StepLabel)({ width: '5rem', @@ -34,7 +34,7 @@ export const StyledStepLabel = styled(StepLabel)({ [theme.breakpoints.down('sm')]: { fontSize: '0.5rem', }, -}) +}); export const MetricsStepperContent = styled('div')({ display: 'flex', @@ -45,7 +45,7 @@ export const MetricsStepperContent = styled('div')({ [theme.breakpoints.down('md')]: { width: '80%', }, -}) +}); export const basicButtonStyle = { height: '2.5rem', @@ -53,7 +53,7 @@ export const basicButtonStyle = { fontSize: '1rem', fontWeight: '500', textTransform: theme.typography.button.textTransform, -} +}; export const SaveButton = styled(Button)({ ...basicButtonStyle, @@ -62,7 +62,7 @@ export const SaveButton = styled(Button)({ [theme.breakpoints.down('lg')]: { fontSize: '0.8rem', }, -}) +}); export const BackButton = styled(Button)({ ...basicButtonStyle, @@ -71,7 +71,7 @@ export const BackButton = styled(Button)({ [theme.breakpoints.down('lg')]: { fontSize: '0.8rem', }, -}) +}); export const NextButton = styled(Button)({ ...basicButtonStyle, @@ -92,7 +92,7 @@ export const NextButton = styled(Button)({ [theme.breakpoints.down('lg')]: { fontSize: '0.8rem', }, -}) +}); export const ButtonContainer = styled('div')({ display: 'flex', @@ -101,4 +101,4 @@ export const ButtonContainer = styled('div')({ margin: '0 auto', padding: '0 0 2rem 0', width: '70%', -}) +}); diff --git a/frontend/src/components/Metrics/ReportButtonGroup/index.tsx b/frontend/src/components/Metrics/ReportButtonGroup/index.tsx index 4ba10cd339..4392f205b4 100644 --- a/frontend/src/components/Metrics/ReportButtonGroup/index.tsx +++ b/frontend/src/components/Metrics/ReportButtonGroup/index.tsx @@ -1,89 +1,86 @@ -import { Tooltip } from '@mui/material' -import { REQUIRED_DATA, TIPS } from '@src/constants/resources' -import { BackButton, SaveButton } from '@src/components/Metrics/MetricsStepper/style' -import SaveAltIcon from '@mui/icons-material/SaveAlt' -import { COMMON_BUTTONS, DOWNLOAD_TYPES } from '@src/constants/commons' -import React, { useEffect } from 'react' -import { CSVReportRequestDTO } from '@src/clients/report/dto/request' -import { backStep } from '@src/context/stepper/StepperSlice' -import { useAppDispatch } from '@src/hooks/useAppDispatch' -import { useExportCsvEffect } from '@src/hooks/useExportCsvEffect' -import { useAppSelector } from '@src/hooks' -import { selectMetrics } from '@src/context/config/configSlice' -import { ExpiredDialog } from '@src/components/Metrics/ReportStep/ExpiredDialog' +import { Tooltip } from '@mui/material'; +import { TIPS } from '@src/constants/resources'; +import { BackButton, SaveButton } from '@src/components/Metrics/MetricsStepper/style'; +import SaveAltIcon from '@mui/icons-material/SaveAlt'; +import { COMMON_BUTTONS, DOWNLOAD_TYPES } from '@src/constants/commons'; +import React, { useEffect } from 'react'; +import { CSVReportRequestDTO } from '@src/clients/report/dto/request'; +import { useExportCsvEffect } from '@src/hooks/useExportCsvEffect'; +import { ExpiredDialog } from '@src/components/Metrics/ReportStep/ExpiredDialog'; import { StyledButtonGroup, StyledExportButton, StyledRightButtonGroup, -} from '@src/components/Metrics/ReportButtonGroup/style' -import { ReportResponseDTO } from '@src/clients/report/dto/response' +} from '@src/components/Metrics/ReportButtonGroup/style'; +import { ReportResponseDTO } from '@src/clients/report/dto/response'; interface ReportButtonGroupProps { - handleSave: () => void - csvTimeStamp: number - startDate: string - endDate: string - setErrorMessage: (message: string) => void - shouldShowBoardExportButton: boolean - reportData: ReportResponseDTO | undefined + handleSave?: () => void; + handleBack: () => void; + csvTimeStamp: number; + startDate: string; + endDate: string; + setErrorMessage: (message: string) => void; + reportData: ReportResponseDTO | undefined; + isShowSave: boolean; + isShowExportBoardButton: boolean; + isShowExportPipelineButton: boolean; + isShowExportMetrics: boolean; } export const ReportButtonGroup = ({ handleSave, + handleBack, csvTimeStamp, startDate, endDate, setErrorMessage, reportData, - shouldShowBoardExportButton, + isShowSave, + isShowExportMetrics, + isShowExportBoardButton, + isShowExportPipelineButton, }: ReportButtonGroupProps) => { - const dispatch = useAppDispatch() - const { fetchExportData, errorMessage, isExpired } = useExportCsvEffect() - const requiredData = useAppSelector(selectMetrics) - const isShowExportPipelineButton = - requiredData.includes(REQUIRED_DATA.DEPLOYMENT_FREQUENCY) || - requiredData.includes(REQUIRED_DATA.CHANGE_FAILURE_RATE) || - requiredData.includes(REQUIRED_DATA.LEAD_TIME_FOR_CHANGES) || - requiredData.includes(REQUIRED_DATA.MEAN_TIME_TO_RECOVERY) + const { fetchExportData, errorMessage, isExpired } = useExportCsvEffect(); useEffect(() => { - setErrorMessage(errorMessage) - }, [errorMessage]) + setErrorMessage(errorMessage); + }, [errorMessage]); const exportCSV = (dataType: DOWNLOAD_TYPES, startDate: string, endDate: string): CSVReportRequestDTO => ({ dataType: dataType, csvTimeStamp: csvTimeStamp, startDate: startDate, endDate: endDate, - }) + }); const handleDownload = (dataType: DOWNLOAD_TYPES, startDate: string, endDate: string) => { - fetchExportData(exportCSV(dataType, startDate, endDate)) - } - - const handleBack = () => { - dispatch(backStep()) - } + fetchExportData(exportCSV(dataType, startDate, endDate)); + }; return ( <> - - - }> - {COMMON_BUTTONS.SAVE} - - + + {isShowSave && ( + + }> + {COMMON_BUTTONS.SAVE} + + + )} {COMMON_BUTTONS.BACK} - handleDownload(DOWNLOAD_TYPES.METRICS, startDate, endDate)} - > - {COMMON_BUTTONS.EXPORT_METRIC_DATA} - - {shouldShowBoardExportButton && ( + {isShowExportMetrics && ( + handleDownload(DOWNLOAD_TYPES.METRICS, startDate, endDate)} + > + {COMMON_BUTTONS.EXPORT_METRIC_DATA} + + )} + {isShowExportBoardButton && ( handleDownload(DOWNLOAD_TYPES.BOARD, startDate, endDate)} @@ -107,5 +104,5 @@ export const ReportButtonGroup = ({ {} - ) -} + ); +}; diff --git a/frontend/src/components/Metrics/ReportButtonGroup/style.tsx b/frontend/src/components/Metrics/ReportButtonGroup/style.tsx index 6a1745465f..9e8f7181bd 100644 --- a/frontend/src/components/Metrics/ReportButtonGroup/style.tsx +++ b/frontend/src/components/Metrics/ReportButtonGroup/style.tsx @@ -1,7 +1,7 @@ -import { styled } from '@mui/material/styles' -import { Button } from '@mui/material' -import { theme } from '@src/theme' -import { basicButtonStyle } from '@src/components/Metrics/ReportStep/style' +import { styled } from '@mui/material/styles'; +import { Button } from '@mui/material'; +import { theme } from '@src/theme'; +import { basicButtonStyle } from '@src/components/Metrics/ReportStep/style'; export const StyledRightButtonGroup = styled('div')({ [theme.breakpoints.down('lg')]: { @@ -10,18 +10,18 @@ export const StyledRightButtonGroup = styled('div')({ justifyContent: 'end', alignItems: 'center', }, -}) +}); -export const StyledButtonGroup = styled('div')({ - boxSizing: 'border-box', - display: 'flex', - alignItems: 'center', - textAlign: 'center', - margin: '0 auto', - justifyContent: 'space-between', - width: '100%', - paddingTop: '2rem', -}) +export const StyledButtonGroup = styled('div')` + box-sizing: border-box; + display: flex; + align-items: center; + text-align: center; + margin: 0 auto; + justify-content: ${(props: { isShowSave: boolean }) => (props.isShowSave ? 'space-between' : 'flex-end')}; + width: 100%; + padding-top: 2rem; +`; export const StyledExportButton = styled(Button)({ ...basicButtonStyle, @@ -44,4 +44,4 @@ export const StyledExportButton = styled(Button)({ [theme.breakpoints.down('lg')]: { fontSize: '0.8rem', }, -}) +}); diff --git a/frontend/src/components/Metrics/ReportStep/BoradMetrics/index.tsx b/frontend/src/components/Metrics/ReportStep/BoradMetrics/index.tsx index d6bd0bd4c4..6c904c2c1a 100644 --- a/frontend/src/components/Metrics/ReportStep/BoradMetrics/index.tsx +++ b/frontend/src/components/Metrics/ReportStep/BoradMetrics/index.tsx @@ -1,6 +1,6 @@ -import React, { useEffect } from 'react' -import { useAppSelector } from '@src/hooks' -import { selectConfig, selectJiraColumns } from '@src/context/config/configSlice' +import React, { useEffect } from 'react'; +import { useAppSelector } from '@src/hooks'; +import { selectConfig, selectJiraColumns } from '@src/context/config/configSlice'; import { BOARD_METRICS, CALENDAR, @@ -8,70 +8,79 @@ import { REPORT_PAGE, METRICS_TITLE, REQUIRED_DATA, -} from '@src/constants/resources' -import { BoardReportRequestDTO, ReportRequestDTO } from '@src/clients/report/dto/request' -import { selectMetricsContent } from '@src/context/Metrics/metricsSlice' -import dayjs from 'dayjs' -import { StyledMetricsSection } from '@src/components/Metrics/ReportStep/BoradMetrics/style' -import { filterAndMapCycleTimeSettings, getJiraBoardToken } from '@src/utils/util' -import { ReportTitle } from '@src/components/Common/ReportGrid/ReportTitle' -import { ReportGrid } from '@src/components/Common/ReportGrid' -import { ReportResponseDTO } from '@src/clients/report/dto/response' + SHOW_MORE, +} from '@src/constants/resources'; +import { BoardReportRequestDTO, ReportRequestDTO } from '@src/clients/report/dto/request'; +import { selectMetricsContent } from '@src/context/Metrics/metricsSlice'; +import dayjs from 'dayjs'; +import { + StyledMetricsSection, + StyledShowMore, + StyledTitleWrapper, +} from '@src/components/Metrics/ReportStep/BoradMetrics/style'; +import { filterAndMapCycleTimeSettings, getJiraBoardToken } from '@src/utils/util'; +import { ReportTitle } from '@src/components/Common/ReportGrid/ReportTitle'; +import { ReportGrid } from '@src/components/Common/ReportGrid'; +import { ReportResponseDTO } from '@src/clients/report/dto/response'; +import { Nullable } from '@src/utils/types'; interface BoardMetricsProps { - startToRequestBoardData: (request: ReportRequestDTO) => void - boardReport?: ReportResponseDTO - csvTimeStamp: number - startDate: string | null - endDate: string | null + startToRequestBoardData: (request: ReportRequestDTO) => void; + onShowDetail: () => void; + boardReport?: ReportResponseDTO; + csvTimeStamp: number; + startDate: Nullable; + endDate: Nullable; + isBackFromDetail: boolean; } const BoardMetrics = ({ + isBackFromDetail, startToRequestBoardData, + onShowDetail, boardReport, csvTimeStamp, startDate, endDate, }: BoardMetricsProps) => { - const configData = useAppSelector(selectConfig) + const configData = useAppSelector(selectConfig); const { cycleTimeSettings, treatFlagCardAsBlock, users, targetFields, doneColumn, assigneeFilter } = - useAppSelector(selectMetricsContent) - const { metrics, calendarType } = configData.basic - const { board } = configData - const { token, type, site, projectKey, boardId, email } = board.config - const jiraToken = getJiraBoardToken(token, email) - const jiraColumns = useAppSelector(selectJiraColumns) + useAppSelector(selectMetricsContent); + const jiraColumns = useAppSelector(selectJiraColumns); + + const { metrics, calendarType } = configData.basic; + const { board } = configData; + const { token, type, site, projectKey, boardId, email } = board.config; + const jiraToken = getJiraBoardToken(token, email); const jiraColumnsWithValue = jiraColumns?.map( (obj: { key: string; value: { name: string; statuses: string[] } }) => obj.value - ) - const boardMetrics = metrics.filter((metric) => BOARD_METRICS.includes(metric)) + ); + const boardMetrics = metrics.filter((metric) => BOARD_METRICS.includes(metric)); - const getBoardReportRequestBody = (): BoardReportRequestDTO => { - return { - metrics: boardMetrics, - startTime: dayjs(startDate).valueOf().toString(), - endTime: dayjs(endDate).valueOf().toString(), - considerHoliday: calendarType === CALENDAR.CHINA, - jiraBoardSetting: { - token: jiraToken, - type: type.toLowerCase().replace(' ', '-'), - site, - projectKey, - boardId, - boardColumns: filterAndMapCycleTimeSettings(cycleTimeSettings, jiraColumnsWithValue), - treatFlagCardAsBlock, - users, - assigneeFilter, - targetFields, - doneColumn, - }, - csvTimeStamp: csvTimeStamp, - } - } + const getBoardReportRequestBody = (): BoardReportRequestDTO => ({ + metrics: boardMetrics, + startTime: dayjs(startDate).valueOf().toString(), + endTime: dayjs(endDate).valueOf().toString(), + considerHoliday: calendarType === CALENDAR.CHINA, + jiraBoardSetting: { + token: jiraToken, + type: type.toLowerCase().replace(' ', '-'), + site, + projectKey, + boardId, + boardColumns: filterAndMapCycleTimeSettings(cycleTimeSettings, jiraColumnsWithValue), + treatFlagCardAsBlock, + users, + assigneeFilter, + targetFields, + doneColumn, + }, + csvTimeStamp: csvTimeStamp, + }); const getBoardItems = () => { - const velocity = boardReport?.velocity - const cycleTime = boardReport?.cycleTime + const velocity = boardReport?.velocity; + const cycleTime = boardReport?.cycleTime; const velocityItems = boardMetrics.includes(REQUIRED_DATA.VELOCITY) ? [ { @@ -90,7 +99,7 @@ const BoardMetrics = ({ ], }, ] - : [] + : []; const cycleTimeItems = boardMetrics.includes(REQUIRED_DATA.CYCLE_TIME) ? [ @@ -108,23 +117,26 @@ const BoardMetrics = ({ ], }, ] - : [] + : []; - return [...velocityItems, ...cycleTimeItems] - } + return [...velocityItems, ...cycleTimeItems]; + }; useEffect(() => { - startToRequestBoardData(getBoardReportRequestBody()) - }, []) + !isBackFromDetail && startToRequestBoardData(getBoardReportRequestBody()); + }, []); return ( <> - + + + {boardReport?.isBoardMetricsReady && {SHOW_MORE}} + - ) -} + ); +}; -export default BoardMetrics +export default BoardMetrics; diff --git a/frontend/src/components/Metrics/ReportStep/BoradMetrics/style.tsx b/frontend/src/components/Metrics/ReportStep/BoradMetrics/style.tsx index 84eb460497..be05c615cf 100644 --- a/frontend/src/components/Metrics/ReportStep/BoradMetrics/style.tsx +++ b/frontend/src/components/Metrics/ReportStep/BoradMetrics/style.tsx @@ -1,5 +1,18 @@ -import { styled } from '@mui/material/styles' +import { styled } from '@mui/material/styles'; export const StyledMetricsSection = styled('div')({ marginTop: '2rem', -}) +}); + +export const StyledTitleWrapper = styled('div')({ + display: 'flex', + alignItems: 'center', + marginBottom: '1rem', +}); + +export const StyledShowMore = styled('div')({ + marginLeft: '0.5rem', + fontSize: '0.8rem', + textDecoration: 'none', + color: '#4350AF', +}); diff --git a/frontend/src/components/Metrics/ReportStep/DoraMetrics/index.tsx b/frontend/src/components/Metrics/ReportStep/DoraMetrics/index.tsx index c8ab222eec..1424ac21ba 100644 --- a/frontend/src/components/Metrics/ReportStep/DoraMetrics/index.tsx +++ b/frontend/src/components/Metrics/ReportStep/DoraMetrics/index.tsx @@ -1,6 +1,6 @@ -import React, { useEffect } from 'react' -import { useAppSelector } from '@src/hooks' -import { selectConfig } from '@src/context/config/configSlice' +import React, { useEffect } from 'react'; +import { useAppSelector } from '@src/hooks'; +import { selectConfig } from '@src/context/config/configSlice'; import { CALENDAR, DORA_METRICS, @@ -8,34 +8,47 @@ import { REPORT_PAGE, METRICS_TITLE, REQUIRED_DATA, -} from '@src/constants/resources' -import { ReportRequestDTO } from '@src/clients/report/dto/request' -import { IPipelineConfig, selectMetricsContent } from '@src/context/Metrics/metricsSlice' -import dayjs from 'dayjs' -import { StyledMetricsSection } from '@src/components/Metrics/ReportStep/DoraMetrics/style' -import { ReportTitle } from '@src/components/Common/ReportGrid/ReportTitle' -import { ReportGrid } from '@src/components/Common/ReportGrid' -import { ReportResponseDTO } from '@src/clients/report/dto/response' -import { StyledSpacing } from '@src/components/Metrics/ReportStep/style' -import { formatMillisecondsToHours, formatMinToHours } from '@src/utils/util' + SHOW_MORE, +} from '@src/constants/resources'; +import { ReportRequestDTO } from '@src/clients/report/dto/request'; +import { IPipelineConfig, selectMetricsContent } from '@src/context/Metrics/metricsSlice'; +import dayjs from 'dayjs'; +import { StyledMetricsSection } from '@src/components/Metrics/ReportStep/DoraMetrics/style'; +import { ReportTitle } from '@src/components/Common/ReportGrid/ReportTitle'; +import { ReportGrid } from '@src/components/Common/ReportGrid'; +import { ReportResponseDTO } from '@src/clients/report/dto/response'; +import { StyledSpacing } from '@src/components/Metrics/ReportStep/style'; +import { formatMillisecondsToHours, formatMinToHours } from '@src/utils/util'; +import { StyledShowMore, StyledTitleWrapper } from '@src/components/Metrics/ReportStep/DoraMetrics/style'; +import { Nullable } from '@src/utils/types'; interface DoraMetricsProps { - startToRequestDoraData: (request: ReportRequestDTO) => void - doraReport?: ReportResponseDTO - csvTimeStamp: number - startDate: string | null - endDate: string | null + startToRequestDoraData: (request: ReportRequestDTO) => void; + onShowDetail: () => void; + doraReport?: ReportResponseDTO; + csvTimeStamp: number; + startDate: Nullable; + endDate: Nullable; + isBackFromDetail: boolean; } -const DoraMetrics = ({ startToRequestDoraData, doraReport, csvTimeStamp, startDate, endDate }: DoraMetricsProps) => { - const configData = useAppSelector(selectConfig) - const { pipelineTool, sourceControl } = configData - const { metrics, calendarType } = configData.basic - const { pipelineCrews, deploymentFrequencySettings, leadTimeForChanges } = useAppSelector(selectMetricsContent) - const shouldShowSourceControl = metrics.includes(REQUIRED_DATA.LEAD_TIME_FOR_CHANGES) +const DoraMetrics = ({ + isBackFromDetail, + startToRequestDoraData, + onShowDetail, + doraReport, + csvTimeStamp, + startDate, + endDate, +}: DoraMetricsProps) => { + const configData = useAppSelector(selectConfig); + const { pipelineTool, sourceControl } = configData; + const { metrics, calendarType } = configData.basic; + const { pipelineCrews, deploymentFrequencySettings, leadTimeForChanges } = useAppSelector(selectMetricsContent); + const shouldShowSourceControl = metrics.includes(REQUIRED_DATA.LEAD_TIME_FOR_CHANGES); const getDoraReportRequestBody = (): ReportRequestDTO => { - const doraMetrics = metrics.filter((metric) => DORA_METRICS.includes(metric)) + const doraMetrics = metrics.filter((metric) => DORA_METRICS.includes(metric)); return { metrics: doraMetrics, @@ -53,19 +66,19 @@ const DoraMetrics = ({ startToRequestDoraData, doraReport, csvTimeStamp, startDa leadTime: getPipelineConfig(leadTimeForChanges), }, csvTimeStamp: csvTimeStamp, - } - } + }; + }; const getPipelineConfig = (pipelineConfigs: IPipelineConfig[]) => { if (!pipelineConfigs[0].organization && pipelineConfigs.length === 1) { - return [] + return []; } return pipelineConfigs.map(({ organization, pipelineName, step, branches }) => { const pipelineConfigFromPipelineList = configData.pipelineTool.verifiedResponse.pipelineList.find( (pipeline) => pipeline.name === pipelineName && pipeline.orgName === organization - ) + ); if (pipelineConfigFromPipelineList != undefined) { - const { orgName, orgId, name, id, repository } = pipelineConfigFromPipelineList + const { orgName, orgId, name, id, repository } = pipelineConfigFromPipelineList; return { orgId, orgName, @@ -74,21 +87,21 @@ const DoraMetrics = ({ startToRequestDoraData, doraReport, csvTimeStamp, startDa step, repository, branches, - } + }; } }) as { - id: string - name: string - orgId: string - orgName: string - repository: string - step: string - branches: string[] - }[] - } + id: string; + name: string; + orgId: string; + orgName: string; + repository: string; + step: string; + branches: string[]; + }[]; + }; const getSourceControlItems = () => { - const leadTimeForChanges = doraReport?.leadTimeForChanges + const leadTimeForChanges = doraReport?.leadTimeForChanges; return [ { title: METRICS_TITLE.LEAD_TIME_FOR_CHANGES, @@ -107,13 +120,13 @@ const DoraMetrics = ({ startToRequestDoraData, doraReport, csvTimeStamp, startDa }, ], }, - ] - } + ]; + }; const getPipelineItems = () => { - const deploymentFrequency = doraReport?.deploymentFrequency - const meanTimeToRecovery = doraReport?.meanTimeToRecovery - const changeFailureRate = doraReport?.changeFailureRate + const deploymentFrequency = doraReport?.deploymentFrequency; + const meanTimeToRecovery = doraReport?.meanTimeToRecovery; + const changeFailureRate = doraReport?.changeFailureRate; const deploymentFrequencyList = metrics.includes(REQUIRED_DATA.DEPLOYMENT_FREQUENCY) ? [ @@ -127,7 +140,7 @@ const DoraMetrics = ({ startToRequestDoraData, doraReport, csvTimeStamp, startDa ], }, ] - : [] + : []; const meanTimeToRecoveryList = metrics.includes(REQUIRED_DATA.MEAN_TIME_TO_RECOVERY) ? [ @@ -141,7 +154,7 @@ const DoraMetrics = ({ startToRequestDoraData, doraReport, csvTimeStamp, startDa ], }, ] - : [] + : []; const changeFailureRateList = metrics.includes(REQUIRED_DATA.CHANGE_FAILURE_RATE) ? [ @@ -156,25 +169,30 @@ const DoraMetrics = ({ startToRequestDoraData, doraReport, csvTimeStamp, startDa ], }, ] - : [] + : []; - return [...deploymentFrequencyList, ...changeFailureRateList, ...meanTimeToRecoveryList] - } + return [...deploymentFrequencyList, ...changeFailureRateList, ...meanTimeToRecoveryList]; + }; useEffect(() => { - startToRequestDoraData(getDoraReportRequestBody()) - }, []) + !isBackFromDetail && startToRequestDoraData(getDoraReportRequestBody()); + }, []); return ( <> - + + + {(doraReport?.isPipelineMetricsReady || doraReport?.isSourceControlMetricsReady) && ( + {SHOW_MORE} + )} + {shouldShowSourceControl && } - ) -} + ); +}; -export default DoraMetrics +export default DoraMetrics; diff --git a/frontend/src/components/Metrics/ReportStep/DoraMetrics/style.tsx b/frontend/src/components/Metrics/ReportStep/DoraMetrics/style.tsx index 84eb460497..be05c615cf 100644 --- a/frontend/src/components/Metrics/ReportStep/DoraMetrics/style.tsx +++ b/frontend/src/components/Metrics/ReportStep/DoraMetrics/style.tsx @@ -1,5 +1,18 @@ -import { styled } from '@mui/material/styles' +import { styled } from '@mui/material/styles'; export const StyledMetricsSection = styled('div')({ marginTop: '2rem', -}) +}); + +export const StyledTitleWrapper = styled('div')({ + display: 'flex', + alignItems: 'center', + marginBottom: '1rem', +}); + +export const StyledShowMore = styled('div')({ + marginLeft: '0.5rem', + fontSize: '0.8rem', + textDecoration: 'none', + color: '#4350AF', +}); diff --git a/frontend/src/components/Metrics/ReportStep/ExpiredDialog/index.tsx b/frontend/src/components/Metrics/ReportStep/ExpiredDialog/index.tsx index e479e54036..9d89bd7c66 100644 --- a/frontend/src/components/Metrics/ReportStep/ExpiredDialog/index.tsx +++ b/frontend/src/components/Metrics/ReportStep/ExpiredDialog/index.tsx @@ -1,27 +1,27 @@ -import * as React from 'react' -import { Fragment, useEffect } from 'react' -import Button from '@mui/material/Button' -import Dialog from '@mui/material/Dialog' -import DialogContent from '@mui/material/DialogContent' -import DialogContentText from '@mui/material/DialogContentText' -import ReportGmailerrorredIcon from '@mui/icons-material/ReportGmailerrorred' -import { StyleDialogActions, StyleDialogTitle } from '@src/components/Metrics/ReportStep/ExpiredDialog/style' +import * as React from 'react'; +import { Fragment, useEffect } from 'react'; +import Button from '@mui/material/Button'; +import Dialog from '@mui/material/Dialog'; +import DialogContent from '@mui/material/DialogContent'; +import DialogContentText from '@mui/material/DialogContentText'; +import ReportGmailerrorredIcon from '@mui/icons-material/ReportGmailerrorred'; +import { StyleDialogActions, StyleDialogTitle } from '@src/components/Metrics/ReportStep/ExpiredDialog/style'; export interface ExpiredDialogInterface { - isExpired: boolean - handleOk: () => void + isExpired: boolean; + handleOk: () => void; } export const ExpiredDialog = ({ isExpired, handleOk }: ExpiredDialogInterface) => { - const [open, setOpen] = React.useState(false) + const [open, setOpen] = React.useState(false); const handleClose = () => { - setOpen(false) - } + setOpen(false); + }; useEffect(() => { - setOpen(isExpired) - }, [isExpired]) + setOpen(isExpired); + }, [isExpired]); return ( @@ -46,5 +46,5 @@ export const ExpiredDialog = ({ isExpired, handleOk }: ExpiredDialogInterface) = - ) -} + ); +}; diff --git a/frontend/src/components/Metrics/ReportStep/ExpiredDialog/style.tsx b/frontend/src/components/Metrics/ReportStep/ExpiredDialog/style.tsx index 04612f3910..5456a44e39 100644 --- a/frontend/src/components/Metrics/ReportStep/ExpiredDialog/style.tsx +++ b/frontend/src/components/Metrics/ReportStep/ExpiredDialog/style.tsx @@ -1,11 +1,11 @@ -import { styled } from '@mui/material/styles' -import { DialogActions, DialogTitle } from '@mui/material' +import { styled } from '@mui/material/styles'; +import { DialogActions, DialogTitle } from '@mui/material'; export const StyleDialogTitle = styled(DialogTitle)({ display: 'flex', alignItems: 'center', -}) +}); export const StyleDialogActions = styled(DialogActions)({ padding: '1rem', -}) +}); diff --git a/frontend/src/components/Metrics/ReportStep/ReportDetail/board.tsx b/frontend/src/components/Metrics/ReportStep/ReportDetail/board.tsx new file mode 100644 index 0000000000..531b4930be --- /dev/null +++ b/frontend/src/components/Metrics/ReportStep/ReportDetail/board.tsx @@ -0,0 +1,36 @@ +import React from 'react'; +import { ReportResponseDTO } from '@src/clients/report/dto/response'; +import ReportForThreeColumns from '@src/components/Common/ReportForThreeColumns'; +import ReportForTwoColumns from '@src/components/Common/ReportForTwoColumns'; +import { reportMapper } from '@src/hooks/reportMapper/report'; +import { ReportDataWithTwoColumns } from '@src/hooks/reportMapper/reportUIDataStructure'; +import { Optional } from '@src/utils/types'; +import { withGoBack } from './withBack'; +import { METRICS_TITLE } from '@src/constants/resources'; + +interface Property { + data: ReportResponseDTO; + onBack: () => void; +} + +const showSectionWith2Columns = (title: string, value: Optional) => + value && ; + +export const BoardDetail = withGoBack(({ data }: Property) => { + const mappedData = reportMapper(data); + + return ( + <> + {showSectionWith2Columns(METRICS_TITLE.VELOCITY, mappedData.velocityList)} + {showSectionWith2Columns(METRICS_TITLE.CYCLE_TIME, mappedData.cycleTimeList)} + {mappedData.classification && ( + + )} + + ); +}); diff --git a/frontend/src/components/Metrics/ReportStep/ReportDetail/dora.tsx b/frontend/src/components/Metrics/ReportStep/ReportDetail/dora.tsx new file mode 100644 index 0000000000..6f94b2a2b0 --- /dev/null +++ b/frontend/src/components/Metrics/ReportStep/ReportDetail/dora.tsx @@ -0,0 +1,28 @@ +import React from 'react'; +import { ReportResponseDTO } from '@src/clients/report/dto/response'; +import ReportForThreeColumns from '@src/components/Common/ReportForThreeColumns'; +import { METRICS_TITLE, NAME, PIPELINE_STEP } from '@src/constants/resources'; +import { reportMapper } from '@src/hooks/reportMapper/report'; +import { ReportDataWithThreeColumns } from '@src/hooks/reportMapper/reportUIDataStructure'; +import { Optional } from '@src/utils/types'; +import { withGoBack } from './withBack'; + +interface Property { + data: ReportResponseDTO; + onBack: () => void; +} +const showSection = (title: string, value: Optional) => + value && ; + +export const DoraDetail = withGoBack(({ data }: Property) => { + const mappedData = reportMapper(data); + + return ( + <> + {showSection(METRICS_TITLE.DEPLOYMENT_FREQUENCY, mappedData.deploymentFrequencyList)} + {showSection(METRICS_TITLE.LEAD_TIME_FOR_CHANGES, mappedData.leadTimeForChangesList)} + {showSection(METRICS_TITLE.CHANGE_FAILURE_RATE, mappedData.changeFailureRateList)} + {showSection(METRICS_TITLE.MEAN_TIME_TO_RECOVERY, mappedData.meanTimeToRecoveryList)} + + ); +}); diff --git a/frontend/src/components/Metrics/ReportStep/ReportDetail/index.ts b/frontend/src/components/Metrics/ReportStep/ReportDetail/index.ts new file mode 100644 index 0000000000..a046ec7b37 --- /dev/null +++ b/frontend/src/components/Metrics/ReportStep/ReportDetail/index.ts @@ -0,0 +1,2 @@ +export * from '@src/components/Metrics/ReportStep/ReportDetail/board'; +export * from '@src/components/Metrics/ReportStep/ReportDetail/dora'; diff --git a/frontend/src/components/Metrics/ReportStep/ReportDetail/index.tsx b/frontend/src/components/Metrics/ReportStep/ReportDetail/index.tsx deleted file mode 100644 index 3f60fa0dab..0000000000 --- a/frontend/src/components/Metrics/ReportStep/ReportDetail/index.tsx +++ /dev/null @@ -1,301 +0,0 @@ -// import { useEffect, useLayoutEffect, useState } from 'react' -// import { useGenerateReportEffect } from '@src/hooks/useGenerateReportEffect' -// import { Loading } from '@src/components/Loading' -// import { useAppSelector } from '@src/hooks' -// import { selectConfig, selectJiraColumns, selectMetrics } from '@src/context/config/configSlice' -// import { CALENDAR, PIPELINE_STEP, NAME, REQUIRED_DATA, MESSAGE, TIPS } from '@src/constants/resources' -// import { -// COMMON_BUTTONS, -// DOWNLOAD_TYPES, -// INIT_REPORT_DATA_WITH_THREE_COLUMNS, -// INIT_REPORT_DATA_WITH_TWO_COLUMNS, -// } from '@src/constants/commons' -// import ReportForTwoColumns from '@src/components/Common/ReportForTwoColumns' -// import ReportForThreeColumns from '@src/components/Common/ReportForThreeColumns' -// import { CSVReportRequestDTO, ReportRequestDTO } from '@src/clients/report/dto/request' -// import { IPipelineConfig, selectMetricsContent } from '@src/context/Metrics/metricsSlice' -// import dayjs from 'dayjs' -// import { BackButton, SaveButton } from '@src/components/Metrics/MetricsStepper/style' -// import { useExportCsvEffect } from '@src/hooks/useExportCsvEffect' -// import { backStep, selectTimeStamp } from '@src/context/stepper/StepperSlice' -// import { useAppDispatch } from '@src/hooks/useAppDispatch' -// import { ErrorNotification } from '@src/components/ErrorNotification' -// import { useNavigate } from 'react-router-dom' -// import CollectionDuration from '@src/components/Common/CollectionDuration' -// import { ExpiredDialog } from '@src/components/Metrics/ReportStep/ExpiredDialog' -// import { filterAndMapCycleTimeSettings, getJiraBoardToken } from '@src/utils/util' -// import { useNotificationLayoutEffectInterface } from '@src/hooks/useNotificationLayoutEffect' -// import { ReportResponse } from '@src/clients/report/dto/response' -// import { ROUTE } from '@src/constants/router' -// import { Tooltip } from '@mui/material' -// import SaveAltIcon from '@mui/icons-material/SaveAlt' -// import { -// ButtonGroupStyle, -// ErrorNotificationContainer, -// ExportButton, -// } from '@src/components/Metrics/ReportStep/ReportDetail/style' -// -// interface ReportDetailInterface extends useNotificationLayoutEffectInterface { -// handleSave: () => void -// } -// -// const ReportDetail = ({ updateProps, handleSave }: ReportDetailInterface) => { -// const dispatch = useAppDispatch() -// const navigate = useNavigate() -// const { -// startPollingReports, -// stopPollingReports, -// reports, -// isLoading, -// isServerError, -// errorMessage: reportErrorMsg, -// } = useGenerateReportEffect() -// const { fetchExportData, errorMessage, isExpired } = useExportCsvEffect() -// const [velocityState, setVelocityState] = useState({ value: INIT_REPORT_DATA_WITH_TWO_COLUMNS, isShow: false }) -// const [cycleTimeState, setCycleTimeState] = useState({ value: INIT_REPORT_DATA_WITH_TWO_COLUMNS, isShow: false }) -// const [classificationState, setClassificationState] = useState({ -// value: INIT_REPORT_DATA_WITH_THREE_COLUMNS, -// isShow: false, -// }) -// const [deploymentFrequencyState, setDeploymentFrequencyState] = useState({ -// value: INIT_REPORT_DATA_WITH_THREE_COLUMNS, -// isShow: false, -// }) -// const [meanTimeToRecoveryState, setMeanTimeToRecoveryState] = useState({ -// value: INIT_REPORT_DATA_WITH_THREE_COLUMNS, -// isShow: false, -// }) -// const [leadTimeForChangesState, setLeadTimeForChangesState] = useState({ -// value: INIT_REPORT_DATA_WITH_THREE_COLUMNS, -// isShow: false, -// }) -// const [changeFailureRateState, setChangeFailureRateState] = useState({ -// value: INIT_REPORT_DATA_WITH_THREE_COLUMNS, -// isShow: false, -// }) -// const [exportValidityTimeMin, setExportValidityTimeMin] = useState(undefined) -// const csvTimeStamp = useAppSelector(selectTimeStamp) -// const configData = useAppSelector(selectConfig) -// const { -// cycleTimeSettings, -// treatFlagCardAsBlock, -// users, -// pipelineCrews, -// targetFields, -// doneColumn, -// deploymentFrequencySettings, -// leadTimeForChanges, -// assigneeFilter, -// } = useAppSelector(selectMetricsContent) -// const { metrics, calendarType, dateRange } = configData.basic -// const { board, pipelineTool, sourceControl } = configData -// const { token, type, site, projectKey, boardId, email } = board.config -// const { startDate, endDate } = dateRange -// const requiredData = useAppSelector(selectMetrics) -// const isShowExportBoardButton = -// requiredData.includes(REQUIRED_DATA.VELOCITY) || -// requiredData.includes(REQUIRED_DATA.CYCLE_TIME) || -// requiredData.includes(REQUIRED_DATA.CLASSIFICATION) -// const isShowExportPipelineButton = -// requiredData.includes(REQUIRED_DATA.DEPLOYMENT_FREQUENCY) || -// requiredData.includes(REQUIRED_DATA.CHANGE_FAILURE_RATE) || -// requiredData.includes(REQUIRED_DATA.LEAD_TIME_FOR_CHANGES) || -// requiredData.includes(REQUIRED_DATA.MEAN_TIME_TO_RECOVERY) -// -// const getPipelineConfig = (pipelineConfigs: IPipelineConfig[]) => { -// if (!pipelineConfigs[0].organization && pipelineConfigs.length === 1) { -// return [] -// } -// return pipelineConfigs.map(({ organization, pipelineName, step, branches }) => { -// const pipelineConfigFromPipelineList = configData.pipelineTool.verifiedResponse.pipelineList.find( -// (pipeline) => pipeline.name === pipelineName && pipeline.orgName === organization -// ) -// if (pipelineConfigFromPipelineList != undefined) { -// const { orgName, orgId, name, id, repository } = pipelineConfigFromPipelineList -// return { -// orgId, -// orgName, -// id, -// name, -// step, -// repository, -// branches, -// } -// } -// }) as { -// id: string -// name: string -// orgId: string -// orgName: string -// repository: string -// step: string -// branches: string[] -// }[] -// } -// -// const jiraColumns = useAppSelector(selectJiraColumns) -// const jiraColumnsWithValue = jiraColumns?.map( -// (obj: { key: string; value: { name: string; statuses: string[] } }) => obj.value -// ) -// -// const jiraToken = getJiraBoardToken(token, email) -// const getReportRequestBody = (): ReportRequestDTO => ({ -// metrics: metrics, -// startTime: dayjs(startDate).valueOf().toString(), -// endTime: dayjs(endDate).valueOf().toString(), -// considerHoliday: calendarType === CALENDAR.CHINA, -// buildKiteSetting: { -// pipelineCrews, -// ...pipelineTool.config, -// deploymentEnvList: getPipelineConfig(deploymentFrequencySettings), -// }, -// codebaseSetting: { -// type: sourceControl.config.type, -// token: sourceControl.config.token, -// leadTime: getPipelineConfig(leadTimeForChanges), -// }, -// jiraBoardSetting: { -// token: jiraToken, -// type: type.toLowerCase().replace(' ', '-'), -// site, -// projectKey, -// boardId, -// boardColumns: filterAndMapCycleTimeSettings(cycleTimeSettings, jiraColumnsWithValue), -// treatFlagCardAsBlock, -// users, -// assigneeFilter, -// targetFields, -// doneColumn, -// }, -// csvTimeStamp: csvTimeStamp, -// }) -// -// const getExportCSV = ( -// dataType: DOWNLOAD_TYPES, -// startDate: string | null, -// endDate: string | null -// ): CSVReportRequestDTO => ({ -// dataType: dataType, -// csvTimeStamp: csvTimeStamp, -// startDate: startDate ?? '', -// endDate: endDate ?? '', -// }) -// -// useEffect(() => { -// startPollingReports(getReportRequestBody()) -// }, []) -// -// useEffect(() => { -// updateReportData(reports) -// return () => { -// stopPollingReports() -// } -// }, [reports]) -// -// const updateReportData = (res: ReportResponse | undefined) => { -// res?.velocityList && setVelocityState({ ...velocityState, value: res.velocityList, isShow: true }) -// res?.cycleTimeList && setCycleTimeState({ ...cycleTimeState, value: res.cycleTimeList, isShow: true }) -// res?.classification && setClassificationState({ value: res.classification, isShow: true }) -// res?.deploymentFrequencyList && -// setDeploymentFrequencyState({ -// ...deploymentFrequencyState, -// value: res.deploymentFrequencyList, -// isShow: true, -// }) -// res?.meanTimeToRecoveryList && -// setMeanTimeToRecoveryState({ -// ...meanTimeToRecoveryState, -// value: res.meanTimeToRecoveryList, -// isShow: true, -// }) -// res?.changeFailureRateList && -// setChangeFailureRateState({ -// ...changeFailureRateState, -// value: res.changeFailureRateList, -// isShow: true, -// }) -// res?.leadTimeForChangesList && -// setLeadTimeForChangesState({ -// ...leadTimeForChangesState, -// value: res.leadTimeForChangesList, -// isShow: true, -// }) -// res?.exportValidityTimeMin && setExportValidityTimeMin(res.exportValidityTimeMin) -// } -// -// const handleDownload = (dataType: DOWNLOAD_TYPES, startDate: string | null, endDate: string | null) => { -// fetchExportData(getExportCSV(dataType, startDate, endDate)) -// } -// -// const handleBack = () => { -// dispatch(backStep()) -// } -// -// return ( -// <> -// {isLoading ? ( -// -// ) : isServerError ? ( -// navigate(ROUTE.ERROR_PAGE) -// ) : ( -// <> -// {startDate && endDate && } -// {reportErrorMsg && ( -// -// -// -// )} -// {errorMessage && ( -// -// -// -// )} -// {velocityState.isShow && } -// {cycleTimeState.isShow && } -// {classificationState.isShow && ( -// -// )} -// {deploymentFrequencyState.isShow && ( -// -// )} -// {leadTimeForChangesState.isShow && ( -// -// )} -// {changeFailureRateState.isShow && ( -// -// )} -// {meanTimeToRecoveryState.isShow && ( -// -// )} -// -// )} -// -// ) -// } -// -// export default ReportDetail diff --git a/frontend/src/components/Metrics/ReportStep/ReportDetail/style.tsx b/frontend/src/components/Metrics/ReportStep/ReportDetail/style.tsx index 709f0d816c..e69de29bb2 100644 --- a/frontend/src/components/Metrics/ReportStep/ReportDetail/style.tsx +++ b/frontend/src/components/Metrics/ReportStep/ReportDetail/style.tsx @@ -1,26 +0,0 @@ -import { styled } from '@mui/material/styles' -import { Button } from '@mui/material' -import { theme } from '@src/theme' -import { Z_INDEX } from '@src/constants/commons' -import { basicButtonStyle } from '@src/components/Metrics/ReportStep/style' - -export const ExportButton = styled(Button)({ - ...basicButtonStyle, - width: '12rem', - backgroundColor: theme.main.backgroundColor, - color: theme.main.color, - '&:hover': { - ...basicButtonStyle, - backgroundColor: theme.main.backgroundColor, - color: theme.main.color, - }, -}) - -export const ErrorNotificationContainer = styled('div')({ - position: 'fixed', - top: '50%', - left: '50%', - transform: 'translate(-50%, -50%)', - zIndex: Z_INDEX.MODAL_BACKDROP, - width: '80%', -}) diff --git a/frontend/src/components/Metrics/ReportStep/ReportDetail/withBack.tsx b/frontend/src/components/Metrics/ReportStep/ReportDetail/withBack.tsx new file mode 100644 index 0000000000..2087dff0e9 --- /dev/null +++ b/frontend/src/components/Metrics/ReportStep/ReportDetail/withBack.tsx @@ -0,0 +1,36 @@ +import React from 'react'; +import { styled } from '@mui/material/styles'; +import { ArrowBack } from '@mui/icons-material'; +import { BACK } from '@src/constants/resources'; +interface Property { + onBack: () => void; +} + +const StyledDiv = styled('div')` + display: flex; + align-items: center; + width: max-content; + z-index: 2; + margin-bottom: 2.5rem; + color: #595959; + cursor: pointer; + font-size: 1rem; +`; + +const StyledArrowBack = styled(ArrowBack)` + width: 1.5rem; + margin-right: 0.5rem; +`; + +export const withGoBack = +

    (Child: React.ComponentType

    ) => + (prop: P) => + ( + <> + + + {BACK} + + + + ); diff --git a/frontend/src/components/Metrics/ReportStep/index.tsx b/frontend/src/components/Metrics/ReportStep/index.tsx index 04d82f1e8a..ef83e188b6 100644 --- a/frontend/src/components/Metrics/ReportStep/index.tsx +++ b/frontend/src/components/Metrics/ReportStep/index.tsx @@ -1,25 +1,31 @@ -import React, { useEffect, useLayoutEffect, useState } from 'react' -import { useGenerateReportEffect } from '@src/hooks/useGenerateReportEffect' -import { useAppSelector } from '@src/hooks' -import { selectConfig } from '@src/context/config/configSlice' -import { BOARD_METRICS, DORA_METRICS, MESSAGE } from '@src/constants/resources' -import { StyledErrorNotification } from '@src/components/Metrics/ReportStep/style' -import { ErrorNotification } from '@src/components/ErrorNotification' -import { useNavigate } from 'react-router-dom' -import { useNotificationLayoutEffectInterface } from '@src/hooks/useNotificationLayoutEffect' -import { ROUTE } from '@src/constants/router' -import { ReportButtonGroup } from '@src/components/Metrics/ReportButtonGroup' -import BoardMetrics from '@src/components/Metrics/ReportStep/BoradMetrics' -import DoraMetrics from '@src/components/Metrics/ReportStep/DoraMetrics' -import { selectTimeStamp } from '@src/context/stepper/StepperSlice' +import React, { useEffect, useLayoutEffect, useState } from 'react'; +import { useGenerateReportEffect } from '@src/hooks/useGenerateReportEffect'; +import { useAppSelector } from '@src/hooks'; +import { isSelectBoardMetrics, isSelectDoraMetrics, selectConfig } from '@src/context/config/configSlice'; +import { MESSAGE, REPORT_PAGE_TYPE } from '@src/constants/resources'; +import { StyledErrorNotification } from '@src/components/Metrics/ReportStep/style'; +import { ErrorNotification } from '@src/components/ErrorNotification'; +import { useNavigate } from 'react-router-dom'; +import { useNotificationLayoutEffectInterface } from '@src/hooks/useNotificationLayoutEffect'; +import { ROUTE } from '@src/constants/router'; +import { ReportButtonGroup } from '@src/components/Metrics/ReportButtonGroup'; +import BoardMetrics from '@src/components/Metrics/ReportStep/BoradMetrics'; +import DoraMetrics from '@src/components/Metrics/ReportStep/DoraMetrics'; +import { backStep, selectTimeStamp } from '@src/context/stepper/StepperSlice'; +import DateRangeViewer from '@src/components/Common/DateRangeViewer'; +import { MetricSelectionHeader } from '../MetricsStep/style'; +import { BoardDetail, DoraDetail } from './ReportDetail'; +import { ReportResponseDTO } from '@src/clients/report/dto/response'; +import { useAppDispatch } from '@src/hooks/useAppDispatch'; export interface ReportStepProps { - notification: useNotificationLayoutEffectInterface - handleSave: () => void + notification: useNotificationLayoutEffectInterface; + handleSave: () => void; } const ReportStep = ({ notification, handleSave }: ReportStepProps) => { - const navigate = useNavigate() + const navigate = useNavigate(); + const dispatch = useAppDispatch(); const { isServerError, errorMessage: reportErrorMsg, @@ -27,21 +33,22 @@ const ReportStep = ({ notification, handleSave }: ReportStepProps) => { startToRequestDoraData, reportData, stopPollingReports, - } = useGenerateReportEffect() + } = useGenerateReportEffect(); - const [exportValidityTimeMin, setExportValidityTimeMin] = useState(undefined) - const configData = useAppSelector(selectConfig) - const csvTimeStamp = useAppSelector(selectTimeStamp) + const [exportValidityTimeMin, setExportValidityTimeMin] = useState(undefined); + const [pageType, setPageType] = useState(REPORT_PAGE_TYPE.SUMMARY); + const [isBackFromDetail, setIsBackFromDetail] = useState(false); + const configData = useAppSelector(selectConfig); + const csvTimeStamp = useAppSelector(selectTimeStamp); - const startDate = configData.basic.dateRange.startDate ?? '' - const endDate = configData.basic.dateRange.endDate ?? '' + const startDate = configData.basic.dateRange.startDate ?? ''; + const endDate = configData.basic.dateRange.endDate ?? ''; - const { updateProps } = notification - const [errorMessage, setErrorMessage] = useState() + const { updateProps } = notification; + const [errorMessage, setErrorMessage] = useState(); - const { metrics } = configData.basic - const shouldShowBoardMetrics = metrics.some((metric) => BOARD_METRICS.includes(metric)) - const shouldShowDoraMetrics = metrics.some((metric) => DORA_METRICS.includes(metric)) + const shouldShowBoardMetrics = useAppSelector(isSelectBoardMetrics); + const shouldShowDoraMetrics = useAppSelector(isSelectDoraMetrics); useLayoutEffect(() => { exportValidityTimeMin && @@ -49,47 +56,85 @@ const ReportStep = ({ notification, handleSave }: ReportStepProps) => { open: true, title: MESSAGE.NOTIFICATION_FIRST_REPORT.replace('%s', exportValidityTimeMin.toString()), closeAutomatically: true, - }) - }, [exportValidityTimeMin]) + }); + }, [exportValidityTimeMin]); useLayoutEffect(() => { if (exportValidityTimeMin) { - const startTime = Date.now() + const startTime = Date.now(); const timer = setInterval(() => { - const currentTime = Date.now() - const elapsedTime = currentTime - startTime + const currentTime = Date.now(); + const elapsedTime = currentTime - startTime; - const remainingExpireTime = 5 * 60 * 1000 - const remainingTime = exportValidityTimeMin * 60 * 1000 - elapsedTime + const remainingExpireTime = 5 * 60 * 1000; + const remainingTime = exportValidityTimeMin * 60 * 1000 - elapsedTime; if (remainingTime <= remainingExpireTime) { updateProps?.({ open: true, title: MESSAGE.EXPIRE_IN_FIVE_MINUTES, closeAutomatically: true, - }) - clearInterval(timer) + }); + clearInterval(timer); } - }, 1000) + }, 1000); return () => { - clearInterval(timer) - } + clearInterval(timer); + }; } - }, [exportValidityTimeMin]) + }, [exportValidityTimeMin]); useEffect(() => { - setErrorMessage(reportErrorMsg) - }, [reportErrorMsg]) + setErrorMessage(reportErrorMsg); + }, [reportErrorMsg]); useEffect(() => { - setExportValidityTimeMin(reportData?.exportValidityTime) - }, [reportData]) + setExportValidityTimeMin(reportData?.exportValidityTime); + }, [reportData]); useLayoutEffect(() => { return () => { - stopPollingReports() - } - }, []) + stopPollingReports(); + }; + }, []); + + const showSummary = () => ( + <> + {shouldShowBoardMetrics && ( + setPageType(REPORT_PAGE_TYPE.BOARD)} + boardReport={reportData} + csvTimeStamp={csvTimeStamp} + /> + )} + {shouldShowDoraMetrics && ( + setPageType(REPORT_PAGE_TYPE.DORA)} + doraReport={reportData} + csvTimeStamp={csvTimeStamp} + /> + )} + + ); + const showBoardDetail = (data: ReportResponseDTO) => backToSummaryPage()} data={data} />; + const showDoraDetail = (data: ReportResponseDTO) => backToSummaryPage()} data={data} />; + + const handleBack = () => { + pageType === REPORT_PAGE_TYPE.SUMMARY ? dispatch(backStep()) : backToSummaryPage(); + }; + + const backToSummaryPage = () => { + setPageType(REPORT_PAGE_TYPE.SUMMARY); + setIsBackFromDetail(true); + }; return ( <> @@ -97,46 +142,43 @@ const ReportStep = ({ notification, handleSave }: ReportStepProps) => { navigate(ROUTE.ERROR_PAGE) ) : ( <> + {startDate && endDate && ( + + + + )} {errorMessage && ( )} - <> - {shouldShowBoardMetrics && ( - - )} - {shouldShowDoraMetrics && ( - - )} - + {pageType === REPORT_PAGE_TYPE.SUMMARY + ? showSummary() + : !!reportData && + (pageType === REPORT_PAGE_TYPE.BOARD ? showBoardDetail(reportData) : showDoraDetail(reportData))} handleBack()} handleSave={() => handleSave()} reportData={reportData} - shouldShowBoardExportButton={shouldShowBoardMetrics} startDate={startDate} endDate={endDate} csvTimeStamp={csvTimeStamp} setErrorMessage={(message) => { - setErrorMessage(message) + setErrorMessage(message); }} /> )} - ) -} + ); +}; -export default ReportStep +export default ReportStep; diff --git a/frontend/src/components/Metrics/ReportStep/style.tsx b/frontend/src/components/Metrics/ReportStep/style.tsx index 3d0f2f1624..f0771828dd 100644 --- a/frontend/src/components/Metrics/ReportStep/style.tsx +++ b/frontend/src/components/Metrics/ReportStep/style.tsx @@ -1,14 +1,14 @@ -import { styled } from '@mui/material/styles' -import { Z_INDEX } from '@src/constants/commons' -import { theme } from '@src/theme' +import { styled } from '@mui/material/styles'; +import { Z_INDEX } from '@src/constants/commons'; +import { theme } from '@src/theme'; export const StyledErrorNotification = styled('div')({ zIndex: Z_INDEX.MODAL_BACKDROP, -}) +}); export const StyledSpacing = styled('div')({ height: '1.5rem', -}) +}); export const basicButtonStyle = { height: '2.5rem', @@ -17,4 +17,4 @@ export const basicButtonStyle = { fontSize: '1rem', fontWeight: '500', textTransform: theme.typography.button.textTransform, -} +}; diff --git a/frontend/src/components/ProjectDescription.tsx b/frontend/src/components/ProjectDescription.tsx index 63319a8067..c336c01928 100644 --- a/frontend/src/components/ProjectDescription.tsx +++ b/frontend/src/components/ProjectDescription.tsx @@ -1,16 +1,16 @@ -import styled from '@emotion/styled' -import { theme } from '@src/theme' +import styled from '@emotion/styled'; +import { theme } from '@src/theme'; const DescriptionContainer = styled.p({ padding: '1rem', marginTop: '0', boxShadow: `0 0.8rem 0.7rem -0.5rem ${theme.palette.grey[500]}`, -}) +}); export const ProjectDescription = () => { return ( {`Heartbeat is a tool for tracking project delivery metrics that can help you get a better understanding of delivery performance. This product allows you easily get all aspects of source data faster and more accurate to analyze team delivery performance which enables delivery teams and team leaders focusing on driving continuous improvement and enhancing team productivity and efficiency.`} - ) -} + ); +}; diff --git a/frontend/src/config/routes.ts b/frontend/src/config/routes.ts index 87cb5760d5..a2c3731949 100644 --- a/frontend/src/config/routes.ts +++ b/frontend/src/config/routes.ts @@ -1,4 +1,4 @@ -import { lazy } from 'react' +import { lazy } from 'react'; export const routes = [ { @@ -22,4 +22,4 @@ export const routes = [ component: lazy(() => import('../pages/Home')), name: 'Home', }, -] +]; diff --git a/frontend/src/constants/commons.ts b/frontend/src/constants/commons.ts index e60e748e1c..d9cc36b772 100644 --- a/frontend/src/constants/commons.ts +++ b/frontend/src/constants/commons.ts @@ -1,23 +1,23 @@ -import { ReportDataWithThreeColumns, ReportDataWithTwoColumns } from '@src/hooks/reportMapper/reportUIDataStructure' +import { ReportDataWithThreeColumns, ReportDataWithTwoColumns } from '@src/hooks/reportMapper/reportUIDataStructure'; -export const PROJECT_NAME = 'Heartbeat' +export const PROJECT_NAME = 'Heartbeat'; -export const DEFAULT_HELPER_TEXT = ' ' +export const DEFAULT_HELPER_TEXT = ' '; -export const FIVE_HUNDRED = 500 +export const FIVE_HUNDRED = 500; -export const ZERO = 0 +export const ZERO = 0; -export const EMPTY_STRING = '' +export const EMPTY_STRING = ''; -export const STEPS = ['Config', 'Metrics', 'Report'] +export const STEPS = ['Config', 'Metrics', 'Report']; -export const SELECTED_VALUE_SEPARATOR = ', ' +export const SELECTED_VALUE_SEPARATOR = ', '; export const DURATION = { ERROR_MESSAGE_TIME: 4000, NOTIFICATION_TIME: 10000, -} +}; export const INIT_REPORT_DATA_WITH_TWO_COLUMNS: ReportDataWithTwoColumns[] = [ { @@ -25,7 +25,7 @@ export const INIT_REPORT_DATA_WITH_TWO_COLUMNS: ReportDataWithTwoColumns[] = [ name: '', valueList: [{ value: 0, unit: '' }], }, -] +]; export const INIT_REPORT_DATA_WITH_THREE_COLUMNS: ReportDataWithThreeColumns[] = [ { @@ -38,7 +38,7 @@ export const INIT_REPORT_DATA_WITH_THREE_COLUMNS: ReportDataWithThreeColumns[] = }, ], }, -] +]; export const Z_INDEX = { DEFAULT: 0, @@ -53,7 +53,7 @@ export const Z_INDEX = { TOOLTIP: 1050, STICKY: 1060, FIXED: 1070, -} +}; export enum DOWNLOAD_TYPES { METRICS = 'metric', @@ -70,7 +70,7 @@ export const METRICS_STEPS = { CONFIG: 0, METRICS: 1, REPORT: 2, -} +}; export const COMMON_BUTTONS = { SAVE: 'Save', @@ -79,9 +79,9 @@ export const COMMON_BUTTONS = { EXPORT_PIPELINE_DATA: 'Export pipeline data', EXPORT_BOARD_DATA: 'Export board data', EXPORT_METRIC_DATA: 'Export metric data', -} +}; export const GRID_CONFIG = { HALF: { XS: 6, MAX_INDEX: 2, FLEX: 1 }, FULL: { XS: 12, MAX_INDEX: 4, FLEX: 0.25 }, -} +}; diff --git a/frontend/src/constants/regex.ts b/frontend/src/constants/regex.ts index 39ad5bb444..71e7d7549b 100644 --- a/frontend/src/constants/regex.ts +++ b/frontend/src/constants/regex.ts @@ -3,4 +3,4 @@ export const REGEX = { BOARD_TOKEN: /^[a-zA-Z0-9\-=_]{1,500}$/, BUILDKITE_TOKEN: /^(bkua)?_?([a-zA-Z0-9]{40})$/, GITHUB_TOKEN: /^(ghp|gho|ghu|ghs|ghr)+_+([a-zA-Z0-9]{36})$/, -} +}; diff --git a/frontend/src/constants/resources.ts b/frontend/src/constants/resources.ts index 9c89dc944d..54b8335fec 100644 --- a/frontend/src/constants/resources.ts +++ b/frontend/src/constants/resources.ts @@ -1,7 +1,16 @@ export const CALENDAR = { REGULAR: 'Regular Calendar(Weekend Considered)', CHINA: 'Calendar with Chinese Holiday', -} +}; + +export const REPORT_PAGE_TYPE = { + SUMMARY: 'Summary', + BOARD: 'BoardReport', + DORA: 'DoraReport', +}; + +export const SHOW_MORE = 'show more >'; +export const BACK = 'Back'; export enum REQUIRED_DATA { All = 'All', @@ -42,9 +51,9 @@ export const DORA_METRICS: string[] = [ REQUIRED_DATA.DEPLOYMENT_FREQUENCY, REQUIRED_DATA.CHANGE_FAILURE_RATE, REQUIRED_DATA.MEAN_TIME_TO_RECOVERY, -] +]; -export const BOARD_METRICS: string[] = [REQUIRED_DATA.VELOCITY, REQUIRED_DATA.CYCLE_TIME, REQUIRED_DATA.CLASSIFICATION] +export const BOARD_METRICS: string[] = [REQUIRED_DATA.VELOCITY, REQUIRED_DATA.CYCLE_TIME, REQUIRED_DATA.CLASSIFICATION]; export enum CONFIG_TITLE { BOARD = 'Board', @@ -55,16 +64,16 @@ export enum CONFIG_TITLE { export const BOARD_TYPES = { CLASSIC_JIRA: 'Classic Jira', JIRA: 'Jira', -} +}; export const PIPELINE_TOOL_TYPES = { BUILD_KITE: 'BuildKite', GO_CD: 'GoCD', -} +}; export const SOURCE_CONTROL_TYPES = { GITHUB: 'GitHub', -} +}; export enum PIPELINE_SETTING_TYPES { DEPLOYMENT_FREQUENCY_SETTINGS_TYPE = 'DeploymentFrequencySettings', @@ -74,13 +83,13 @@ export enum PIPELINE_SETTING_TYPES { export const ASSIGNEE_FILTER_TYPES = { LAST_ASSIGNEE: 'lastAssignee', HISTORICAL_ASSIGNEE: 'historicalAssignee', -} +}; -export const EMAIL = 'Email' +export const EMAIL = 'Email'; -export const BOARD_TOKEN = 'Token' +export const BOARD_TOKEN = 'Token'; -export const DONE = 'Done' +export const DONE = 'Done'; export const METRICS_CONSTANTS = { cycleTimeEmptyStr: '----', @@ -93,7 +102,7 @@ export const METRICS_CONSTANTS = { waitingValue: 'Waiting for testing', testingValue: 'Testing', reviewValue: 'Review', -} +}; export const CYCLE_TIME_LIST = [ METRICS_CONSTANTS.cycleTimeEmptyStr, @@ -105,18 +114,18 @@ export const CYCLE_TIME_LIST = [ METRICS_CONSTANTS.testingValue, METRICS_CONSTANTS.reviewValue, METRICS_CONSTANTS.doneValue, -] +]; export const TOKEN_HELPER_TEXT = { RequiredTokenText: 'Token is required', InvalidTokenText: 'Token is invalid', -} +}; export const TIPS = { SAVE_CONFIG: 'Note: When you save the settings, some tokens might be saved, please save it safely (e.g. by 1 password, vault), Rotate the tokens regularly. (e.g. every 3 months)', CYCLE_TIME: 'The report page will sum all the status in the column for cycletime calculation', -} +}; export enum VELOCITY_METRICS_NAME { VELOCITY_SP = 'Velocity(Story Point)', @@ -137,17 +146,17 @@ export enum CYCLE_TIME_METRICS_NAME { AVERAGE_TESTING_TIME = 'Average testing time', } -export const DEPLOYMENT_FREQUENCY_NAME = 'Deployment frequency(deployments/day)' +export const DEPLOYMENT_FREQUENCY_NAME = 'Deployment frequency(deployments/day)'; -export const FAILURE_RATE_NAME = 'Failure rate' +export const FAILURE_RATE_NAME = 'Failure rate'; -export const MEAN_TIME_TO_RECOVERY_NAME = 'Mean Time To Recovery' +export const MEAN_TIME_TO_RECOVERY_NAME = 'Mean Time To Recovery'; -export const PIPELINE_STEP = 'Pipeline/step' +export const PIPELINE_STEP = 'Pipeline/step'; -export const NAME = 'Name' +export const NAME = 'Name'; -export const AVERAGE_FIELD = 'Average' +export const AVERAGE_FIELD = 'Average'; export enum REPORT_SUFFIX_UNITS { PER_SP = '(days/SP)', @@ -172,7 +181,7 @@ export const MESSAGE = { NOTIFICATION_FIRST_REPORT: 'The file needs to be exported within %s minutes, otherwise it will expire.', EXPIRE_IN_FIVE_MINUTES: 'The file will expire in 5 minutes, please download it in time.', REPORT_LOADING: 'The report is being generated, please do not refresh the page or all the data will be disappeared.', -} +}; export const METRICS_CYCLE_SETTING_TABLE_HEADER = [ { @@ -187,7 +196,7 @@ export const METRICS_CYCLE_SETTING_TABLE_HEADER = [ text: 'Heartbeat State', emphasis: true, }, -] +]; export const REPORT_PAGE = { BOARD: { @@ -196,4 +205,4 @@ export const REPORT_PAGE = { DORA: { TITLE: 'DORA Metrics', }, -} +}; diff --git a/frontend/src/constants/template.ts b/frontend/src/constants/template.ts index 9cb28f04c6..7e2fef9b28 100644 --- a/frontend/src/constants/template.ts +++ b/frontend/src/constants/template.ts @@ -1 +1 @@ -export const DATE_FORMAT_TEMPLATE = 'YYYY/MM/DD' +export const DATE_FORMAT_TEMPLATE = 'YYYY/MM/DD'; diff --git a/frontend/src/context/Metrics/metricsSlice.tsx b/frontend/src/context/Metrics/metricsSlice.ts similarity index 70% rename from frontend/src/context/Metrics/metricsSlice.tsx rename to frontend/src/context/Metrics/metricsSlice.ts index 1510a71c54..bacdd7e7e5 100644 --- a/frontend/src/context/Metrics/metricsSlice.tsx +++ b/frontend/src/context/Metrics/metricsSlice.ts @@ -1,62 +1,62 @@ /* istanbul ignore file */ -import { createSlice } from '@reduxjs/toolkit' -import camelCase from 'lodash.camelcase' -import { RootState } from '@src/store' -import { ASSIGNEE_FILTER_TYPES, CYCLE_TIME_LIST, MESSAGE, METRICS_CONSTANTS } from '@src/constants/resources' -import { pipeline } from '@src/context/config/pipelineTool/verifyResponseSlice' -import _ from 'lodash' +import { createSlice } from '@reduxjs/toolkit'; +import camelCase from 'lodash.camelcase'; +import { RootState } from '@src/store'; +import { ASSIGNEE_FILTER_TYPES, CYCLE_TIME_LIST, MESSAGE, METRICS_CONSTANTS } from '@src/constants/resources'; +import { pipeline } from '@src/context/config/pipelineTool/verifyResponseSlice'; +import _ from 'lodash'; export interface IPipelineConfig { - id: number - organization: string - pipelineName: string - step: string - branches: string[] + id: number; + organization: string; + pipelineName: string; + step: string; + branches: string[]; } export interface IPipelineWarningMessageConfig { - id: number | null - organization: string | null - pipelineName: string | null - step: string | null + id: number | null; + organization: string | null; + pipelineName: string | null; + step: string | null; } export interface ICycleTimeSetting { - name: string - value: string + name: string; + value: string; } export interface IJiraColumnsWithValue { - name: string - statuses: string[] + name: string; + statuses: string[]; } export interface savedMetricsSettingState { - jiraColumns: { key: string; value: { name: string; statuses: string[] } }[] - targetFields: { name: string; key: string; flag: boolean }[] - users: string[] - pipelineCrews: string[] - doneColumn: string[] - cycleTimeSettings: ICycleTimeSetting[] - deploymentFrequencySettings: IPipelineConfig[] - leadTimeForChanges: IPipelineConfig[] - treatFlagCardAsBlock: boolean - assigneeFilter: string + jiraColumns: { key: string; value: { name: string; statuses: string[] } }[]; + targetFields: { name: string; key: string; flag: boolean }[]; + users: string[]; + pipelineCrews: string[]; + doneColumn: string[]; + cycleTimeSettings: ICycleTimeSetting[]; + deploymentFrequencySettings: IPipelineConfig[]; + leadTimeForChanges: IPipelineConfig[]; + treatFlagCardAsBlock: boolean; + assigneeFilter: string; importedData: { - importedCrews: string[] - importedAssigneeFilter: string - importedPipelineCrews: string[] + importedCrews: string[]; + importedAssigneeFilter: string; + importedPipelineCrews: string[]; importedCycleTime: { - importedCycleTimeSettings: { [key: string]: string }[] - importedTreatFlagCardAsBlock: boolean - } - importedDoneStatus: string[] - importedClassification: string[] - importedDeployment: IPipelineConfig[] - } - cycleTimeWarningMessage: string | null - classificationWarningMessage: string | null - realDoneWarningMessage: string | null - deploymentWarningMessage: IPipelineWarningMessageConfig[] + importedCycleTimeSettings: { [key: string]: string }[]; + importedTreatFlagCardAsBlock: boolean; + }; + importedDoneStatus: string[]; + importedClassification: string[]; + importedDeployment: IPipelineConfig[]; + }; + cycleTimeWarningMessage: string | null; + classificationWarningMessage: string | null; + realDoneWarningMessage: string | null; + deploymentWarningMessage: IPipelineWarningMessageConfig[]; } const initialState: savedMetricsSettingState = { @@ -86,49 +86,49 @@ const initialState: savedMetricsSettingState = { classificationWarningMessage: null, realDoneWarningMessage: null, deploymentWarningMessage: [], -} +}; const compareArrays = (arrayA: string[], arrayB: string[]): string | null => { if (arrayA?.length > arrayB?.length) { - const differentValues = arrayA?.filter((value) => !arrayB.includes(value)) - return `The column of ${differentValues} is a deleted column, which means this column existed the time you saved config, but was deleted. Please confirm!` + const differentValues = arrayA?.filter((value) => !arrayB.includes(value)); + return `The column of ${differentValues} is a deleted column, which means this column existed the time you saved config, but was deleted. Please confirm!`; } else { - const differentValues = arrayB?.filter((value) => !arrayA.includes(value)) + const differentValues = arrayB?.filter((value) => !arrayA.includes(value)); return differentValues?.length > 0 ? `The column of ${differentValues} is a new column. Please select a value for it!` - : null + : null; } -} +}; const findDifferentValues = (arrayA: string[], arrayB: string[]): string[] | null => { - const diffInArrayA = arrayA?.filter((value) => !arrayB.includes(value)) + const diffInArrayA = arrayA?.filter((value) => !arrayB.includes(value)); if (diffInArrayA?.length === 0) { - return null + return null; } else { - return diffInArrayA + return diffInArrayA; } -} +}; const findKeyByValues = (arrayA: { [key: string]: string }[], arrayB: string[]): string | null => { - const matchingKeys: string[] = [] + const matchingKeys: string[] = []; for (const setting of arrayA) { - const key = Object.keys(setting)[0] - const value = setting[key] + const key = Object.keys(setting)[0]; + const value = setting[key]; if (arrayB.includes(value)) { - matchingKeys.push(key) + matchingKeys.push(key); } } - return `The value of ${matchingKeys} in imported json is not in dropdown list now. Please select a value for it!` -} + return `The value of ${matchingKeys} in imported json is not in dropdown list now. Please select a value for it!`; +}; const setSelectUsers = (users: string[], importedCrews: string[]) => - users.filter((item: string) => importedCrews?.includes(item)) + users.filter((item: string) => importedCrews?.includes(item)); const setPipelineCrews = (pipelineCrews: string[], importedPipelineCrews: string[]) => { if (_.isEmpty(pipelineCrews)) { - return [] + return []; } - return pipelineCrews.filter((item: string) => importedPipelineCrews?.includes(item)) -} + return pipelineCrews.filter((item: string) => importedPipelineCrews?.includes(item)); +}; const setSelectTargetFields = ( targetFields: { name: string; key: string; flag: boolean }[], @@ -137,22 +137,22 @@ const setSelectTargetFields = ( targetFields.map((item: { name: string; key: string; flag: boolean }) => ({ ...item, flag: importedClassification?.includes(item.key), - })) + })); const setCycleTimeSettings = ( jiraColumns: { key: string; value: { name: string; statuses: string[] } }[], importedCycleTimeSettings: { [key: string]: string }[] ) => { return jiraColumns?.map((item: { key: string; value: { name: string; statuses: string[] } }) => { - const controlName = item.value.name - let defaultOptionValue = METRICS_CONSTANTS.cycleTimeEmptyStr - const validImportValue = importedCycleTimeSettings?.find((i) => Object.keys(i)[0] === controlName) + const controlName = item.value.name; + let defaultOptionValue = METRICS_CONSTANTS.cycleTimeEmptyStr; + const validImportValue = importedCycleTimeSettings?.find((i) => Object.keys(i)[0] === controlName); if (validImportValue && CYCLE_TIME_LIST.includes(Object.values(validImportValue)[0])) { - defaultOptionValue = Object.values(validImportValue)[0] + defaultOptionValue = Object.values(validImportValue)[0]; } - return { name: controlName, value: defaultOptionValue } - }) -} + return { name: controlName, value: defaultOptionValue }; + }); +}; const setSelectDoneColumns = ( jiraColumns: { key: string; value: { name: string; statuses: string[] } }[], @@ -160,49 +160,49 @@ const setSelectDoneColumns = ( importedDoneStatus: string[] ) => { const doneStatus = - jiraColumns?.find((item) => item.key === METRICS_CONSTANTS.doneKeyFromBackend)?.value.statuses ?? [] + jiraColumns?.find((item) => item.key === METRICS_CONSTANTS.doneKeyFromBackend)?.value.statuses ?? []; const selectedDoneColumns = cycleTimeSettings ?.filter(({ value }) => value === METRICS_CONSTANTS.doneValue) - .map(({ name }) => name) + .map(({ name }) => name); const filteredStatus = jiraColumns ?.filter(({ value }) => selectedDoneColumns.includes(value.name)) - .flatMap(({ value }) => value.statuses) - const status = selectedDoneColumns?.length < 1 ? doneStatus : filteredStatus - return status.filter((item: string) => importedDoneStatus?.includes(item)) -} + .flatMap(({ value }) => value.statuses); + const status = selectedDoneColumns?.length < 1 ? doneStatus : filteredStatus; + return status.filter((item: string) => importedDoneStatus?.includes(item)); +}; export const metricsSlice = createSlice({ name: 'metrics', initialState, reducers: { saveTargetFields: (state, action) => { - state.targetFields = action.payload + state.targetFields = action.payload; }, saveDoneColumn: (state, action) => { - state.doneColumn = action.payload + state.doneColumn = action.payload; }, saveUsers: (state, action) => { - state.users = action.payload + state.users = action.payload; }, savePipelineCrews: (state, action) => { - state.pipelineCrews = action.payload + state.pipelineCrews = action.payload; }, saveCycleTimeSettings: (state, action) => { - state.cycleTimeSettings = action.payload + state.cycleTimeSettings = action.payload; }, addADeploymentFrequencySetting: (state) => { const newId = state.deploymentFrequencySettings.length >= 1 ? state.deploymentFrequencySettings[state.deploymentFrequencySettings.length - 1].id + 1 - : 0 + : 0; state.deploymentFrequencySettings = [ ...state.deploymentFrequencySettings, { id: newId, organization: '', pipelineName: '', step: '', branches: [] }, - ] + ]; }, updateDeploymentFrequencySettings: (state, action) => { - const { updateId, label, value } = action.payload + const { updateId, label, value } = action.payload; state.deploymentFrequencySettings = state.deploymentFrequencySettings.map((deploymentFrequencySetting) => { return deploymentFrequencySetting.id === updateId @@ -210,107 +210,112 @@ export const metricsSlice = createSlice({ ...deploymentFrequencySetting, [label === 'Steps' ? 'step' : camelCase(label)]: value, } - : deploymentFrequencySetting - }) + : deploymentFrequencySetting; + }); }, updateMetricsImportedData: (state, action) => { const { crews, cycleTime, doneStatus, classification, deployment, leadTime, assigneeFilter, pipelineCrews } = - action.payload - state.importedData.importedCrews = crews || state.importedData.importedCrews - state.importedData.importedPipelineCrews = pipelineCrews || state.importedData.importedPipelineCrews + action.payload; + state.importedData.importedCrews = crews || state.importedData.importedCrews; + state.importedData.importedPipelineCrews = pipelineCrews || state.importedData.importedPipelineCrews; state.importedData.importedCycleTime.importedCycleTimeSettings = - cycleTime?.jiraColumns || state.importedData.importedCycleTime.importedCycleTimeSettings + cycleTime?.jiraColumns || state.importedData.importedCycleTime.importedCycleTimeSettings; state.importedData.importedCycleTime.importedTreatFlagCardAsBlock = - cycleTime?.treatFlagCardAsBlock && state.importedData.importedCycleTime.importedTreatFlagCardAsBlock - state.importedData.importedAssigneeFilter = assigneeFilter || state.importedData.importedAssigneeFilter - state.importedData.importedDoneStatus = doneStatus || state.importedData.importedDoneStatus - state.importedData.importedClassification = classification || state.importedData.importedClassification - state.importedData.importedDeployment = deployment || leadTime || state.importedData.importedDeployment + cycleTime?.treatFlagCardAsBlock && state.importedData.importedCycleTime.importedTreatFlagCardAsBlock; + state.importedData.importedAssigneeFilter = assigneeFilter || state.importedData.importedAssigneeFilter; + state.importedData.importedDoneStatus = doneStatus || state.importedData.importedDoneStatus; + state.importedData.importedClassification = classification || state.importedData.importedClassification; + state.importedData.importedDeployment = deployment || leadTime || state.importedData.importedDeployment; }, updateMetricsState: (state, action) => { - const { targetFields, users, jiraColumns, isProjectCreated, ignoredTargetFields } = action.payload + const { targetFields, users, jiraColumns, isProjectCreated, ignoredTargetFields } = action.payload; const { importedCrews, importedClassification, importedCycleTime, importedDoneStatus, importedAssigneeFilter } = - state.importedData - state.users = isProjectCreated ? users : setSelectUsers(users, importedCrews) - state.targetFields = isProjectCreated ? targetFields : setSelectTargetFields(targetFields, importedClassification) + state.importedData; + state.users = isProjectCreated ? users : setSelectUsers(users, importedCrews); + state.targetFields = isProjectCreated + ? targetFields + : setSelectTargetFields(targetFields, importedClassification); if (!isProjectCreated && importedCycleTime?.importedCycleTimeSettings?.length > 0) { const importedCycleTimeSettingsKeys = importedCycleTime.importedCycleTimeSettings.flatMap((obj) => Object.keys(obj) - ) + ); const importedCycleTimeSettingsValues = importedCycleTime.importedCycleTimeSettings.flatMap((obj) => Object.values(obj) - ) + ); const jiraColumnsNames = jiraColumns?.map( (obj: { key: string; value: { name: string; statuses: string[] } }) => obj.value.name - ) - const metricsContainsValues = Object.values(METRICS_CONSTANTS) - const importedKeyMismatchWarning = compareArrays(importedCycleTimeSettingsKeys, jiraColumnsNames) - const importedValueMismatchWarning = findDifferentValues(importedCycleTimeSettingsValues, metricsContainsValues) + ); + const metricsContainsValues = Object.values(METRICS_CONSTANTS); + const importedKeyMismatchWarning = compareArrays(importedCycleTimeSettingsKeys, jiraColumnsNames); + const importedValueMismatchWarning = findDifferentValues( + importedCycleTimeSettingsValues, + metricsContainsValues + ); const getWarningMessage = (): string | null => { if (importedKeyMismatchWarning?.length) { - return compareArrays(importedCycleTimeSettingsKeys, jiraColumnsNames) + return compareArrays(importedCycleTimeSettingsKeys, jiraColumnsNames); } if (importedValueMismatchWarning?.length) { - return findKeyByValues(importedCycleTime.importedCycleTimeSettings, importedValueMismatchWarning) + return findKeyByValues(importedCycleTime.importedCycleTimeSettings, importedValueMismatchWarning); } - return null - } - state.cycleTimeWarningMessage = getWarningMessage() + return null; + }; + state.cycleTimeWarningMessage = getWarningMessage(); } else { - state.cycleTimeWarningMessage = null + state.cycleTimeWarningMessage = null; } if (!isProjectCreated && importedClassification?.length > 0) { - const keyArray = targetFields?.map((field: { key: string; name: string; flag: boolean }) => field.key) + const keyArray = targetFields?.map((field: { key: string; name: string; flag: boolean }) => field.key); const ignoredKeyArray = ignoredTargetFields?.map( (field: { key: string; name: string; flag: boolean }) => field.key - ) - const filteredImportedClassification = importedClassification.filter((item) => !ignoredKeyArray.includes(item)) + ); + const filteredImportedClassification = importedClassification.filter((item) => !ignoredKeyArray.includes(item)); if (filteredImportedClassification.every((item) => keyArray.includes(item))) { - state.classificationWarningMessage = null + state.classificationWarningMessage = null; } else { - state.classificationWarningMessage = MESSAGE.CLASSIFICATION_WARNING + state.classificationWarningMessage = MESSAGE.CLASSIFICATION_WARNING; } } else { - state.classificationWarningMessage = null + state.classificationWarningMessage = null; } - state.cycleTimeSettings = setCycleTimeSettings(jiraColumns, importedCycleTime.importedCycleTimeSettings) + state.cycleTimeSettings = setCycleTimeSettings(jiraColumns, importedCycleTime.importedCycleTimeSettings); if (!isProjectCreated && !!importedDoneStatus.length) { setSelectDoneColumns(jiraColumns, state.cycleTimeSettings, importedDoneStatus).length < importedDoneStatus.length ? (state.realDoneWarningMessage = MESSAGE.REAL_DONE_WARNING) - : (state.realDoneWarningMessage = null) + : (state.realDoneWarningMessage = null); } state.doneColumn = isProjectCreated ? [] - : setSelectDoneColumns(jiraColumns, state.cycleTimeSettings, importedDoneStatus) + : setSelectDoneColumns(jiraColumns, state.cycleTimeSettings, importedDoneStatus); state.assigneeFilter = importedAssigneeFilter === ASSIGNEE_FILTER_TYPES.LAST_ASSIGNEE || importedAssigneeFilter === ASSIGNEE_FILTER_TYPES.HISTORICAL_ASSIGNEE ? importedAssigneeFilter - : ASSIGNEE_FILTER_TYPES.LAST_ASSIGNEE + : ASSIGNEE_FILTER_TYPES.LAST_ASSIGNEE; state.treatFlagCardAsBlock = typeof importedCycleTime.importedTreatFlagCardAsBlock === 'boolean' ? importedCycleTime.importedTreatFlagCardAsBlock - : true + : true; }, updatePipelineSettings: (state, action) => { - const { pipelineList, isProjectCreated, pipelineCrews } = action.payload - const { importedDeployment, importedPipelineCrews } = state.importedData - state.pipelineCrews = isProjectCreated ? pipelineCrews : setPipelineCrews(pipelineCrews, importedPipelineCrews) - const orgNames: Array = _.uniq(pipelineList.map((item: pipeline) => item.orgName)) + const { pipelineList, isProjectCreated, pipelineCrews } = action.payload; + const { importedDeployment, importedPipelineCrews } = state.importedData; + state.pipelineCrews = isProjectCreated ? pipelineCrews : setPipelineCrews(pipelineCrews, importedPipelineCrews); + const orgNames: Array = _.uniq(pipelineList.map((item: pipeline) => item.orgName)); const filteredPipelineNames = (organization: string) => pipelineList .filter((pipeline: pipeline) => pipeline.orgName.toLowerCase() === organization.toLowerCase()) - .map((item: pipeline) => item.name) + .map((item: pipeline) => item.name); const getValidPipelines = (pipelines: IPipelineConfig[]) => !pipelines.length || isProjectCreated ? [{ id: 0, organization: '', pipelineName: '', step: '', branches: [] }] @@ -320,48 +325,48 @@ export const metricsSlice = createSlice({ pipelineName: filteredPipelineNames(organization).includes(pipelineName) ? pipelineName : '', step: '', branches: [], - })) + })); const createPipelineWarning = ({ id, organization, pipelineName }: IPipelineConfig) => { const orgWarning = orgNames.some((i) => (i as string).toLowerCase() === organization.toLowerCase()) ? null - : MESSAGE.ORGANIZATION_WARNING + : MESSAGE.ORGANIZATION_WARNING; const pipelineNameWarning = orgWarning || filteredPipelineNames(organization).includes(pipelineName) ? null - : MESSAGE.PIPELINE_NAME_WARNING + : MESSAGE.PIPELINE_NAME_WARNING; return { id, organization: orgWarning, pipelineName: pipelineNameWarning, step: null, - } - } + }; + }; const getPipelinesWarningMessage = (pipelines: IPipelineConfig[]) => { if (!pipelines.length || isProjectCreated) { - return [] + return []; } - return pipelines.map((pipeline) => createPipelineWarning(pipeline)) - } + return pipelines.map((pipeline) => createPipelineWarning(pipeline)); + }; - state.deploymentFrequencySettings = getValidPipelines(importedDeployment) - state.deploymentWarningMessage = getPipelinesWarningMessage(importedDeployment) + state.deploymentFrequencySettings = getValidPipelines(importedDeployment); + state.deploymentWarningMessage = getPipelinesWarningMessage(importedDeployment); }, updatePipelineStep: (state, action) => { - const { steps, id, branches, pipelineCrews } = action.payload - const { importedDeployment, importedPipelineCrews } = state.importedData - const updatedImportedPipeline = importedDeployment - const updatedImportedPipelineStep = updatedImportedPipeline.find((pipeline) => pipeline.id === id)?.step ?? '' + const { steps, id, branches, pipelineCrews } = action.payload; + const { importedDeployment, importedPipelineCrews } = state.importedData; + const updatedImportedPipeline = importedDeployment; + const updatedImportedPipelineStep = updatedImportedPipeline.find((pipeline) => pipeline.id === id)?.step ?? ''; const updatedImportedPipelineBranches = - updatedImportedPipeline.find((pipeline) => pipeline.id === id)?.branches ?? [] - const validStep = steps.includes(updatedImportedPipelineStep) ? updatedImportedPipelineStep : '' - const validBranches = _.filter(branches, (branch) => updatedImportedPipelineBranches.includes(branch)) - const validPipelineCrews = _.filter(pipelineCrews, (crew) => importedPipelineCrews.includes(crew)) - state.pipelineCrews = validPipelineCrews - const stepWarningMessage = steps.includes(updatedImportedPipelineStep) ? null : MESSAGE.STEP_WARNING + updatedImportedPipeline.find((pipeline) => pipeline.id === id)?.branches ?? []; + const validStep = steps.includes(updatedImportedPipelineStep) ? updatedImportedPipelineStep : ''; + const validBranches = _.filter(branches, (branch) => updatedImportedPipelineBranches.includes(branch)); + const validPipelineCrews = _.filter(pipelineCrews, (crew) => importedPipelineCrews.includes(crew)); + state.pipelineCrews = validPipelineCrews; + const stepWarningMessage = steps.includes(updatedImportedPipelineStep) ? null : MESSAGE.STEP_WARNING; const getPipelineSettings = (pipelines: IPipelineConfig[]) => pipelines.map((pipeline) => @@ -372,7 +377,7 @@ export const metricsSlice = createSlice({ branches: validBranches, } : pipeline - ) + ); const getStepWarningMessage = (pipelines: IPipelineWarningMessageConfig[]) => { return pipelines.map((pipeline) => @@ -382,31 +387,31 @@ export const metricsSlice = createSlice({ step: stepWarningMessage, } : pipeline - ) - } + ); + }; - state.deploymentFrequencySettings = getPipelineSettings(state.deploymentFrequencySettings) - state.deploymentWarningMessage = getStepWarningMessage(state.deploymentWarningMessage) + state.deploymentFrequencySettings = getPipelineSettings(state.deploymentFrequencySettings); + state.deploymentWarningMessage = getStepWarningMessage(state.deploymentWarningMessage); }, deleteADeploymentFrequencySetting: (state, action) => { - const deleteId = action.payload - state.deploymentFrequencySettings = [...state.deploymentFrequencySettings.filter(({ id }) => id !== deleteId)] + const deleteId = action.payload; + state.deploymentFrequencySettings = [...state.deploymentFrequencySettings.filter(({ id }) => id !== deleteId)]; }, initDeploymentFrequencySettings: (state) => { - state.deploymentFrequencySettings = initialState.deploymentFrequencySettings + state.deploymentFrequencySettings = initialState.deploymentFrequencySettings; }, updateTreatFlagCardAsBlock: (state, action) => { - state.treatFlagCardAsBlock = action.payload + state.treatFlagCardAsBlock = action.payload; }, updateAssigneeFilter: (state, action) => { - state.assigneeFilter = action.payload + state.assigneeFilter = action.payload; }, }, -}) +}); export const { saveTargetFields, @@ -424,35 +429,34 @@ export const { updateMetricsState, updatePipelineSettings, updatePipelineStep, -} = metricsSlice.actions - -export const selectDeploymentFrequencySettings = (state: RootState) => state.metrics.deploymentFrequencySettings -export const selectLeadTimeForChanges = (state: RootState) => state.metrics.leadTimeForChanges - -export const selectCycleTimeSettings = (state: RootState) => state.metrics.cycleTimeSettings -export const selectMetricsContent = (state: RootState) => state.metrics -export const selectTreatFlagCardAsBlock = (state: RootState) => state.metrics.treatFlagCardAsBlock -export const selectAssigneeFilter = (state: RootState) => state.metrics.assigneeFilter -export const selectCycleTimeWarningMessage = (state: RootState) => state.metrics.cycleTimeWarningMessage -export const selectClassificationWarningMessage = (state: RootState) => state.metrics.classificationWarningMessage -export const selectRealDoneWarningMessage = (state: RootState) => state.metrics.realDoneWarningMessage - -export const selectOrganizationWarningMessage = (state: RootState, id: number, type: string) => { - const { deploymentWarningMessage } = state.metrics - const warningMessage = deploymentWarningMessage - return warningMessage.find((item) => item.id === id)?.organization -} - -export const selectPipelineNameWarningMessage = (state: RootState, id: number, type: string) => { - const { deploymentWarningMessage } = state.metrics - const warningMessage = deploymentWarningMessage - return warningMessage.find((item) => item.id === id)?.pipelineName -} - -export const selectStepWarningMessage = (state: RootState, id: number, type: string) => { - const { deploymentWarningMessage } = state.metrics - const warningMessage = deploymentWarningMessage - return warningMessage.find((item) => item.id === id)?.step -} - -export default metricsSlice.reducer +} = metricsSlice.actions; + +export const selectDeploymentFrequencySettings = (state: RootState) => state.metrics.deploymentFrequencySettings; + +export const selectCycleTimeSettings = (state: RootState) => state.metrics.cycleTimeSettings; +export const selectMetricsContent = (state: RootState) => state.metrics; +export const selectTreatFlagCardAsBlock = (state: RootState) => state.metrics.treatFlagCardAsBlock; +export const selectAssigneeFilter = (state: RootState) => state.metrics.assigneeFilter; +export const selectCycleTimeWarningMessage = (state: RootState) => state.metrics.cycleTimeWarningMessage; +export const selectClassificationWarningMessage = (state: RootState) => state.metrics.classificationWarningMessage; +export const selectRealDoneWarningMessage = (state: RootState) => state.metrics.realDoneWarningMessage; + +export const selectOrganizationWarningMessage = (state: RootState, id: number) => { + const { deploymentWarningMessage } = state.metrics; + const warningMessage = deploymentWarningMessage; + return warningMessage.find((item) => item.id === id)?.organization; +}; + +export const selectPipelineNameWarningMessage = (state: RootState, id: number) => { + const { deploymentWarningMessage } = state.metrics; + const warningMessage = deploymentWarningMessage; + return warningMessage.find((item) => item.id === id)?.pipelineName; +}; + +export const selectStepWarningMessage = (state: RootState, id: number) => { + const { deploymentWarningMessage } = state.metrics; + const warningMessage = deploymentWarningMessage; + return warningMessage.find((item) => item.id === id)?.step; +}; + +export default metricsSlice.reducer; diff --git a/frontend/src/context/config/board/boardSlice.ts b/frontend/src/context/config/board/boardSlice.ts index 5e7896b022..81ff80e4ef 100644 --- a/frontend/src/context/config/board/boardSlice.ts +++ b/frontend/src/context/config/board/boardSlice.ts @@ -1,26 +1,26 @@ -import { BOARD_TYPES } from '@src/constants/resources' +import { BOARD_TYPES } from '@src/constants/resources'; export interface IBoardVerifyResponseState { - jiraColumns: { key: string; value: { name: string; statuses: string[] } }[] - targetFields: { name: string; key: string; flag: boolean }[] - users: string[] + jiraColumns: { key: string; value: { name: string; statuses: string[] } }[]; + targetFields: { name: string; key: string; flag: boolean }[]; + users: string[]; } export interface IBoardState { - config: { type: string; boardId: string; email: string; projectKey: string; site: string; token: string } - isVerified: boolean - isShow: boolean - verifiedResponse: IBoardVerifyResponseState + config: { type: string; boardId: string; email: string; projectKey: string; site: string; token: string }; + isVerified: boolean; + isShow: boolean; + verifiedResponse: IBoardVerifyResponseState; } export const initialVerifiedBoardState: IBoardVerifyResponseState = { jiraColumns: [], targetFields: [], users: [], -} +}; export const initialBoardState: IBoardState = { config: { type: BOARD_TYPES.JIRA, boardId: '', email: '', projectKey: '', site: '', token: '' }, isVerified: false, isShow: false, verifiedResponse: initialVerifiedBoardState, -} +}; diff --git a/frontend/src/context/config/configSlice.ts b/frontend/src/context/config/configSlice.ts index 256f747c47..33507fc629 100644 --- a/frontend/src/context/config/configSlice.ts +++ b/frontend/src/context/config/configSlice.ts @@ -1,29 +1,29 @@ -import { createSlice } from '@reduxjs/toolkit' -import type { RootState } from '@src/store' -import { CALENDAR, MESSAGE } from '@src/constants/resources' -import { REQUIRED_DATA } from '@src/constants/resources' -import { IBoardState, initialBoardState } from '@src/context/config/board/boardSlice' -import { initialPipelineToolState, IPipelineToolState } from '@src/context/config/pipelineTool/pipelineToolSlice' -import { initialSourceControlState, ISourceControl } from '@src/context/config/sourceControl/sourceControlSlice' -import dayjs from 'dayjs' -import { pipeline } from '@src/context/config/pipelineTool/verifyResponseSlice' -import _ from 'lodash' +import { createSlice } from '@reduxjs/toolkit'; +import type { RootState } from '@src/store'; +import { BOARD_METRICS, CALENDAR, DORA_METRICS, MESSAGE } from '@src/constants/resources'; +import { REQUIRED_DATA } from '@src/constants/resources'; +import { IBoardState, initialBoardState } from '@src/context/config/board/boardSlice'; +import { initialPipelineToolState, IPipelineToolState } from '@src/context/config/pipelineTool/pipelineToolSlice'; +import { initialSourceControlState, ISourceControl } from '@src/context/config/sourceControl/sourceControlSlice'; +import dayjs from 'dayjs'; +import { pipeline } from '@src/context/config/pipelineTool/verifyResponseSlice'; +import _ from 'lodash'; export interface BasicConfigState { - isProjectCreated: boolean + isProjectCreated: boolean; basic: { - projectName: string - calendarType: string + projectName: string; + calendarType: string; dateRange: { - startDate: string | null - endDate: string | null - } - metrics: string[] - } - board: IBoardState - pipelineTool: IPipelineToolState - sourceControl: ISourceControl - warningMessage: string | null + startDate: string | null; + endDate: string | null; + }; + metrics: string[]; + }; + board: IBoardState; + pipelineTool: IPipelineToolState; + sourceControl: ISourceControl; + warningMessage: string | null; } export const initialBasicConfigState: BasicConfigState = { @@ -41,7 +41,7 @@ export const initialBasicConfigState: BasicConfigState = { pipelineTool: initialPipelineToolState, sourceControl: initialSourceControlState, warningMessage: null, -} +}; export const configSlice = createSlice({ name: 'config', @@ -53,14 +53,14 @@ export const configSlice = createSlice({ }, reducers: { updateProjectName: (state, action) => { - state.basic.projectName = action.payload + state.basic.projectName = action.payload; }, updateCalendarType: (state, action) => { - state.basic.calendarType = action.payload + state.basic.calendarType = action.payload; }, updateDateRange: (state, action) => { - const { startDate, endDate } = action.payload - state.basic.dateRange = { startDate, endDate } + const { startDate, endDate } = action.payload; + state.basic.dateRange = { startDate, endDate }; }, updateMetrics: (state, action) => { const { @@ -71,65 +71,67 @@ export const configSlice = createSlice({ DEPLOYMENT_FREQUENCY, CHANGE_FAILURE_RATE, MEAN_TIME_TO_RECOVERY, - } = REQUIRED_DATA + } = REQUIRED_DATA; - state.basic.metrics = action.payload + state.basic.metrics = action.payload; - state.board.isShow = [VELOCITY, CYCLE_TIME, CLASSIFICATION].some((metric) => state.basic.metrics.includes(metric)) + state.board.isShow = [VELOCITY, CYCLE_TIME, CLASSIFICATION].some((metric) => + state.basic.metrics.includes(metric) + ); state.pipelineTool.isShow = [ LEAD_TIME_FOR_CHANGES, DEPLOYMENT_FREQUENCY, CHANGE_FAILURE_RATE, MEAN_TIME_TO_RECOVERY, - ].some((metric) => state.basic.metrics.includes(metric)) - state.sourceControl.isShow = [LEAD_TIME_FOR_CHANGES].some((metric) => state.basic.metrics.includes(metric)) - state.basic.metrics = action.payload + ].some((metric) => state.basic.metrics.includes(metric)); + state.sourceControl.isShow = [LEAD_TIME_FOR_CHANGES].some((metric) => state.basic.metrics.includes(metric)); + state.basic.metrics = action.payload; }, updateBasicConfigState: (state, action) => { - state.basic = action.payload - const { projectName, dateRange, metrics } = state.basic + state.basic = action.payload; + const { projectName, dateRange, metrics } = state.basic; if (!state.isProjectCreated) { state.warningMessage = projectName && dateRange.startDate && dateRange.endDate && metrics.length > 0 ? null - : MESSAGE.CONFIG_PAGE_VERIFY_IMPORT_ERROR + : MESSAGE.CONFIG_PAGE_VERIFY_IMPORT_ERROR; } - state.board.config = action.payload.board || state.board.config - state.pipelineTool.config = action.payload.pipelineTool || state.pipelineTool.config - state.sourceControl.config = action.payload.sourceControl || state.sourceControl.config + state.board.config = action.payload.board || state.board.config; + state.pipelineTool.config = action.payload.pipelineTool || state.pipelineTool.config; + state.sourceControl.config = action.payload.sourceControl || state.sourceControl.config; }, updateProjectCreatedState: (state, action) => { - state.isProjectCreated = action.payload + state.isProjectCreated = action.payload; }, updateBoardVerifyState: (state, action) => { - state.board.isVerified = action.payload + state.board.isVerified = action.payload; }, updateBoard: (state, action) => { - state.board.config = action.payload + state.board.config = action.payload; }, updateJiraVerifyResponse: (state, action) => { - const { jiraColumns, targetFields, users } = action.payload - state.board.verifiedResponse.jiraColumns = jiraColumns - state.board.verifiedResponse.targetFields = targetFields - state.board.verifiedResponse.users = users + const { jiraColumns, targetFields, users } = action.payload; + state.board.verifiedResponse.jiraColumns = jiraColumns; + state.board.verifiedResponse.targetFields = targetFields; + state.board.verifiedResponse.users = users; }, updatePipelineToolVerifyState: (state, action) => { - state.pipelineTool.isVerified = action.payload + state.pipelineTool.isVerified = action.payload; }, updatePipelineTool: (state, action) => { - state.pipelineTool.config = action.payload + state.pipelineTool.config = action.payload; }, updatePipelineToolVerifyResponse: (state, action) => { - const { pipelineList } = action.payload + const { pipelineList } = action.payload; state.pipelineTool.verifiedResponse.pipelineList = pipelineList.map((pipeline: pipeline) => ({ ...pipeline, steps: [], - })) + })); }, updatePipelineToolVerifyResponseSteps: (state, action) => { - const { organization, pipelineName, steps, branches, pipelineCrews } = action.payload + const { organization, pipelineName, steps, branches, pipelineCrews } = action.payload; state.pipelineTool.verifiedResponse.pipelineList = state.pipelineTool.verifiedResponse.pipelineList.map( (pipeline) => pipeline.name === pipelineName && pipeline.orgName === organization @@ -139,26 +141,26 @@ export const configSlice = createSlice({ steps: steps, } : pipeline - ) + ); state.pipelineTool.verifiedResponse.pipelineCrews = _.union( state.pipelineTool.verifiedResponse.pipelineCrews, pipelineCrews - ) + ); }, updateSourceControlVerifyState: (state, action) => { - state.sourceControl.isVerified = action.payload + state.sourceControl.isVerified = action.payload; }, updateSourceControl: (state, action) => { - state.sourceControl.config = action.payload + state.sourceControl.config = action.payload; }, updateSourceControlVerifiedResponse: (state, action) => { - const { githubRepos } = action.payload - state.sourceControl.verifiedResponse.repoList = githubRepos + const { githubRepos } = action.payload; + state.sourceControl.verifiedResponse.repoList = githubRepos; }, resetImportedData: () => initialBasicConfigState, }, -}) +}); export const { updateProjectCreatedState, updateProjectName, @@ -177,42 +179,46 @@ export const { updateSourceControlVerifiedResponse, updatePipelineToolVerifyResponseSteps, resetImportedData, -} = configSlice.actions - -export const selectProjectName = (state: RootState) => state.config.basic.projectName -export const selectCalendarType = (state: RootState) => state.config.basic.calendarType -export const selectDateRange = (state: RootState) => state.config.basic.dateRange -export const selectMetrics = (state: RootState) => state.config.basic.metrics -export const selectBoard = (state: RootState) => state.config.board.config -export const isPipelineToolVerified = (state: RootState) => state.config.pipelineTool.isVerified -export const selectPipelineTool = (state: RootState) => state.config.pipelineTool.config -export const isSourceControlVerified = (state: RootState) => state.config.sourceControl.isVerified -export const selectSourceControl = (state: RootState) => state.config.sourceControl.config -export const selectWarningMessage = (state: RootState) => state.config.warningMessage - -export const selectConfig = (state: RootState) => state.config - -export const selectIsBoardVerified = (state: RootState) => state.config.board.isVerified -export const selectUsers = (state: RootState) => state.config.board.verifiedResponse.users -export const selectJiraColumns = (state: RootState) => state.config.board.verifiedResponse.jiraColumns -export const selectIsProjectCreated = (state: RootState) => state.config.isProjectCreated +} = configSlice.actions; + +export const selectProjectName = (state: RootState) => state.config.basic.projectName; +export const selectCalendarType = (state: RootState) => state.config.basic.calendarType; +export const selectDateRange = (state: RootState) => state.config.basic.dateRange; +export const selectMetrics = (state: RootState) => state.config.basic.metrics; +export const isSelectBoardMetrics = (state: RootState) => + state.config.basic.metrics.some((metric) => BOARD_METRICS.includes(metric)); +export const isSelectDoraMetrics = (state: RootState) => + state.config.basic.metrics.some((metric) => DORA_METRICS.includes(metric)); +export const selectBoard = (state: RootState) => state.config.board.config; +export const isPipelineToolVerified = (state: RootState) => state.config.pipelineTool.isVerified; +export const selectPipelineTool = (state: RootState) => state.config.pipelineTool.config; +export const isSourceControlVerified = (state: RootState) => state.config.sourceControl.isVerified; +export const selectSourceControl = (state: RootState) => state.config.sourceControl.config; +export const selectWarningMessage = (state: RootState) => state.config.warningMessage; + +export const selectConfig = (state: RootState) => state.config; + +export const selectIsBoardVerified = (state: RootState) => state.config.board.isVerified; +export const selectUsers = (state: RootState) => state.config.board.verifiedResponse.users; +export const selectJiraColumns = (state: RootState) => state.config.board.verifiedResponse.jiraColumns; +export const selectIsProjectCreated = (state: RootState) => state.config.isProjectCreated; export const selectPipelineOrganizations = (state: RootState) => [ ...new Set(state.config.pipelineTool.verifiedResponse.pipelineList.map((item) => item.orgName)), -] +]; export const selectPipelineNames = (state: RootState, organization: string) => state.config.pipelineTool.verifiedResponse.pipelineList .filter((pipeline) => pipeline.orgName === organization) .map((item) => item.name) - .sort((a, b) => a.localeCompare(b, undefined, { sensitivity: 'base' })) + .sort((a, b) => a.localeCompare(b, undefined, { sensitivity: 'base' })); export const selectStepsParams = (state: RootState, organizationName: string, pipelineName: string) => { const pipeline = state.config.pipelineTool.verifiedResponse.pipelineList.find( (pipeline) => pipeline.name === pipelineName && pipeline.orgName === organizationName - ) - const { startDate, endDate } = state.config.basic.dateRange - const pipelineType = state.config.pipelineTool.config.type - const token = state.config.pipelineTool.config.token + ); + const { startDate, endDate } = state.config.basic.dateRange; + const pipelineType = state.config.pipelineTool.config.type; + const token = state.config.pipelineTool.config.token; return { params: { @@ -226,20 +232,20 @@ export const selectStepsParams = (state: RootState, organizationName: string, pi organizationId: pipeline?.orgId ?? '', pipelineType, token, - } -} + }; +}; export const selectSteps = (state: RootState, organizationName: string, pipelineName: string) => state.config.pipelineTool.verifiedResponse.pipelineList.find( (pipeline) => pipeline.name === pipelineName && pipeline.orgName === organizationName - )?.steps ?? [] + )?.steps ?? []; export const selectBranches = (state: RootState, organizationName: string, pipelineName: string) => state.config.pipelineTool.verifiedResponse.pipelineList.find( /* istanbul ignore next */ (pipeline) => pipeline.name === pipelineName && pipeline.orgName === organizationName - )?.branches ?? [] + )?.branches ?? []; -export const selectPipelineCrews = (state: RootState) => state.config.pipelineTool.verifiedResponse.pipelineCrews +export const selectPipelineCrews = (state: RootState) => state.config.pipelineTool.verifiedResponse.pipelineCrews; -export default configSlice.reducer +export default configSlice.reducer; diff --git a/frontend/src/context/config/pipelineTool/pipelineToolSlice.ts b/frontend/src/context/config/pipelineTool/pipelineToolSlice.ts index 7185e7337f..0ccfd27a73 100644 --- a/frontend/src/context/config/pipelineTool/pipelineToolSlice.ts +++ b/frontend/src/context/config/pipelineTool/pipelineToolSlice.ts @@ -1,11 +1,11 @@ -import { PIPELINE_TOOL_TYPES } from '@src/constants/resources' -import { initialPipelineToolVerifiedResponseState, IPipelineToolVerifyResponse } from './verifyResponseSlice' +import { PIPELINE_TOOL_TYPES } from '@src/constants/resources'; +import { initialPipelineToolVerifiedResponseState, IPipelineToolVerifyResponse } from './verifyResponseSlice'; export interface IPipelineToolState { - config: { type: string; token: string } - isVerified: boolean - isShow: boolean - verifiedResponse: IPipelineToolVerifyResponse + config: { type: string; token: string }; + isVerified: boolean; + isShow: boolean; + verifiedResponse: IPipelineToolVerifyResponse; } export const initialPipelineToolState: IPipelineToolState = { @@ -16,4 +16,4 @@ export const initialPipelineToolState: IPipelineToolState = { isVerified: false, isShow: false, verifiedResponse: initialPipelineToolVerifiedResponseState, -} +}; diff --git a/frontend/src/context/config/pipelineTool/verifyResponseSlice.ts b/frontend/src/context/config/pipelineTool/verifyResponseSlice.ts index 86423b118e..f8ac16642a 100644 --- a/frontend/src/context/config/pipelineTool/verifyResponseSlice.ts +++ b/frontend/src/context/config/pipelineTool/verifyResponseSlice.ts @@ -1,19 +1,19 @@ export interface IPipelineToolVerifyResponse { - pipelineList: pipeline[] - pipelineCrews: string[] + pipelineList: pipeline[]; + pipelineCrews: string[]; } export interface pipeline { - id: string - name: string - orgId: string - orgName: string - repository: string - steps: string[] - branches: string[] + id: string; + name: string; + orgId: string; + orgName: string; + repository: string; + steps: string[]; + branches: string[]; } export const initialPipelineToolVerifiedResponseState: IPipelineToolVerifyResponse = { pipelineList: [], pipelineCrews: [], -} +}; diff --git a/frontend/src/context/config/sourceControl/sourceControlSlice.ts b/frontend/src/context/config/sourceControl/sourceControlSlice.ts index 9f652b5908..da6ddfc817 100644 --- a/frontend/src/context/config/sourceControl/sourceControlSlice.ts +++ b/frontend/src/context/config/sourceControl/sourceControlSlice.ts @@ -1,11 +1,11 @@ -import { SOURCE_CONTROL_TYPES } from '@src/constants/resources' -import { initSourceControlVerifyResponseState, ISourceControlVerifyResponse } from './verifyResponseSlice' +import { SOURCE_CONTROL_TYPES } from '@src/constants/resources'; +import { initSourceControlVerifyResponseState, ISourceControlVerifyResponse } from './verifyResponseSlice'; export interface ISourceControl { - config: { type: string; token: string } - isVerified: boolean - isShow: boolean - verifiedResponse: ISourceControlVerifyResponse + config: { type: string; token: string }; + isVerified: boolean; + isShow: boolean; + verifiedResponse: ISourceControlVerifyResponse; } export const initialSourceControlState: ISourceControl = { @@ -16,4 +16,4 @@ export const initialSourceControlState: ISourceControl = { isVerified: false, isShow: false, verifiedResponse: initSourceControlVerifyResponseState, -} +}; diff --git a/frontend/src/context/config/sourceControl/verifyResponseSlice.ts b/frontend/src/context/config/sourceControl/verifyResponseSlice.ts index 248f710766..d3865be633 100644 --- a/frontend/src/context/config/sourceControl/verifyResponseSlice.ts +++ b/frontend/src/context/config/sourceControl/verifyResponseSlice.ts @@ -1,7 +1,7 @@ export interface ISourceControlVerifyResponse { - repoList: string[] + repoList: string[]; } export const initSourceControlVerifyResponseState: ISourceControlVerifyResponse = { repoList: [], -} +}; diff --git a/frontend/src/context/header/headerSlice.tsx b/frontend/src/context/header/headerSlice.tsx index 6bfecdf662..56de3bdd45 100644 --- a/frontend/src/context/header/headerSlice.tsx +++ b/frontend/src/context/header/headerSlice.tsx @@ -1,26 +1,26 @@ -import { createSlice } from '@reduxjs/toolkit' -import { RootState } from '@src/store' +import { createSlice } from '@reduxjs/toolkit'; +import { RootState } from '@src/store'; export interface headerState { - version: string + version: string; } const initialState: headerState = { version: '', -} +}; export const headerSlice = createSlice({ name: 'header', initialState, reducers: { saveVersion: (state, action) => { - state.version = action.payload + state.version = action.payload; }, }, -}) +}); -export const { saveVersion } = headerSlice.actions +export const { saveVersion } = headerSlice.actions; -export const getVersion = (state: RootState) => state.header.version +export const getVersion = (state: RootState) => state.header.version; -export default headerSlice.reducer +export default headerSlice.reducer; diff --git a/frontend/src/context/interface/index.tsx b/frontend/src/context/interface/index.tsx index 99fa232efb..4c9e832e23 100644 --- a/frontend/src/context/interface/index.tsx +++ b/frontend/src/context/interface/index.tsx @@ -1,7 +1,7 @@ export interface PipelineSetting { - id: number - organization: string - pipelineName: string - step: string - branches: string[] + id: number; + organization: string; + pipelineName: string; + step: string; + branches: string[]; } diff --git a/frontend/src/context/stepper/StepperSlice.tsx b/frontend/src/context/stepper/StepperSlice.tsx index c4f0615035..1d325e0752 100644 --- a/frontend/src/context/stepper/StepperSlice.tsx +++ b/frontend/src/context/stepper/StepperSlice.tsx @@ -1,40 +1,40 @@ -import { createSlice } from '@reduxjs/toolkit' -import type { RootState } from '@src/store' -import { ZERO } from '@src/constants/commons' +import { createSlice } from '@reduxjs/toolkit'; +import type { RootState } from '@src/store'; +import { ZERO } from '@src/constants/commons'; export interface StepState { - stepNumber: number - timeStamp: number + stepNumber: number; + timeStamp: number; } const initialState: StepState = { stepNumber: 0, timeStamp: 0, -} +}; export const stepperSlice = createSlice({ name: 'stepper', initialState, reducers: { resetStep: (state) => { - state.stepNumber = initialState.stepNumber - state.timeStamp = initialState.timeStamp + state.stepNumber = initialState.stepNumber; + state.timeStamp = initialState.timeStamp; }, nextStep: (state) => { - state.stepNumber += 1 + state.stepNumber += 1; }, backStep: (state) => { - state.stepNumber = state.stepNumber === ZERO ? ZERO : state.stepNumber - 1 + state.stepNumber = state.stepNumber === ZERO ? ZERO : state.stepNumber - 1; }, updateTimeStamp: (state, action) => { - state.timeStamp = action.payload + state.timeStamp = action.payload; }, }, -}) +}); -export const { resetStep, nextStep, backStep, updateTimeStamp } = stepperSlice.actions +export const { resetStep, nextStep, backStep, updateTimeStamp } = stepperSlice.actions; -export const selectStepNumber = (state: RootState) => state.stepper.stepNumber -export const selectTimeStamp = (state: RootState) => state.stepper.timeStamp +export const selectStepNumber = (state: RootState) => state.stepper.stepNumber; +export const selectTimeStamp = (state: RootState) => state.stepper.timeStamp; -export default stepperSlice.reducer +export default stepperSlice.reducer; diff --git a/frontend/src/emojis/emoji.ts b/frontend/src/emojis/emoji.ts index 129bff2e98..754ba54e55 100644 --- a/frontend/src/emojis/emoji.ts +++ b/frontend/src/emojis/emoji.ts @@ -1,43 +1,43 @@ -import { transformToCleanedBuildKiteEmoji } from '@src/utils/util' -import buildKiteEmojis from '@src/assets/buildkiteEmojis.json' -import appleEmojis from '@src/assets/appleEmojis.json' +import { transformToCleanedBuildKiteEmoji } from '@src/utils/util'; +import buildKiteEmojis from '@src/assets/buildkiteEmojis.json'; +import appleEmojis from '@src/assets/appleEmojis.json'; export interface OriginBuildKiteEmoji { - name: string - image: string - aliases: string[] + name: string; + image: string; + aliases: string[]; } export interface CleanedBuildKiteEmoji { - image: string - aliases: string[] + image: string; + aliases: string[]; } -const EMOJI_URL_PREFIX = 'https://buildkiteassets.com/emojis/' +const EMOJI_URL_PREFIX = 'https://buildkiteassets.com/emojis/'; -const DEFAULT_EMOJI = 'img-buildkite-64/buildkite.png' +const DEFAULT_EMOJI = 'img-buildkite-64/buildkite.png'; const cleanedEmojis: CleanedBuildKiteEmoji[] = (() => - transformToCleanedBuildKiteEmoji([...buildKiteEmojis, ...appleEmojis]))() + transformToCleanedBuildKiteEmoji([...buildKiteEmojis, ...appleEmojis]))(); const getEmojiNames = (input: string): string[] => { - const regex = /:([\w+-]+):/g - const matches = input.match(regex) || [] - return matches.map((match) => match.replaceAll(':', '')) -} + const regex = /:([\w+-]+):/g; + const matches = input.match(regex) || []; + return matches.map((match) => match.replaceAll(':', '')); +}; export const getEmojiUrls = (pipelineStepName: string): string[] => { - const emojiNames = getEmojiNames(pipelineStepName) + const emojiNames = getEmojiNames(pipelineStepName); return emojiNames.flatMap((name) => { - const emojiImage: string | undefined = cleanedEmojis.find(({ aliases }) => aliases.includes(name))?.image - return emojiImage ? `${EMOJI_URL_PREFIX}${emojiImage}` : `${EMOJI_URL_PREFIX}${DEFAULT_EMOJI}` - }) -} + const emojiImage: string | undefined = cleanedEmojis.find(({ aliases }) => aliases.includes(name))?.image; + return emojiImage ? `${EMOJI_URL_PREFIX}${emojiImage}` : `${EMOJI_URL_PREFIX}${DEFAULT_EMOJI}`; + }); +}; export const removeExtraEmojiName = (pipelineStepName: string): string => { - const emojiNames = getEmojiNames(pipelineStepName) + const emojiNames = getEmojiNames(pipelineStepName); emojiNames.map((name) => { - pipelineStepName = pipelineStepName.replaceAll(name, '') - }) - return pipelineStepName.replaceAll(':', '') -} + pipelineStepName = pipelineStepName.replaceAll(name, ''); + }); + return pipelineStepName.replaceAll(':', ''); +}; diff --git a/frontend/src/emojis/style.tsx b/frontend/src/emojis/style.tsx index 012139ff57..508c6ff225 100644 --- a/frontend/src/emojis/style.tsx +++ b/frontend/src/emojis/style.tsx @@ -1,17 +1,17 @@ -import { styled } from '@mui/material/styles' -import { Avatar, Typography } from '@mui/material' +import { styled } from '@mui/material/styles'; +import { Avatar, Typography } from '@mui/material'; export const StyledAvatar = styled(Avatar)({ width: '1.25rem', height: '1.25rem', marginRight: '0.25rem', -}) +}); export const EmojiWrap = styled('div')({ display: 'flex', alignItems: 'center', -}) +}); export const StyledTypography = styled(Typography)({ fontSize: '0.88rem', -}) +}); diff --git a/frontend/src/exceptions/BadRequestException.ts b/frontend/src/exceptions/BadRequestException.ts index aaf7ae0237..9570d6d51b 100644 --- a/frontend/src/exceptions/BadRequestException.ts +++ b/frontend/src/exceptions/BadRequestException.ts @@ -1,9 +1,9 @@ -import { IHeartBeatException } from '@src/exceptions/ExceptionType' +import { IHeartBeatException } from '@src/exceptions/ExceptionType'; export class BadRequestException extends Error implements IHeartBeatException { - code: number + code: number; constructor(message: string, status: number) { - super(message) - this.code = status + super(message); + this.code = status; } } diff --git a/frontend/src/exceptions/ExceptionType.ts b/frontend/src/exceptions/ExceptionType.ts index 9b90d52236..6f06324a15 100644 --- a/frontend/src/exceptions/ExceptionType.ts +++ b/frontend/src/exceptions/ExceptionType.ts @@ -1,4 +1,4 @@ export interface IHeartBeatException { - code?: number - message: string + code?: number; + message: string; } diff --git a/frontend/src/exceptions/ForbiddenException.ts b/frontend/src/exceptions/ForbiddenException.ts index a66db5de61..2538154bc1 100644 --- a/frontend/src/exceptions/ForbiddenException.ts +++ b/frontend/src/exceptions/ForbiddenException.ts @@ -1,9 +1,9 @@ -import { IHeartBeatException } from '@src/exceptions/ExceptionType' +import { IHeartBeatException } from '@src/exceptions/ExceptionType'; export class ForbiddenException extends Error implements IHeartBeatException { - code: number + code: number; constructor(message: string, status: number) { - super(message) - this.code = status + super(message); + this.code = status; } } diff --git a/frontend/src/exceptions/InternalServerException.ts b/frontend/src/exceptions/InternalServerException.ts index 9a7e55b3b4..728a0f67ef 100644 --- a/frontend/src/exceptions/InternalServerException.ts +++ b/frontend/src/exceptions/InternalServerException.ts @@ -1,9 +1,9 @@ -import { IHeartBeatException } from '@src/exceptions/ExceptionType' +import { IHeartBeatException } from '@src/exceptions/ExceptionType'; export class InternalServerException extends Error implements IHeartBeatException { - code: number + code: number; constructor(message: string, status: number) { - super(message) - this.code = status + super(message); + this.code = status; } } diff --git a/frontend/src/exceptions/NotFoundException.ts b/frontend/src/exceptions/NotFoundException.ts index db9766a26d..2e6aecfc12 100644 --- a/frontend/src/exceptions/NotFoundException.ts +++ b/frontend/src/exceptions/NotFoundException.ts @@ -1,9 +1,9 @@ -import { IHeartBeatException } from '@src/exceptions/ExceptionType' +import { IHeartBeatException } from '@src/exceptions/ExceptionType'; export class NotFoundException extends Error implements IHeartBeatException { - code: number + code: number; constructor(message: string, status: number) { - super(message) - this.code = status + super(message); + this.code = status; } } diff --git a/frontend/src/exceptions/TimeoutException.ts b/frontend/src/exceptions/TimeoutException.ts index 079885e90a..1a5c5f3c63 100644 --- a/frontend/src/exceptions/TimeoutException.ts +++ b/frontend/src/exceptions/TimeoutException.ts @@ -1,9 +1,9 @@ -import { IHeartBeatException } from '@src/exceptions/ExceptionType' +import { IHeartBeatException } from '@src/exceptions/ExceptionType'; export class TimeoutException extends Error implements IHeartBeatException { - code: number + code: number; constructor(message: string, status: number) { - super(message) - this.code = status + super(message); + this.code = status; } } diff --git a/frontend/src/exceptions/UnauthorizedException.ts b/frontend/src/exceptions/UnauthorizedException.ts index 42a93c78b7..792e95a1b8 100644 --- a/frontend/src/exceptions/UnauthorizedException.ts +++ b/frontend/src/exceptions/UnauthorizedException.ts @@ -1,9 +1,9 @@ -import { IHeartBeatException } from '@src/exceptions/ExceptionType' +import { IHeartBeatException } from '@src/exceptions/ExceptionType'; export class UnauthorizedException extends Error implements IHeartBeatException { - code: number + code: number; constructor(message: string, status: number) { - super(message) - this.code = status + super(message); + this.code = status; } } diff --git a/frontend/src/exceptions/UnkonwException.ts b/frontend/src/exceptions/UnkonwException.ts index 0fe5d58209..ffba8dff0d 100644 --- a/frontend/src/exceptions/UnkonwException.ts +++ b/frontend/src/exceptions/UnkonwException.ts @@ -1,8 +1,8 @@ -import { MESSAGE } from '@src/constants/resources' -import { IHeartBeatException } from '@src/exceptions/ExceptionType' +import { MESSAGE } from '@src/constants/resources'; +import { IHeartBeatException } from '@src/exceptions/ExceptionType'; export class UnknownException extends Error implements IHeartBeatException { constructor() { - super(MESSAGE.UNKNOWN_ERROR) + super(MESSAGE.UNKNOWN_ERROR); } } diff --git a/frontend/src/exceptions/index.ts b/frontend/src/exceptions/index.ts index 1f7cb18a5c..b922f34672 100644 --- a/frontend/src/exceptions/index.ts +++ b/frontend/src/exceptions/index.ts @@ -1,10 +1,10 @@ -import { BadRequestException } from '@src/exceptions/BadRequestException' -import { UnauthorizedException } from '@src/exceptions/UnauthorizedException' -import { ForbiddenException } from '@src/exceptions/ForbiddenException' -import { NotFoundException } from '@src/exceptions/NotFoundException' -import { InternalServerException } from '@src/exceptions/InternalServerException' -import { TimeoutException } from '@src/exceptions/TimeoutException' -import { UnknownException } from '@src/exceptions/UnkonwException' +import { BadRequestException } from '@src/exceptions/BadRequestException'; +import { UnauthorizedException } from '@src/exceptions/UnauthorizedException'; +import { ForbiddenException } from '@src/exceptions/ForbiddenException'; +import { NotFoundException } from '@src/exceptions/NotFoundException'; +import { InternalServerException } from '@src/exceptions/InternalServerException'; +import { TimeoutException } from '@src/exceptions/TimeoutException'; +import { UnknownException } from '@src/exceptions/UnkonwException'; export const isHeartBeatException = (o: unknown) => [ @@ -15,4 +15,4 @@ export const isHeartBeatException = (o: unknown) => InternalServerException, TimeoutException, UnknownException, - ].some((excptionClass) => o instanceof excptionClass) + ].some((excptionClass) => o instanceof excptionClass); diff --git a/frontend/src/fileConfig/fileConfig.ts b/frontend/src/fileConfig/fileConfig.ts index 730d988468..745f65a025 100644 --- a/frontend/src/fileConfig/fileConfig.ts +++ b/frontend/src/fileConfig/fileConfig.ts @@ -1,87 +1,87 @@ -import { CALENDAR } from '@src/constants/resources' +import { CALENDAR } from '@src/constants/resources'; export interface OldFileConfig { - projectName: string - metrics: string[] - startDate: string - endDate: string - considerHoliday: boolean + projectName: string; + metrics: string[]; + startDate: string; + endDate: string; + considerHoliday: boolean; board?: { - type?: string - verifyToken?: string - boardId?: string - token?: string - site?: string - email?: string - projectKey?: string - } + type?: string; + verifyToken?: string; + boardId?: string; + token?: string; + site?: string; + email?: string; + projectKey?: string; + }; pipelineTool?: { - type?: string - verifyToken?: string - token?: string - } + type?: string; + verifyToken?: string; + token?: string; + }; sourceControl?: { - type?: string - verifyToken?: string - token?: string - } - crews?: string[] - assigneeFilter?: string - cycleTime?: unknown - doneStatus?: string[] - classifications?: string[] - deployment?: OldConfigSetting[] - leadTime?: OldConfigSetting[] - pipelineCrews?: string[] + type?: string; + verifyToken?: string; + token?: string; + }; + crews?: string[]; + assigneeFilter?: string; + cycleTime?: unknown; + doneStatus?: string[]; + classifications?: string[]; + deployment?: OldConfigSetting[]; + leadTime?: OldConfigSetting[]; + pipelineCrews?: string[]; } interface OldConfigSetting { - pipelineId?: string - step?: string - orgId?: string - branches?: string[] + pipelineId?: string; + step?: string; + orgId?: string; + branches?: string[]; } interface NewConfigSetting { - id: number - organization?: string - pipelineName?: string - step?: string - branch?: string[] + id: number; + organization?: string; + pipelineName?: string; + step?: string; + branch?: string[]; } export interface NewFileConfig { - projectName: string + projectName: string; dateRange: { - startDate: string - endDate: string - } - calendarType: string - metrics: string[] + startDate: string; + endDate: string; + }; + calendarType: string; + metrics: string[]; board?: { - type?: string - boardId?: string - email?: string - projectKey?: string - site?: string - token?: string - } + type?: string; + boardId?: string; + email?: string; + projectKey?: string; + site?: string; + token?: string; + }; pipelineTool?: { - type?: string - token?: string - } + type?: string; + token?: string; + }; sourceControl?: { - type?: string - token?: string - } - crews?: string[] - assigneeFilter?: string - cycleTime?: unknown - doneStatus?: string[] - classification?: string[] - deployment?: NewConfigSetting[] - leadTime?: NewConfigSetting[] - pipelineCrews?: string[] + type?: string; + token?: string; + }; + crews?: string[]; + assigneeFilter?: string; + cycleTime?: unknown; + doneStatus?: string[]; + classification?: string[]; + deployment?: NewConfigSetting[]; + leadTime?: NewConfigSetting[]; + pipelineCrews?: string[]; } export const convertToNewFileConfig = (fileConfig: OldFileConfig | NewFileConfig): NewFileConfig => { if ('considerHoliday' in fileConfig) { @@ -101,7 +101,7 @@ export const convertToNewFileConfig = (fileConfig: OldFileConfig | NewFileConfig classifications, deployment, pipelineCrews, - } = fileConfig + } = fileConfig; return { projectName, dateRange: { startDate, endDate }, @@ -136,7 +136,7 @@ export const convertToNewFileConfig = (fileConfig: OldFileConfig | NewFileConfig step: item?.step, branches: item?.branches, })), - } + }; } - return fileConfig -} + return fileConfig; +}; diff --git a/frontend/src/hooks/index.ts b/frontend/src/hooks/index.ts index e51e22dad4..6fdab881ce 100644 --- a/frontend/src/hooks/index.ts +++ b/frontend/src/hooks/index.ts @@ -1,6 +1,6 @@ -import { useDispatch, useSelector } from 'react-redux' -import type { TypedUseSelectorHook } from 'react-redux' -import type { RootState, AppDispatch } from '@src/store' +import { useDispatch, useSelector } from 'react-redux'; +import type { TypedUseSelectorHook } from 'react-redux'; +import type { RootState, AppDispatch } from '@src/store'; -export const useAppDispatch: () => AppDispatch = useDispatch -export const useAppSelector: TypedUseSelectorHook = useSelector +export const useAppDispatch: () => AppDispatch = useDispatch; +export const useAppSelector: TypedUseSelectorHook = useSelector; diff --git a/frontend/src/hooks/reportMapper/changeFailureRate.ts b/frontend/src/hooks/reportMapper/changeFailureRate.ts index c45a93bf3c..165089da11 100644 --- a/frontend/src/hooks/reportMapper/changeFailureRate.ts +++ b/frontend/src/hooks/reportMapper/changeFailureRate.ts @@ -1,12 +1,12 @@ -import { ReportDataWithThreeColumns } from '@src/hooks/reportMapper/reportUIDataStructure' -import { ChangeFailureRateResponse } from '@src/clients/report/dto/response' -import { FAILURE_RATE_NAME } from '@src/constants/resources' +import { ReportDataWithThreeColumns } from '@src/hooks/reportMapper/reportUIDataStructure'; +import { ChangeFailureRateResponse } from '@src/clients/report/dto/response'; +import { FAILURE_RATE_NAME } from '@src/constants/resources'; export const changeFailureRateMapper = ({ avgChangeFailureRate, changeFailureRateOfPipelines, }: ChangeFailureRateResponse) => { - const mappedChangeFailureRateValue: ReportDataWithThreeColumns[] = [] + const mappedChangeFailureRateValue: ReportDataWithThreeColumns[] = []; changeFailureRateOfPipelines.map((item, index) => { const deploymentFrequencyValue: ReportDataWithThreeColumns = { @@ -18,9 +18,9 @@ export const changeFailureRateMapper = ({ value: `${(item.failureRate * 100).toFixed(2)}%(${item.failedTimesOfPipeline}/${item.totalTimesOfPipeline})`, }, ], - } - mappedChangeFailureRateValue.push(deploymentFrequencyValue) - }) + }; + mappedChangeFailureRateValue.push(deploymentFrequencyValue); + }); mappedChangeFailureRateValue.push({ id: mappedChangeFailureRateValue.length, name: avgChangeFailureRate.name, @@ -32,7 +32,7 @@ export const changeFailureRateMapper = ({ })`, }, ], - }) + }); - return mappedChangeFailureRateValue -} + return mappedChangeFailureRateValue; +}; diff --git a/frontend/src/hooks/reportMapper/classification.ts b/frontend/src/hooks/reportMapper/classification.ts index 59da6155f4..4935f4aaa1 100644 --- a/frontend/src/hooks/reportMapper/classification.ts +++ b/frontend/src/hooks/reportMapper/classification.ts @@ -1,23 +1,23 @@ -import { ReportDataWithThreeColumns } from '@src/hooks/reportMapper/reportUIDataStructure' -import { ClassificationResponse } from '@src/clients/report/dto/response' +import { ReportDataWithThreeColumns } from '@src/hooks/reportMapper/reportUIDataStructure'; +import { ClassificationResponse } from '@src/clients/report/dto/response'; export const classificationMapper = (classification: ClassificationResponse[]) => { - const mappedClassificationValue: ReportDataWithThreeColumns[] = [] + const mappedClassificationValue: ReportDataWithThreeColumns[] = []; classification.map((item, index) => { - const pairsValues: { name: string; value: string }[] = [] + const pairsValues: { name: string; value: string }[] = []; item.pairList.map((pairItem) => { - pairsValues.push({ name: pairItem.name, value: `${(pairItem.value * 100).toFixed(2)}%` }) - }) + pairsValues.push({ name: pairItem.name, value: `${(pairItem.value * 100).toFixed(2)}%` }); + }); const classificationValue: ReportDataWithThreeColumns = { id: index, name: item.fieldName, valuesList: pairsValues, - } - mappedClassificationValue.push(classificationValue) - }) + }; + mappedClassificationValue.push(classificationValue); + }); - return mappedClassificationValue -} + return mappedClassificationValue; +}; diff --git a/frontend/src/hooks/reportMapper/cycleTime.ts b/frontend/src/hooks/reportMapper/cycleTime.ts index f8d2586215..8dd4f3644e 100644 --- a/frontend/src/hooks/reportMapper/cycleTime.ts +++ b/frontend/src/hooks/reportMapper/cycleTime.ts @@ -1,6 +1,6 @@ -import { CYCLE_TIME_METRICS_NAME, METRICS_CONSTANTS, REPORT_SUFFIX_UNITS } from '@src/constants/resources' -import { ReportDataWithTwoColumns, ValueWithUnits } from '@src/hooks/reportMapper/reportUIDataStructure' -import { CycleTimeResponse, Swimlane } from '@src/clients/report/dto/response' +import { CYCLE_TIME_METRICS_NAME, METRICS_CONSTANTS, REPORT_SUFFIX_UNITS } from '@src/constants/resources'; +import { ReportDataWithTwoColumns, ValueWithUnits } from '@src/hooks/reportMapper/reportUIDataStructure'; +import { CycleTimeResponse, Swimlane } from '@src/clients/report/dto/response'; export const cycleTimeMapper = ({ swimlaneList, @@ -8,17 +8,17 @@ export const cycleTimeMapper = ({ averageCycleTimePerSP, averageCycleTimePerCard, }: CycleTimeResponse) => { - const mappedCycleTimeValue: ReportDataWithTwoColumns[] = [] + const mappedCycleTimeValue: ReportDataWithTwoColumns[] = []; const getSwimlaneByItemName = (itemName: string) => { - return swimlaneList.find((item: Swimlane) => item.optionalItemName === itemName) - } + return swimlaneList.find((item: Swimlane) => item.optionalItemName === itemName); + }; const calPerColumnTotalTimeDivTotalTime = (itemName: string): ValueWithUnits[] => { - const swimlane = getSwimlaneByItemName(itemName) - return swimlane ? [{ value: `${parseFloat(((swimlane.totalTime / totalTimeForCards) * 100).toFixed(2))}%` }] : [] - } + const swimlane = getSwimlaneByItemName(itemName); + return swimlane ? [{ value: `${parseFloat(((swimlane.totalTime / totalTimeForCards) * 100).toFixed(2))}%` }] : []; + }; const getAverageTimeForPerColumn = (itemName: string) => { - const swimlane = getSwimlaneByItemName(itemName) + const swimlane = getSwimlaneByItemName(itemName); return swimlane ? [ { value: swimlane.averageTimeForSP.toFixed(2), unit: REPORT_SUFFIX_UNITS.PER_SP }, @@ -27,8 +27,8 @@ export const cycleTimeMapper = ({ unit: REPORT_SUFFIX_UNITS.PER_CARD, }, ] - : [] - } + : []; + }; const cycleTimeValue: { [key: string]: ValueWithUnits[] } = { AVERAGE_CYCLE_TIME: [ @@ -48,13 +48,13 @@ export const cycleTimeMapper = ({ AVERAGE_BLOCK_TIME: getAverageTimeForPerColumn(METRICS_CONSTANTS.blockValue), AVERAGE_REVIEW_TIME: getAverageTimeForPerColumn(METRICS_CONSTANTS.reviewValue), AVERAGE_TESTING_TIME: getAverageTimeForPerColumn(METRICS_CONSTANTS.testingValue), - } + }; Object.entries(CYCLE_TIME_METRICS_NAME).map(([key, cycleName]) => { if (cycleTimeValue[key].length > 0) { - mappedCycleTimeValue.push({ id: mappedCycleTimeValue.length, name: cycleName, valueList: cycleTimeValue[key] }) + mappedCycleTimeValue.push({ id: mappedCycleTimeValue.length, name: cycleName, valueList: cycleTimeValue[key] }); } - }) + }); - return mappedCycleTimeValue -} + return mappedCycleTimeValue; +}; diff --git a/frontend/src/hooks/reportMapper/deploymentFrequency.ts b/frontend/src/hooks/reportMapper/deploymentFrequency.ts index bf55bf74a1..df4689bd44 100644 --- a/frontend/src/hooks/reportMapper/deploymentFrequency.ts +++ b/frontend/src/hooks/reportMapper/deploymentFrequency.ts @@ -1,21 +1,21 @@ -import { ReportDataWithThreeColumns } from '@src/hooks/reportMapper/reportUIDataStructure' -import { DeploymentFrequencyResponse } from '@src/clients/report/dto/response' -import { DEPLOYMENT_FREQUENCY_NAME } from '@src/constants/resources' +import { ReportDataWithThreeColumns } from '@src/hooks/reportMapper/reportUIDataStructure'; +import { DeploymentFrequencyResponse } from '@src/clients/report/dto/response'; +import { DEPLOYMENT_FREQUENCY_NAME } from '@src/constants/resources'; export const deploymentFrequencyMapper = ({ avgDeploymentFrequency, deploymentFrequencyOfPipelines, }: DeploymentFrequencyResponse) => { - const mappedDeploymentFrequencyValue: ReportDataWithThreeColumns[] = [] + const mappedDeploymentFrequencyValue: ReportDataWithThreeColumns[] = []; deploymentFrequencyOfPipelines.map((item, index) => { const deploymentFrequencyValue: ReportDataWithThreeColumns = { id: index, name: `${item.name}/${item.step}`, valuesList: [{ name: DEPLOYMENT_FREQUENCY_NAME, value: `${item.deploymentFrequency.toFixed(2)}` }], - } - mappedDeploymentFrequencyValue.push(deploymentFrequencyValue) - }) + }; + mappedDeploymentFrequencyValue.push(deploymentFrequencyValue); + }); mappedDeploymentFrequencyValue.push({ id: mappedDeploymentFrequencyValue.length, name: avgDeploymentFrequency.name, @@ -25,6 +25,6 @@ export const deploymentFrequencyMapper = ({ value: `${avgDeploymentFrequency.deploymentFrequency.toFixed(2)}`, }, ], - }) - return mappedDeploymentFrequencyValue -} + }); + return mappedDeploymentFrequencyValue; +}; diff --git a/frontend/src/hooks/reportMapper/exportValidityTime.ts b/frontend/src/hooks/reportMapper/exportValidityTime.ts index c24d500e4a..2440f055c8 100644 --- a/frontend/src/hooks/reportMapper/exportValidityTime.ts +++ b/frontend/src/hooks/reportMapper/exportValidityTime.ts @@ -1,8 +1,8 @@ -import dayjs from 'dayjs' -import duration from 'dayjs/plugin/duration' +import dayjs from 'dayjs'; +import duration from 'dayjs/plugin/duration'; export const exportValidityTimeMapper = (exportValidityTime: number | null) => { - dayjs.extend(duration) - const timestamp = exportValidityTime ? exportValidityTime : null - return timestamp ? dayjs.duration(timestamp).asMinutes() : null -} + dayjs.extend(duration); + const timestamp = exportValidityTime ? exportValidityTime : null; + return timestamp ? dayjs.duration(timestamp).asMinutes() : null; +}; diff --git a/frontend/src/hooks/reportMapper/leadTimeForChanges.ts b/frontend/src/hooks/reportMapper/leadTimeForChanges.ts index 55b0870018..28f81de85c 100644 --- a/frontend/src/hooks/reportMapper/leadTimeForChanges.ts +++ b/frontend/src/hooks/reportMapper/leadTimeForChanges.ts @@ -1,18 +1,18 @@ -import { LeadTimeForChangesResponse } from '@src/clients/report/dto/response' +import { LeadTimeForChangesResponse } from '@src/clients/report/dto/response'; export const leadTimeForChangesMapper = ({ leadTimeForChangesOfPipelines, avgLeadTimeForChanges, }: LeadTimeForChangesResponse) => { - const minutesPerHour = 60 + const minutesPerHour = 60; const formatDuration = (duration: number) => { - return (duration / minutesPerHour).toFixed(2) - } + return (duration / minutesPerHour).toFixed(2); + }; const formatNameDisplay = (name: string) => { - if (name == 'pipelineLeadTime') return 'Pipeline Lead Time' - if (name == 'prLeadTime') return 'PR Lead Time' - if (name == 'totalDelayTime') return 'Total Lead Time' - } + if (name == 'pipelineLeadTime') return 'Pipeline Lead Time'; + if (name == 'prLeadTime') return 'PR Lead Time'; + if (name == 'totalDelayTime') return 'Total Lead Time'; + }; const mappedLeadTimeForChangesValue = leadTimeForChangesOfPipelines.map((item, index) => { return { @@ -24,8 +24,8 @@ export const leadTimeForChangesMapper = ({ name: formatNameDisplay(name) as string, value: formatDuration(value), })), - } - }) + }; + }); mappedLeadTimeForChangesValue.push({ id: mappedLeadTimeForChangesValue.length, @@ -36,7 +36,7 @@ export const leadTimeForChangesMapper = ({ name: formatNameDisplay(name) as string, value: formatDuration(value), })), - }) + }); - return mappedLeadTimeForChangesValue -} + return mappedLeadTimeForChangesValue; +}; diff --git a/frontend/src/hooks/reportMapper/meanTimeToRecovery.ts b/frontend/src/hooks/reportMapper/meanTimeToRecovery.ts index 1e35046ed0..08f7fa0a71 100644 --- a/frontend/src/hooks/reportMapper/meanTimeToRecovery.ts +++ b/frontend/src/hooks/reportMapper/meanTimeToRecovery.ts @@ -1,19 +1,19 @@ -import { MeanTimeToRecoveryResponse } from '@src/clients/report/dto/response' -import { ReportDataWithThreeColumns } from '@src/hooks/reportMapper/reportUIDataStructure' -import { MEAN_TIME_TO_RECOVERY_NAME } from '@src/constants/resources' +import { MeanTimeToRecoveryResponse } from '@src/clients/report/dto/response'; +import { ReportDataWithThreeColumns } from '@src/hooks/reportMapper/reportUIDataStructure'; +import { MEAN_TIME_TO_RECOVERY_NAME } from '@src/constants/resources'; export const meanTimeToRecoveryMapper = ({ avgMeanTimeToRecovery, meanTimeRecoveryPipelines, }: MeanTimeToRecoveryResponse) => { - const minutesPerHour = 60 - const milliscondMinute = 60000 + const minutesPerHour = 60; + const milliscondMinute = 60000; const formatDuration = (duration: number) => { - const minutesDuration = duration / milliscondMinute - return (minutesDuration / minutesPerHour).toFixed(2) - } + const minutesDuration = duration / milliscondMinute; + return (minutesDuration / minutesPerHour).toFixed(2); + }; - const mappedMeanTimeToRecoveryValue: ReportDataWithThreeColumns[] = [] + const mappedMeanTimeToRecoveryValue: ReportDataWithThreeColumns[] = []; meanTimeRecoveryPipelines.map((item, index) => { const meanTimeToRecoveryValue: ReportDataWithThreeColumns = { @@ -25,9 +25,9 @@ export const meanTimeToRecoveryMapper = ({ value: formatDuration(item.timeToRecovery), }, ], - } - mappedMeanTimeToRecoveryValue.push(meanTimeToRecoveryValue) - }) + }; + mappedMeanTimeToRecoveryValue.push(meanTimeToRecoveryValue); + }); mappedMeanTimeToRecoveryValue.push({ id: mappedMeanTimeToRecoveryValue.length, name: avgMeanTimeToRecovery.name, @@ -37,7 +37,7 @@ export const meanTimeToRecoveryMapper = ({ value: formatDuration(avgMeanTimeToRecovery.timeToRecovery), }, ], - }) + }); - return mappedMeanTimeToRecoveryValue -} + return mappedMeanTimeToRecoveryValue; +}; diff --git a/frontend/src/hooks/reportMapper/report.ts b/frontend/src/hooks/reportMapper/report.ts index 3fe04dfeb9..348c3b0bff 100644 --- a/frontend/src/hooks/reportMapper/report.ts +++ b/frontend/src/hooks/reportMapper/report.ts @@ -1,12 +1,12 @@ -import { ReportResponse, ReportResponseDTO } from '@src/clients/report/dto/response' -import { changeFailureRateMapper } from '@src/hooks/reportMapper/changeFailureRate' -import { velocityMapper } from '@src/hooks/reportMapper/velocity' -import { cycleTimeMapper } from '@src/hooks/reportMapper/cycleTime' -import { classificationMapper } from '@src/hooks/reportMapper/classification' -import { deploymentFrequencyMapper } from '@src/hooks/reportMapper/deploymentFrequency' -import { leadTimeForChangesMapper } from '@src/hooks/reportMapper/leadTimeForChanges' -import { meanTimeToRecoveryMapper } from '@src/hooks/reportMapper/meanTimeToRecovery' -import { exportValidityTimeMapper } from '@src/hooks/reportMapper/exportValidityTime' +import { ReportResponse, ReportResponseDTO } from '@src/clients/report/dto/response'; +import { changeFailureRateMapper } from '@src/hooks/reportMapper/changeFailureRate'; +import { velocityMapper } from '@src/hooks/reportMapper/velocity'; +import { cycleTimeMapper } from '@src/hooks/reportMapper/cycleTime'; +import { classificationMapper } from '@src/hooks/reportMapper/classification'; +import { deploymentFrequencyMapper } from '@src/hooks/reportMapper/deploymentFrequency'; +import { leadTimeForChangesMapper } from '@src/hooks/reportMapper/leadTimeForChanges'; +import { meanTimeToRecoveryMapper } from '@src/hooks/reportMapper/meanTimeToRecovery'; +import { exportValidityTimeMapper } from '@src/hooks/reportMapper/exportValidityTime'; export const reportMapper = ({ velocity, @@ -18,21 +18,21 @@ export const reportMapper = ({ changeFailureRate, exportValidityTime, }: ReportResponseDTO): ReportResponse => { - const velocityList = velocity && velocityMapper(velocity) + const velocityList = velocity && velocityMapper(velocity); - const cycleTimeList = cycleTime && cycleTimeMapper(cycleTime) + const cycleTimeList = cycleTime && cycleTimeMapper(cycleTime); - const classification = classificationList && classificationMapper(classificationList) + const classification = classificationList && classificationMapper(classificationList); - const deploymentFrequencyList = deploymentFrequency && deploymentFrequencyMapper(deploymentFrequency) + const deploymentFrequencyList = deploymentFrequency && deploymentFrequencyMapper(deploymentFrequency); - const meanTimeToRecoveryList = meanTimeToRecovery && meanTimeToRecoveryMapper(meanTimeToRecovery) + const meanTimeToRecoveryList = meanTimeToRecovery && meanTimeToRecoveryMapper(meanTimeToRecovery); - const leadTimeForChangesList = leadTimeForChanges && leadTimeForChangesMapper(leadTimeForChanges) + const leadTimeForChangesList = leadTimeForChanges && leadTimeForChangesMapper(leadTimeForChanges); - const changeFailureRateList = changeFailureRate && changeFailureRateMapper(changeFailureRate) + const changeFailureRateList = changeFailureRate && changeFailureRateMapper(changeFailureRate); - const exportValidityTimeMin = exportValidityTimeMapper(exportValidityTime) + const exportValidityTimeMin = exportValidityTimeMapper(exportValidityTime); return { velocityList, @@ -43,5 +43,5 @@ export const reportMapper = ({ leadTimeForChangesList, changeFailureRateList, exportValidityTimeMin, - } -} + }; +}; diff --git a/frontend/src/hooks/reportMapper/reportUIDataStructure.ts b/frontend/src/hooks/reportMapper/reportUIDataStructure.ts index a08f1092eb..d9add8c71d 100644 --- a/frontend/src/hooks/reportMapper/reportUIDataStructure.ts +++ b/frontend/src/hooks/reportMapper/reportUIDataStructure.ts @@ -1,19 +1,19 @@ export interface ReportDataWithTwoColumns { - id: number - name: string - valueList: ValueWithUnits[] + id: number; + name: string; + valueList: ValueWithUnits[]; } export interface ValueWithUnits { - value: number | string - unit?: string + value: number | string; + unit?: string; } export interface ReportDataWithThreeColumns { - id: number - name: string + id: number; + name: string; valuesList: { - name: string - value: string - }[] + name: string; + value: string; + }[]; } diff --git a/frontend/src/hooks/reportMapper/velocity.ts b/frontend/src/hooks/reportMapper/velocity.ts index df4904356f..d4120fdf5d 100644 --- a/frontend/src/hooks/reportMapper/velocity.ts +++ b/frontend/src/hooks/reportMapper/velocity.ts @@ -1,22 +1,22 @@ -import { VELOCITY_METRICS_NAME } from '@src/constants/resources' -import { ReportDataWithTwoColumns } from '@src/hooks/reportMapper/reportUIDataStructure' -import { VelocityResponse } from '@src/clients/report/dto/response' +import { VELOCITY_METRICS_NAME } from '@src/constants/resources'; +import { ReportDataWithTwoColumns } from '@src/hooks/reportMapper/reportUIDataStructure'; +import { VelocityResponse } from '@src/clients/report/dto/response'; export const velocityMapper = ({ velocityForSP, velocityForCards }: VelocityResponse) => { - const mappedVelocityValue: ReportDataWithTwoColumns[] = [] + const mappedVelocityValue: ReportDataWithTwoColumns[] = []; const velocityValue: { [key: string]: number } = { VELOCITY_SP: velocityForSP, THROUGHPUT_CARDS_COUNT: velocityForCards, - } + }; Object.entries(VELOCITY_METRICS_NAME).map(([key, velocityName], index) => { mappedVelocityValue.push({ id: index, name: velocityName, valueList: [{ value: velocityValue[key] }], - }) - }) + }); + }); - return mappedVelocityValue -} + return mappedVelocityValue; +}; diff --git a/frontend/src/hooks/useAppDispatch.ts b/frontend/src/hooks/useAppDispatch.ts index e51e22dad4..6fdab881ce 100644 --- a/frontend/src/hooks/useAppDispatch.ts +++ b/frontend/src/hooks/useAppDispatch.ts @@ -1,6 +1,6 @@ -import { useDispatch, useSelector } from 'react-redux' -import type { TypedUseSelectorHook } from 'react-redux' -import type { RootState, AppDispatch } from '@src/store' +import { useDispatch, useSelector } from 'react-redux'; +import type { TypedUseSelectorHook } from 'react-redux'; +import type { RootState, AppDispatch } from '@src/store'; -export const useAppDispatch: () => AppDispatch = useDispatch -export const useAppSelector: TypedUseSelectorHook = useSelector +export const useAppDispatch: () => AppDispatch = useDispatch; +export const useAppSelector: TypedUseSelectorHook = useSelector; diff --git a/frontend/src/hooks/useExportCsvEffect.ts b/frontend/src/hooks/useExportCsvEffect.ts index db6dbb989f..5e13605259 100644 --- a/frontend/src/hooks/useExportCsvEffect.ts +++ b/frontend/src/hooks/useExportCsvEffect.ts @@ -1,35 +1,35 @@ -import { useState } from 'react' -import { CSVReportRequestDTO } from '@src/clients/report/dto/request' -import { csvClient } from '@src/clients/report/CSVClient' -import { NotFoundException } from '@src/exceptions/NotFoundException' -import { DURATION } from '@src/constants/commons' +import { useState } from 'react'; +import { CSVReportRequestDTO } from '@src/clients/report/dto/request'; +import { csvClient } from '@src/clients/report/CSVClient'; +import { NotFoundException } from '@src/exceptions/NotFoundException'; +import { DURATION } from '@src/constants/commons'; export interface useExportCsvEffectInterface { - fetchExportData: (params: CSVReportRequestDTO) => void - errorMessage: string - isExpired: boolean + fetchExportData: (params: CSVReportRequestDTO) => void; + errorMessage: string; + isExpired: boolean; } export const useExportCsvEffect = (): useExportCsvEffectInterface => { - const [errorMessage, setErrorMessage] = useState('') - const [isExpired, setIsExpired] = useState(false) + const [errorMessage, setErrorMessage] = useState(''); + const [isExpired, setIsExpired] = useState(false); const fetchExportData = async (params: CSVReportRequestDTO) => { try { - setIsExpired(false) - return await csvClient.exportCSVData(params) + setIsExpired(false); + return await csvClient.exportCSVData(params); } catch (e) { - const err = e as Error + const err = e as Error; if (err instanceof NotFoundException) { - setIsExpired(true) + setIsExpired(true); } else { - setErrorMessage(`failed to export csv: ${err.message}`) + setErrorMessage(`failed to export csv: ${err.message}`); setTimeout(() => { - setErrorMessage('') - }, DURATION.ERROR_MESSAGE_TIME) + setErrorMessage(''); + }, DURATION.ERROR_MESSAGE_TIME); } } - } + }; - return { fetchExportData, errorMessage, isExpired } -} + return { fetchExportData, errorMessage, isExpired }; +}; diff --git a/frontend/src/hooks/useGenerateReportEffect.ts b/frontend/src/hooks/useGenerateReportEffect.ts index 5d0210b91b..b548d5df2b 100644 --- a/frontend/src/hooks/useGenerateReportEffect.ts +++ b/frontend/src/hooks/useGenerateReportEffect.ts @@ -1,95 +1,95 @@ -import { useRef, useState } from 'react' -import { reportClient } from '@src/clients/report/ReportClient' -import { BoardReportRequestDTO, ReportRequestDTO } from '@src/clients/report/dto/request' -import { UnknownException } from '@src/exceptions/UnkonwException' -import { InternalServerException } from '@src/exceptions/InternalServerException' -import { ReportResponseDTO } from '@src/clients/report/dto/response' -import { DURATION, RETRIEVE_REPORT_TYPES } from '@src/constants/commons' -import { exportValidityTimeMapper } from '@src/hooks/reportMapper/exportValidityTime' +import { useRef, useState } from 'react'; +import { reportClient } from '@src/clients/report/ReportClient'; +import { BoardReportRequestDTO, ReportRequestDTO } from '@src/clients/report/dto/request'; +import { UnknownException } from '@src/exceptions/UnkonwException'; +import { InternalServerException } from '@src/exceptions/InternalServerException'; +import { ReportResponseDTO } from '@src/clients/report/dto/response'; +import { DURATION, RETRIEVE_REPORT_TYPES } from '@src/constants/commons'; +import { exportValidityTimeMapper } from '@src/hooks/reportMapper/exportValidityTime'; export interface useGenerateReportEffectInterface { - startToRequestBoardData: (boardParams: BoardReportRequestDTO) => void - startToRequestDoraData: (doraParams: ReportRequestDTO) => void - stopPollingReports: () => void - isServerError: boolean - errorMessage: string - reportData: ReportResponseDTO | undefined + startToRequestBoardData: (boardParams: BoardReportRequestDTO) => void; + startToRequestDoraData: (doraParams: ReportRequestDTO) => void; + stopPollingReports: () => void; + isServerError: boolean; + errorMessage: string; + reportData: ReportResponseDTO | undefined; } export const useGenerateReportEffect = (): useGenerateReportEffectInterface => { - const reportPath = '/reports' - const [isServerError, setIsServerError] = useState(false) - const [errorMessage, setErrorMessage] = useState('') - const [reportData, setReportData] = useState() - const timerIdRef = useRef() - let hasPollingStarted = false + const reportPath = '/reports'; + const [isServerError, setIsServerError] = useState(false); + const [errorMessage, setErrorMessage] = useState(''); + const [reportData, setReportData] = useState(); + const timerIdRef = useRef(); + let hasPollingStarted = false; const startToRequestBoardData = (boardParams: ReportRequestDTO) => { reportClient .retrieveReportByUrl(boardParams, `${reportPath}/${RETRIEVE_REPORT_TYPES.BOARD}`) .then((res) => { - if (hasPollingStarted) return - hasPollingStarted = true - pollingReport(res.response.callbackUrl, res.response.interval) + if (hasPollingStarted) return; + hasPollingStarted = true; + pollingReport(res.response.callbackUrl, res.response.interval); }) .catch((e) => { - handleError(e) - stopPollingReports() - }) - } + handleError(e); + stopPollingReports(); + }); + }; const handleError = (error: Error) => { if (error instanceof InternalServerException || error instanceof UnknownException) { - setIsServerError(true) + setIsServerError(true); } else { - setErrorMessage(`generate report: ${error.message}`) + setErrorMessage(`generate report: ${error.message}`); setTimeout(() => { - setErrorMessage('') - }, DURATION.ERROR_MESSAGE_TIME) + setErrorMessage(''); + }, DURATION.ERROR_MESSAGE_TIME); } - } + }; const startToRequestDoraData = (doraParams: ReportRequestDTO) => { reportClient .retrieveReportByUrl(doraParams, `${reportPath}/${RETRIEVE_REPORT_TYPES.DORA}`) .then((res) => { - if (hasPollingStarted) return - hasPollingStarted = true - pollingReport(res.response.callbackUrl, res.response.interval) + if (hasPollingStarted) return; + hasPollingStarted = true; + pollingReport(res.response.callbackUrl, res.response.interval); }) .catch((e) => { - handleError(e) - stopPollingReports() - }) - } + handleError(e); + stopPollingReports(); + }); + }; const pollingReport = (url: string, interval: number) => { reportClient .pollingReport(url) .then((res: { status: number; response: ReportResponseDTO }) => { - const response = res.response - handleAndUpdateData(response) + const response = res.response; + handleAndUpdateData(response); if (response.isAllMetricsReady) { - stopPollingReports() + stopPollingReports(); } else { - timerIdRef.current = window.setTimeout(() => pollingReport(url, interval), interval * 1000) + timerIdRef.current = window.setTimeout(() => pollingReport(url, interval), interval * 1000); } }) .catch((e) => { - handleError(e) - stopPollingReports() - }) - } + handleError(e); + stopPollingReports(); + }); + }; const stopPollingReports = () => { - window.clearTimeout(timerIdRef.current) - hasPollingStarted = false - } + window.clearTimeout(timerIdRef.current); + hasPollingStarted = false; + }; const handleAndUpdateData = (response: ReportResponseDTO) => { - const exportValidityTime = exportValidityTimeMapper(response.exportValidityTime) - setReportData({ ...response, exportValidityTime: exportValidityTime }) - } + const exportValidityTime = exportValidityTimeMapper(response.exportValidityTime); + setReportData({ ...response, exportValidityTime: exportValidityTime }); + }; return { startToRequestBoardData, @@ -98,5 +98,5 @@ export const useGenerateReportEffect = (): useGenerateReportEffectInterface => { reportData, isServerError, errorMessage, - } -} + }; +}; diff --git a/frontend/src/hooks/useGetMetricsStepsEffect.ts b/frontend/src/hooks/useGetMetricsStepsEffect.ts index 739ce1447e..cf504681c2 100644 --- a/frontend/src/hooks/useGetMetricsStepsEffect.ts +++ b/frontend/src/hooks/useGetMetricsStepsEffect.ts @@ -1,7 +1,7 @@ -import { useState } from 'react' -import { getStepsParams, metricsClient } from '@src/clients/MetricsClient' -import { DURATION } from '@src/constants/commons' -import { MESSAGE } from '@src/constants/resources' +import { useState } from 'react'; +import { getStepsParams, metricsClient } from '@src/clients/MetricsClient'; +import { DURATION } from '@src/constants/commons'; +import { MESSAGE } from '@src/constants/resources'; export interface useGetMetricsStepsEffectInterface { getSteps: ( @@ -12,20 +12,20 @@ export interface useGetMetricsStepsEffectInterface { token: string ) => Promise< | { - haveStep: boolean - response: string[] - branches: string[] - pipelineCrews: string[] + haveStep: boolean; + response: string[]; + branches: string[]; + pipelineCrews: string[]; } | undefined - > - isLoading: boolean - errorMessage: string + >; + isLoading: boolean; + errorMessage: string; } export const useGetMetricsStepsEffect = (): useGetMetricsStepsEffectInterface => { - const [isLoading, setIsLoading] = useState(false) - const [errorMessage, setErrorMessage] = useState('') + const [isLoading, setIsLoading] = useState(false); + const [errorMessage, setErrorMessage] = useState(''); const getSteps = async ( params: getStepsParams, @@ -34,19 +34,19 @@ export const useGetMetricsStepsEffect = (): useGetMetricsStepsEffectInterface => pipelineType: string, token: string ) => { - setIsLoading(true) + setIsLoading(true); try { - return await metricsClient.getSteps(params, organizationId, buildId, pipelineType, token) + return await metricsClient.getSteps(params, organizationId, buildId, pipelineType, token); } catch (e) { - const err = e as Error - setErrorMessage(`${pipelineType} ${MESSAGE.GET_STEPS_FAILED}: ${err.message}`) + const err = e as Error; + setErrorMessage(`${pipelineType} ${MESSAGE.GET_STEPS_FAILED}: ${err.message}`); setTimeout(() => { - setErrorMessage('') - }, DURATION.ERROR_MESSAGE_TIME) + setErrorMessage(''); + }, DURATION.ERROR_MESSAGE_TIME); } finally { - setIsLoading(false) + setIsLoading(false); } - } + }; - return { isLoading, getSteps, errorMessage } -} + return { isLoading, getSteps, errorMessage }; +}; diff --git a/frontend/src/hooks/useMetricsStepValidationCheckContext.tsx b/frontend/src/hooks/useMetricsStepValidationCheckContext.tsx index 8cb08fc7ee..4d05a4acb7 100644 --- a/frontend/src/hooks/useMetricsStepValidationCheckContext.tsx +++ b/frontend/src/hooks/useMetricsStepValidationCheckContext.tsx @@ -1,41 +1,41 @@ -import React, { createContext, useContext } from 'react' -import { useAppSelector } from '@src/hooks/index' -import { selectDeploymentFrequencySettings } from '@src/context/Metrics/metricsSlice' -import { PipelineSetting } from '@src/context/interface' +import React, { createContext, useContext } from 'react'; +import { useAppSelector } from '@src/hooks/index'; +import { selectDeploymentFrequencySettings } from '@src/context/Metrics/metricsSlice'; +import { PipelineSetting } from '@src/context/interface'; interface ProviderContextType { - isPipelineValid: (type: string) => boolean - getDuplicatedPipeLineIds: (pipelineSettings: PipelineSetting[]) => number[] + isPipelineValid: (type: string) => boolean; + getDuplicatedPipeLineIds: (pipelineSettings: PipelineSetting[]) => number[]; } interface ContextProviderProps { - children: React.ReactNode + children: React.ReactNode; } export const ValidationContext = createContext({ isPipelineValid: () => false, getDuplicatedPipeLineIds: () => [], -}) +}); const assignErrorMessage = (label: string, value: string, id: number, duplicatedPipeLineIds: number[]) => - !value ? `${label} is required` : duplicatedPipeLineIds.includes(id) ? `duplicated ${label}` : '' + !value ? `${label} is required` : duplicatedPipeLineIds.includes(id) ? `duplicated ${label}` : ''; const getDuplicatedPipeLineIds = (pipelineSettings: PipelineSetting[]) => { - const errors: { [key: string]: number[] } = {} + const errors: { [key: string]: number[] } = {}; pipelineSettings.forEach(({ id, organization, pipelineName, step }) => { if (organization && pipelineName && step) { - const errorString = `${organization}${pipelineName}${step}` - if (errors[errorString]) errors[errorString].push(id) - else errors[errorString] = [id] + const errorString = `${organization}${pipelineName}${step}`; + if (errors[errorString]) errors[errorString].push(id); + else errors[errorString] = [id]; } - }) + }); return Object.values(errors) .filter((ids) => ids.length > 1) - .flat() -} + .flat(); +}; const getErrorMessages = (pipelineSettings: PipelineSetting[]) => { - const duplicatedPipelineIds: number[] = getDuplicatedPipeLineIds(pipelineSettings) + const duplicatedPipelineIds: number[] = getDuplicatedPipeLineIds(pipelineSettings); return pipelineSettings.map(({ id, organization, pipelineName, step }) => ({ id, error: { @@ -43,17 +43,17 @@ const getErrorMessages = (pipelineSettings: PipelineSetting[]) => { pipelineName: assignErrorMessage('pipelineName', pipelineName, id, duplicatedPipelineIds), step: assignErrorMessage('step', step, id, duplicatedPipelineIds), }, - })) -} + })); +}; export const ContextProvider = ({ children }: ContextProviderProps) => { - const deploymentFrequencySettings = useAppSelector(selectDeploymentFrequencySettings) + const deploymentFrequencySettings = useAppSelector(selectDeploymentFrequencySettings); - const isPipelineValid = (type: string) => { - const pipelines = deploymentFrequencySettings - const errorMessages = getErrorMessages(pipelines) - return errorMessages.every(({ error }) => Object.values(error).every((val) => !val)) - } + const isPipelineValid = () => { + const pipelines = deploymentFrequencySettings; + const errorMessages = getErrorMessages(pipelines); + return errorMessages.every(({ error }) => Object.values(error).every((val) => !val)); + }; return ( { > {children} - ) -} + ); +}; -export const useMetricsStepValidationCheckContext = () => useContext(ValidationContext) +export const useMetricsStepValidationCheckContext = () => useContext(ValidationContext); diff --git a/frontend/src/hooks/useNotificationLayoutEffect.ts b/frontend/src/hooks/useNotificationLayoutEffect.ts index eac4d7d510..e8013ef1d8 100644 --- a/frontend/src/hooks/useNotificationLayoutEffect.ts +++ b/frontend/src/hooks/useNotificationLayoutEffect.ts @@ -1,17 +1,17 @@ -import { useEffect, useState } from 'react' -import { DURATION } from '@src/constants/commons' +import { useEffect, useState } from 'react'; +import { DURATION } from '@src/constants/commons'; export interface NotificationTipProps { - title: string - open: boolean - closeAutomatically: boolean - durationTimeout?: number + title: string; + open: boolean; + closeAutomatically: boolean; + durationTimeout?: number; } export interface useNotificationLayoutEffectInterface { - notificationProps?: NotificationTipProps - resetProps?: () => void - updateProps?: (notificationProps: NotificationTipProps) => void + notificationProps?: NotificationTipProps; + resetProps?: () => void; + updateProps?: (notificationProps: NotificationTipProps) => void; } export const useNotificationLayoutEffect = (): useNotificationLayoutEffectInterface => { @@ -20,7 +20,7 @@ export const useNotificationLayoutEffect = (): useNotificationLayoutEffectInterf title: '', closeAutomatically: false, durationTimeout: DURATION.NOTIFICATION_TIME, - }) + }); const resetProps = () => { setNotificationProps(() => ({ @@ -28,25 +28,25 @@ export const useNotificationLayoutEffect = (): useNotificationLayoutEffectInterf title: '', closeAutomatically: false, durationTimeout: DURATION.NOTIFICATION_TIME, - })) - } + })); + }; const updateProps = (notificationProps: NotificationTipProps) => { - setNotificationProps(notificationProps) - } + setNotificationProps(notificationProps); + }; const closeAutomatically = () => { const durationTimeout = notificationProps.durationTimeout ? notificationProps.durationTimeout - : DURATION.NOTIFICATION_TIME + : DURATION.NOTIFICATION_TIME; window.setTimeout(() => { - resetProps() - }, durationTimeout) - } + resetProps(); + }, durationTimeout); + }; useEffect(() => { - notificationProps?.closeAutomatically && closeAutomatically() - }, [notificationProps]) + notificationProps?.closeAutomatically && closeAutomatically(); + }, [notificationProps]); - return { notificationProps, resetProps, updateProps } -} + return { notificationProps, resetProps, updateProps }; +}; diff --git a/frontend/src/hooks/useVerifyBoardEffect.ts b/frontend/src/hooks/useVerifyBoardEffect.ts index b5c3d06df5..9889874932 100644 --- a/frontend/src/hooks/useVerifyBoardEffect.ts +++ b/frontend/src/hooks/useVerifyBoardEffect.ts @@ -1,44 +1,44 @@ -import { useState } from 'react' -import { boardClient } from '@src/clients/board/BoardClient' -import { MESSAGE } from '@src/constants/resources' -import { BoardRequestDTO } from '@src/clients/board/dto/request' -import { DURATION } from '@src/constants/commons' +import { useState } from 'react'; +import { boardClient } from '@src/clients/board/BoardClient'; +import { MESSAGE } from '@src/constants/resources'; +import { BoardRequestDTO } from '@src/clients/board/dto/request'; +import { DURATION } from '@src/constants/commons'; export interface useVerifyBoardStateInterface { verifyJira: (params: BoardRequestDTO) => Promise< | { - isBoardVerify: boolean - haveDoneCard: boolean - response: object + isBoardVerify: boolean; + haveDoneCard: boolean; + response: object; } | undefined - > - isLoading: boolean - errorMessage: string + >; + isLoading: boolean; + errorMessage: string; } export const useVerifyBoardEffect = (): useVerifyBoardStateInterface => { - const [isLoading, setIsLoading] = useState(false) - const [errorMessage, setErrorMessage] = useState('') + const [isLoading, setIsLoading] = useState(false); + const [errorMessage, setErrorMessage] = useState(''); const verifyJira = async (params: BoardRequestDTO) => { - setIsLoading(true) + setIsLoading(true); try { - return await boardClient.getVerifyBoard(params) + return await boardClient.getVerifyBoard(params); } catch (e) { - const err = e as Error - setErrorMessage(`${params.type} ${MESSAGE.VERIFY_FAILED_ERROR}: ${err.message}`) + const err = e as Error; + setErrorMessage(`${params.type} ${MESSAGE.VERIFY_FAILED_ERROR}: ${err.message}`); setTimeout(() => { - setErrorMessage('') - }, DURATION.ERROR_MESSAGE_TIME) + setErrorMessage(''); + }, DURATION.ERROR_MESSAGE_TIME); } finally { - setIsLoading(false) + setIsLoading(false); } - } + }; return { verifyJira, isLoading, errorMessage, - } -} + }; +}; diff --git a/frontend/src/hooks/useVerifyPipelineToolEffect.ts b/frontend/src/hooks/useVerifyPipelineToolEffect.ts index b42ab1a6a3..7478681980 100644 --- a/frontend/src/hooks/useVerifyPipelineToolEffect.ts +++ b/frontend/src/hooks/useVerifyPipelineToolEffect.ts @@ -1,43 +1,43 @@ -import { useState } from 'react' -import { pipelineToolClient } from '@src/clients/pipeline/PipelineToolClient' -import { MESSAGE } from '@src/constants/resources' -import { PipelineRequestDTO } from '@src/clients/pipeline/dto/request' -import { DURATION } from '@src/constants/commons' +import { useState } from 'react'; +import { pipelineToolClient } from '@src/clients/pipeline/PipelineToolClient'; +import { MESSAGE } from '@src/constants/resources'; +import { PipelineRequestDTO } from '@src/clients/pipeline/dto/request'; +import { DURATION } from '@src/constants/commons'; export interface useVerifyPipeLineToolStateInterface { verifyPipelineTool: (params: PipelineRequestDTO) => Promise< | { - isPipelineToolVerified: boolean - response: object + isPipelineToolVerified: boolean; + response: object; } | undefined - > - isLoading: boolean - errorMessage: string + >; + isLoading: boolean; + errorMessage: string; } export const useVerifyPipelineToolEffect = (): useVerifyPipeLineToolStateInterface => { - const [isLoading, setIsLoading] = useState(false) - const [errorMessage, setErrorMessage] = useState('') + const [isLoading, setIsLoading] = useState(false); + const [errorMessage, setErrorMessage] = useState(''); const verifyPipelineTool = async (params: PipelineRequestDTO) => { - setIsLoading(true) + setIsLoading(true); try { - return await pipelineToolClient.verifyPipelineTool(params) + return await pipelineToolClient.verifyPipelineTool(params); } catch (e) { - const err = e as Error - setErrorMessage(`${params.type} ${MESSAGE.VERIFY_FAILED_ERROR}: ${err.message}`) + const err = e as Error; + setErrorMessage(`${params.type} ${MESSAGE.VERIFY_FAILED_ERROR}: ${err.message}`); setTimeout(() => { - setErrorMessage('') - }, DURATION.ERROR_MESSAGE_TIME) + setErrorMessage(''); + }, DURATION.ERROR_MESSAGE_TIME); } finally { - setIsLoading(false) + setIsLoading(false); } - } + }; return { verifyPipelineTool, isLoading, errorMessage, - } -} + }; +}; diff --git a/frontend/src/hooks/useVeritySourceControlEffect.ts b/frontend/src/hooks/useVeritySourceControlEffect.ts index a9797f7615..6431bd34a4 100644 --- a/frontend/src/hooks/useVeritySourceControlEffect.ts +++ b/frontend/src/hooks/useVeritySourceControlEffect.ts @@ -1,43 +1,43 @@ -import { useState } from 'react' -import { sourceControlClient } from '@src/clients/sourceControl/SourceControlClient' -import { MESSAGE } from '@src/constants/resources' -import { SourceControlRequestDTO } from '@src/clients/sourceControl/dto/request' -import { DURATION } from '@src/constants/commons' +import { useState } from 'react'; +import { sourceControlClient } from '@src/clients/sourceControl/SourceControlClient'; +import { MESSAGE } from '@src/constants/resources'; +import { SourceControlRequestDTO } from '@src/clients/sourceControl/dto/request'; +import { DURATION } from '@src/constants/commons'; export interface useVerifySourceControlStateInterface { verifyGithub: (params: SourceControlRequestDTO) => Promise< | { - isSourceControlVerify: boolean - response: object + isSourceControlVerify: boolean; + response: object; } | undefined - > - isLoading: boolean - errorMessage: string + >; + isLoading: boolean; + errorMessage: string; } export const useVerifySourceControlEffect = (): useVerifySourceControlStateInterface => { - const [isLoading, setIsLoading] = useState(false) - const [errorMessage, setErrorMessage] = useState('') + const [isLoading, setIsLoading] = useState(false); + const [errorMessage, setErrorMessage] = useState(''); const verifyGithub = async (params: SourceControlRequestDTO) => { - setIsLoading(true) + setIsLoading(true); try { - return await sourceControlClient.getVerifySourceControl(params) + return await sourceControlClient.getVerifySourceControl(params); } catch (e) { - const err = e as Error - setErrorMessage(`${params.type} ${MESSAGE.VERIFY_FAILED_ERROR}: ${err.message}`) + const err = e as Error; + setErrorMessage(`${params.type} ${MESSAGE.VERIFY_FAILED_ERROR}: ${err.message}`); setTimeout(() => { - setErrorMessage('') - }, DURATION.ERROR_MESSAGE_TIME) + setErrorMessage(''); + }, DURATION.ERROR_MESSAGE_TIME); } finally { - setIsLoading(false) + setIsLoading(false); } - } + }; return { verifyGithub, isLoading, errorMessage, - } -} + }; +}; diff --git a/frontend/src/layouts/Header.tsx b/frontend/src/layouts/Header.tsx index a2e9299f50..457bb830b8 100644 --- a/frontend/src/layouts/Header.tsx +++ b/frontend/src/layouts/Header.tsx @@ -1,7 +1,7 @@ -import { useLocation, useNavigate } from 'react-router-dom' -import Logo from '@src/assets/Logo.svg' +import { useLocation, useNavigate } from 'react-router-dom'; +import Logo from '@src/assets/Logo.svg'; -import { PROJECT_NAME } from '@src/constants/commons' +import { PROJECT_NAME } from '@src/constants/commons'; import { HomeIconContainer, HomeIconElement, @@ -13,43 +13,43 @@ import { NotificationIconContainer, StyledHeaderInfo, StyledVersion, -} from '@src/layouts/style' -import { NotificationButton } from '@src/components/Common/NotificationButton' -import { useNotificationLayoutEffectInterface } from '@src/hooks/useNotificationLayoutEffect' -import { useEffect } from 'react' -import { headerClient } from '@src/clients/header/HeaderClient' -import { useAppDispatch } from '@src/hooks/useAppDispatch' -import { getVersion, saveVersion } from '@src/context/header/headerSlice' -import { useAppSelector } from '@src/hooks' -import { isEmpty } from 'lodash' -import { resetImportedData } from '@src/context/config/configSlice' +} from '@src/layouts/style'; +import { NotificationButton } from '@src/components/Common/NotificationButton'; +import { useNotificationLayoutEffectInterface } from '@src/hooks/useNotificationLayoutEffect'; +import { useEffect } from 'react'; +import { headerClient } from '@src/clients/header/HeaderClient'; +import { useAppDispatch } from '@src/hooks/useAppDispatch'; +import { getVersion, saveVersion } from '@src/context/header/headerSlice'; +import { useAppSelector } from '@src/hooks'; +import { isEmpty } from 'lodash'; +import { resetImportedData } from '@src/context/config/configSlice'; const Header = (props: useNotificationLayoutEffectInterface) => { - const location = useLocation() - const navigate = useNavigate() - const dispatch = useAppDispatch() - const version = useAppSelector(getVersion) + const location = useLocation(); + const navigate = useNavigate(); + const dispatch = useAppDispatch(); + const version = useAppSelector(getVersion); const goHome = () => { - dispatch(resetImportedData()) - navigate('/') - } + dispatch(resetImportedData()); + navigate('/'); + }; const shouldShowHomeIcon = () => { - return ['/metrics', '/error-page'].includes(location.pathname) - } + return ['/metrics', '/error-page'].includes(location.pathname); + }; const shouldShowNotificationIcon = () => { - return ['/metrics'].includes(location.pathname) - } + return ['/metrics'].includes(location.pathname); + }; useEffect(() => { if (isEmpty(version)) { headerClient.getVersion().then((res) => { - dispatch(saveVersion(res)) - }) + dispatch(saveVersion(res)); + }); } - }, []) + }, []); return ( @@ -73,6 +73,6 @@ const Header = (props: useNotificationLayoutEffectInterface) => { )} - ) -} -export default Header + ); +}; +export default Header; diff --git a/frontend/src/layouts/style.tsx b/frontend/src/layouts/style.tsx index e052116de8..f2b94a23b6 100644 --- a/frontend/src/layouts/style.tsx +++ b/frontend/src/layouts/style.tsx @@ -1,7 +1,7 @@ -import styled from '@emotion/styled' -import { theme } from '@src/theme' -import HomeIcon from '@mui/icons-material/Home' -import { Z_INDEX } from '@src/constants/commons' +import styled from '@emotion/styled'; +import { theme } from '@src/theme'; +import HomeIcon from '@mui/icons-material/Home'; +import { Z_INDEX } from '@src/constants/commons'; export const LogoWarp = styled.div({ display: 'flex', @@ -13,18 +13,18 @@ export const LogoWarp = styled.div({ zIndex: Z_INDEX.STICKY, position: 'sticky', top: 0, -}) +}); export const LogoTitle = styled.span({ color: theme.main.color, fontWeight: 'bold', fontSize: '1.5rem', -}) +}); export const LogoImage = styled.img({ height: '4rem', width: '4rem', -}) +}); export const LogoContainer = styled.div({ display: 'flex', @@ -32,39 +32,39 @@ export const LogoContainer = styled.div({ cursor: 'pointer', color: theme.main.color, marginRight: '1rem', -}) +}); export const StyledHeaderInfo = styled.div({ display: 'flex', alignItems: 'center', color: theme.main.color, -}) +}); export const StyledVersion = styled.div({ color: '#ddd', fontSize: '0.8rem', paddingTop: '0.5rem', -}) +}); export const IconContainer = styled.div({ display: 'inherit', color: theme.main.color, -}) +}); export const HomeIconContainer = styled.span` cursor: pointer; -` +`; export const HomeIconElement = styled(HomeIcon)` color: ${theme.main.color}; -` +`; export const NotificationIconContainer = styled.span({ left: 0, -}) +}); export const ellipsisProps: { [key: string]: string | number } = { textOverflow: 'ellipsis', overflow: 'hidden', whiteSpace: 'nowrap', -} +}; diff --git a/frontend/src/main.tsx b/frontend/src/main.tsx index 406dc51a4b..13943a8814 100644 --- a/frontend/src/main.tsx +++ b/frontend/src/main.tsx @@ -1,10 +1,10 @@ -import React from 'react' -import ReactDOM from 'react-dom/client' -import App from './App' -import { Provider } from 'react-redux' -import { store } from './store' -import { ThemeProvider } from '@mui/material' -import { theme } from '@src/theme' +import React from 'react'; +import ReactDOM from 'react-dom/client'; +import App from './App'; +import { Provider } from 'react-redux'; +import { store } from './store'; +import { ThemeProvider } from '@mui/material'; +import { theme } from '@src/theme'; ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render( @@ -14,4 +14,4 @@ ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render( -) +); diff --git a/frontend/src/pages/ErrorPage.tsx b/frontend/src/pages/ErrorPage.tsx index 1f3f99eabd..7104cc35f3 100644 --- a/frontend/src/pages/ErrorPage.tsx +++ b/frontend/src/pages/ErrorPage.tsx @@ -1,6 +1,6 @@ -import React from 'react' -import Header from '@src/layouts/Header' -import { ErrorContent } from '@src/components/ErrorContent' +import React from 'react'; +import Header from '@src/layouts/Header'; +import { ErrorContent } from '@src/components/ErrorContent'; const ErrorPage = () => { return ( @@ -8,6 +8,6 @@ const ErrorPage = () => {

    - ) -} -export default ErrorPage + ); +}; +export default ErrorPage; diff --git a/frontend/src/pages/Home.tsx b/frontend/src/pages/Home.tsx index c4520a3941..30aaf478d5 100644 --- a/frontend/src/pages/Home.tsx +++ b/frontend/src/pages/Home.tsx @@ -1,7 +1,7 @@ -import React from 'react' -import Header from '@src/layouts/Header' -import { ProjectDescription } from '@src/components/ProjectDescription' -import { HomeGuide } from '@src/components/HomeGuide' +import React from 'react'; +import Header from '@src/layouts/Header'; +import { ProjectDescription } from '@src/components/ProjectDescription'; +import { HomeGuide } from '@src/components/HomeGuide'; const Home = () => { return ( @@ -10,6 +10,6 @@ const Home = () => { - ) -} -export default Home + ); +}; +export default Home; diff --git a/frontend/src/pages/Metrics.tsx b/frontend/src/pages/Metrics.tsx index 202e239c47..c8a123c1bb 100644 --- a/frontend/src/pages/Metrics.tsx +++ b/frontend/src/pages/Metrics.tsx @@ -1,10 +1,10 @@ -import Header from '@src/layouts/Header' -import MetricsStepper from '@src/components/Metrics/MetricsStepper' -import { ContextProvider } from '@src/hooks/useMetricsStepValidationCheckContext' -import { useNotificationLayoutEffect } from '@src/hooks/useNotificationLayoutEffect' +import Header from '@src/layouts/Header'; +import MetricsStepper from '@src/components/Metrics/MetricsStepper'; +import { ContextProvider } from '@src/hooks/useMetricsStepValidationCheckContext'; +import { useNotificationLayoutEffect } from '@src/hooks/useNotificationLayoutEffect'; const Metrics = () => { - const props = useNotificationLayoutEffect() + const props = useNotificationLayoutEffect(); return ( <> @@ -13,7 +13,7 @@ const Metrics = () => { - ) -} + ); +}; -export default Metrics +export default Metrics; diff --git a/frontend/src/router.tsx b/frontend/src/router.tsx index 6ba7e22b11..43cc90a3b8 100644 --- a/frontend/src/router.tsx +++ b/frontend/src/router.tsx @@ -1,17 +1,17 @@ -import { Suspense } from 'react' -import { Route, Routes } from 'react-router-dom' -import { routes } from './config/routes' -import { Loading } from './components/Loading' +import { Suspense } from 'react'; +import { Route, Routes } from 'react-router-dom'; +import { routes } from './config/routes'; +import { Loading } from './components/Loading'; const Router = () => { const appRoutes = routes.map((item) => { - return } /> - }) + return } />; + }); return ( }> {appRoutes} - ) -} + ); +}; -export default Router +export default Router; diff --git a/frontend/src/store.ts b/frontend/src/store.ts index 385fba18a7..48146f68fb 100644 --- a/frontend/src/store.ts +++ b/frontend/src/store.ts @@ -1,8 +1,8 @@ -import { configureStore } from '@reduxjs/toolkit' -import stepperReducer from './context/stepper/StepperSlice' -import configReducer from './context/config/configSlice' -import metricsSlice from './context/Metrics/metricsSlice' -import headerSlice from '@src/context/header/headerSlice' +import { configureStore } from '@reduxjs/toolkit'; +import stepperReducer from './context/stepper/StepperSlice'; +import configReducer from './context/config/configSlice'; +import metricsSlice from './context/Metrics/metricsSlice'; +import headerSlice from '@src/context/header/headerSlice'; export const store = configureStore({ reducer: { @@ -11,7 +11,7 @@ export const store = configureStore({ metrics: metricsSlice, header: headerSlice, }, -}) +}); -export type RootState = ReturnType -export type AppDispatch = typeof store.dispatch +export type RootState = ReturnType; +export type AppDispatch = typeof store.dispatch; diff --git a/frontend/src/theme.ts b/frontend/src/theme.ts index dc18bbbd9c..c1d3bc05e4 100644 --- a/frontend/src/theme.ts +++ b/frontend/src/theme.ts @@ -1,65 +1,65 @@ -import { createTheme } from '@mui/material/styles' -import { indigo } from '@mui/material/colors' -import { FIVE_HUNDRED } from '@src/constants/commons' -import '@fontsource/roboto' +import { createTheme } from '@mui/material/styles'; +import { indigo } from '@mui/material/colors'; +import { FIVE_HUNDRED } from '@src/constants/commons'; +import '@fontsource/roboto'; declare module '@mui/material/styles' { interface Theme { main: { - backgroundColor: string - color: string - secondColor: string - fontSize: string - boxShadow: string - cardShadow: string - cardBorder: string + backgroundColor: string; + color: string; + secondColor: string; + fontSize: string; + boxShadow: string; + cardShadow: string; + cardBorder: string; font: { - primary: string - secondary: string - } + primary: string; + secondary: string; + }; button: { disabled: { - backgroundColor: string - color: string - } - } - } + backgroundColor: string; + color: string; + }; + }; + }; } // allow configuration using `createTheme` interface ThemeOptions { main: { - backgroundColor: string - color: string - secondColor: string - fontSize: string - boxShadow: string - cardShadow: string - cardBorder: string + backgroundColor: string; + color: string; + secondColor: string; + fontSize: string; + boxShadow: string; + cardShadow: string; + cardBorder: string; font: { - primary: string - secondary: string - } + primary: string; + secondary: string; + }; button: { disabled: { - backgroundColor: string - color: string - } - } - } + backgroundColor: string; + color: string; + }; + }; + }; } interface Components { errorMessage: { - color: string - paddingBottom: string - } + color: string; + paddingBottom: string; + }; waringMessage: { - color: string - } + color: string; + }; tip: { - color: string - } + color: string; + }; } } @@ -125,4 +125,4 @@ export const theme = createTheme({ color: '#ED6D03CC', }, }, -}) +}); diff --git a/frontend/src/utils/types.ts b/frontend/src/utils/types.ts new file mode 100644 index 0000000000..59dcebe9eb --- /dev/null +++ b/frontend/src/utils/types.ts @@ -0,0 +1,2 @@ +export type Nullable = T | null; +export type Optional = T | null | undefined; diff --git a/frontend/src/utils/util.ts b/frontend/src/utils/util.ts index 851eddc876..0e06502f06 100644 --- a/frontend/src/utils/util.ts +++ b/frontend/src/utils/util.ts @@ -1,46 +1,46 @@ -import { CleanedBuildKiteEmoji, OriginBuildKiteEmoji } from '@src/emojis/emoji' -import { ICycleTimeSetting, IJiraColumnsWithValue } from '@src/context/Metrics/metricsSlice' -import dayjs from 'dayjs' -import { DATE_FORMAT_TEMPLATE } from '@src/constants/template' -import duration from 'dayjs/plugin/duration' -dayjs.extend(duration) +import { CleanedBuildKiteEmoji, OriginBuildKiteEmoji } from '@src/emojis/emoji'; +import { ICycleTimeSetting, IJiraColumnsWithValue } from '@src/context/Metrics/metricsSlice'; +import dayjs from 'dayjs'; +import { DATE_FORMAT_TEMPLATE } from '@src/constants/template'; +import duration from 'dayjs/plugin/duration'; +dayjs.extend(duration); export const exportToJsonFile = (filename: string, json: object) => { - const dataStr = JSON.stringify(json, null, 4) - const dataUri = `data:application/json;charset=utf-8,${encodeURIComponent(dataStr)}` - const exportFileDefaultName = `${filename}.json` + const dataStr = JSON.stringify(json, null, 4); + const dataUri = `data:application/json;charset=utf-8,${encodeURIComponent(dataStr)}`; + const exportFileDefaultName = `${filename}.json`; - const linkElement = document.createElement('a') - linkElement.setAttribute('href', dataUri) - linkElement.setAttribute('download', exportFileDefaultName) - linkElement.click() -} + const linkElement = document.createElement('a'); + linkElement.setAttribute('href', dataUri); + linkElement.setAttribute('download', exportFileDefaultName); + linkElement.click(); +}; export const downloadCSV = (filename: string, data: string) => { - const blob = new Blob([data], { type: 'application/octet-stream' }) - const url = window.URL.createObjectURL(blob) - const link = document.createElement('a') - link.href = url - link.setAttribute('download', filename) - document.body.appendChild(link) - link.click() - document.body.removeChild(link) -} + const blob = new Blob([data], { type: 'application/octet-stream' }); + const url = window.URL.createObjectURL(blob); + const link = document.createElement('a'); + link.href = url; + link.setAttribute('download', filename); + document.body.appendChild(link); + link.click(); + document.body.removeChild(link); +}; export const transformToCleanedBuildKiteEmoji = (input: OriginBuildKiteEmoji[]): CleanedBuildKiteEmoji[] => input.map(({ name, image, aliases }) => ({ image, aliases: [...new Set([...aliases, name])], - })) + })); export const getJiraBoardToken = (token: string, email: string) => { if (token) { - const encodedMsg = btoa(`${email}:${token}`) - return `Basic ${encodedMsg}` + const encodedMsg = btoa(`${email}:${token}`); + return `Basic ${encodedMsg}`; } else { - return '' + return ''; } -} +}; export const filterAndMapCycleTimeSettings = ( cycleTimeSettings: ICycleTimeSetting[], @@ -49,29 +49,29 @@ export const filterAndMapCycleTimeSettings = ( return cycleTimeSettings .filter((item) => item.value !== '----') .flatMap((cycleTimeSetting) => { - const previousName = cycleTimeSetting.name - const jiraColumnsStatuses = jiraColumnsWithValue.find((item) => item.name === previousName)?.statuses || [] + const previousName = cycleTimeSetting.name; + const jiraColumnsStatuses = jiraColumnsWithValue.find((item) => item.name === previousName)?.statuses || []; return jiraColumnsStatuses.map((item) => ({ name: item, value: cycleTimeSetting.value, - })) - }) -} + })); + }); +}; export const findCaseInsensitiveType = (option: string[], value: string): string => { - const newValue = option.find((item) => value.toLowerCase() === item.toLowerCase()) - return newValue ? newValue : value -} + const newValue = option.find((item) => value.toLowerCase() === item.toLowerCase()); + return newValue ? newValue : value; +}; export const formatDate = (date: Date | string) => { - return dayjs(date).format(DATE_FORMAT_TEMPLATE) -} + return dayjs(date).format(DATE_FORMAT_TEMPLATE); +}; export const formatMinToHours = (duration: number) => { - return dayjs.duration(duration, 'minutes').asHours() -} + return dayjs.duration(duration, 'minutes').asHours(); +}; export const formatMillisecondsToHours = (duration: number) => { - return dayjs.duration(duration, 'milliseconds').asHours() -} + return dayjs.duration(duration, 'milliseconds').asHours(); +}; diff --git a/frontend/src/vite-env.d.ts b/frontend/src/vite-env.d.ts index 09d5363f39..666ebff8c7 100644 --- a/frontend/src/vite-env.d.ts +++ b/frontend/src/vite-env.d.ts @@ -1,7 +1,7 @@ /// declare module '*.svg' { - const content: unknown - export const ReactComponent: unknown - export default content + const content: unknown; + export const ReactComponent: unknown; + export default content; } diff --git a/frontend/vite.config.ts b/frontend/vite.config.ts index 25a4be21bb..09b54b4cc9 100644 --- a/frontend/vite.config.ts +++ b/frontend/vite.config.ts @@ -1,7 +1,7 @@ -import { defineConfig } from 'vite' -import react from '@vitejs/plugin-react-swc' -import { resolve } from 'path/posix' -import { VitePWA } from 'vite-plugin-pwa' +import { defineConfig } from 'vite'; +import react from '@vitejs/plugin-react-swc'; +import { resolve } from 'path/posix'; +import { VitePWA } from 'vite-plugin-pwa'; export default defineConfig({ server: { @@ -62,4 +62,4 @@ export default defineConfig({ '@src': resolve(__dirname, './src'), }, }, -}) +});