├── .buildkite ├── hooks │ └── pre-exit └── pipeline.yml ├── .dockerignore ├── .editorconfig ├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.yml │ ├── config.yml │ └── feature_request.yml ├── auto-request-reviewer.yml ├── dependabot.yml ├── pull_request_template.md └── workflows │ ├── Docs.yaml │ ├── Release.yaml │ ├── Welcome.yml │ ├── auto-request-review.yml │ └── build-and-deploy.yml ├── .gitignore ├── .gitleaksignore ├── .trivyignore ├── LICENSE ├── README.md ├── README.zh-CN.md ├── backend ├── .gitignore ├── README.md ├── build.gradle ├── gradle │ └── wrapper │ │ ├── gradle-wrapper.jar │ │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── lombok.config ├── settings.gradle └── src │ ├── main │ ├── java │ │ └── heartbeat │ │ │ ├── HeartbeatApplication.java │ │ │ ├── client │ │ │ ├── BuildKiteFeignClient.java │ │ │ ├── GitHubFeignClient.java │ │ │ ├── HolidayFeignClient.java │ │ │ ├── JiraFeignClient.java │ │ │ ├── component │ │ │ │ ├── JiraUriGenerator.java │ │ │ │ ├── MockJiraUriGenerator.java │ │ │ │ └── ProductJiraUriGenerator.java │ │ │ ├── decoder │ │ │ │ ├── BuildKiteFeignClientDecoder.java │ │ │ │ ├── GitHubFeignClientDecoder.java │ │ │ │ └── JiraFeignClientDecoder.java │ │ │ └── dto │ │ │ │ ├── board │ │ │ │ └── jira │ │ │ │ │ ├── AllCardsResponseDTO.java │ │ │ │ │ ├── Assignee.java │ │ │ │ │ ├── CardHistoryResponseDTO.java │ │ │ │ │ ├── DoneCardFields.java │ │ │ │ │ ├── FieldResponseDTO.java │ │ │ │ │ ├── HistoryDetail.java │ │ │ │ │ ├── HolidayDTO.java │ │ │ │ │ ├── HolidayResponseDTO.java │ │ │ │ │ ├── HolidaysResponseDTO.java │ │ │ │ │ ├── IssueField.java │ │ │ │ │ ├── Issuetype.java │ │ │ │ │ ├── JiraBoardConfigDTO.java │ │ │ │ │ ├── JiraBoardProject.java │ │ │ │ │ ├── JiraBoardVerifyDTO.java │ │ │ │ │ ├── JiraCard.java │ │ │ │ │ ├── JiraCardField.java │ │ │ │ │ ├── JiraCardWithFields.java │ │ │ │ │ ├── JiraColumn.java │ │ │ │ │ ├── JiraColumnConfig.java │ │ │ │ │ ├── JiraColumnStatus.java │ │ │ │ │ ├── Location.java │ │ │ │ │ ├── Project.java │ │ │ │ │ ├── Sprint.java │ │ │ │ │ ├── Status.java │ │ │ │ │ ├── StatusCategory.java │ │ │ │ │ └── StatusSelfDTO.java │ │ │ │ ├── codebase │ │ │ │ └── github │ │ │ │ │ ├── Author.java │ │ │ │ │ ├── Commit.java │ │ │ │ │ ├── CommitInfo.java │ │ │ │ │ ├── Committer.java │ │ │ │ │ ├── LeadTime.java │ │ │ │ │ ├── PipelineLeadTime.java │ │ │ │ │ └── PullRequestInfo.java │ │ │ │ └── pipeline │ │ │ │ └── buildkite │ │ │ │ ├── BuildKiteBuildInfo.java │ │ │ │ ├── BuildKiteBuildsRequest.java │ │ │ │ ├── BuildKiteJob.java │ │ │ │ ├── BuildKiteOrganizationsInfo.java │ │ │ │ ├── BuildKitePipelineDTO.java │ │ │ │ ├── BuildKiteTokenInfo.java │ │ │ │ ├── CreatedByDTO.java │ │ │ │ ├── DeployInfo.java │ │ │ │ ├── DeployTimes.java │ │ │ │ ├── EnvDTO.java │ │ │ │ ├── PageBuildKitePipelineInfoDTO.java │ │ │ │ ├── PageStepsInfoDto.java │ │ │ │ ├── ProviderDTO.java │ │ │ │ ├── ProviderSettingsDTO.java │ │ │ │ └── StepsDTO.java │ │ │ ├── config │ │ │ ├── CacheConfig.java │ │ │ ├── DataType.java │ │ │ ├── HolidayFeignClientConfiguration.java │ │ │ ├── SwaggerConfig.java │ │ │ ├── ThreadPoolConfig.java │ │ │ └── WebConfig.java │ │ │ ├── controller │ │ │ ├── HealthController.java │ │ │ ├── board │ │ │ │ ├── BoardController.java │ │ │ │ └── dto │ │ │ │ │ ├── request │ │ │ │ │ ├── BoardRequestParam.java │ │ │ │ │ ├── BoardType.java │ │ │ │ │ ├── BoardVerifyRequestParam.java │ │ │ │ │ ├── CardStepsEnum.java │ │ │ │ │ ├── RequestJiraBoardColumnSetting.java │ │ │ │ │ ├── ReworkTimesSetting.java │ │ │ │ │ └── StoryPointsAndCycleTimeRequest.java │ │ │ │ │ └── response │ │ │ │ │ ├── Assignee.java │ │ │ │ │ ├── BoardConfigDTO.java │ │ │ │ │ ├── CardCollection.java │ │ │ │ │ ├── CardCustomFieldKey.java │ │ │ │ │ ├── CardCycleTime.java │ │ │ │ │ ├── CardParent.java │ │ │ │ │ ├── CardParentField.java │ │ │ │ │ ├── ColumnValue.java │ │ │ │ │ ├── CycleTimeInfo.java │ │ │ │ │ ├── CycleTimeInfoDTO.java │ │ │ │ │ ├── Fields.java │ │ │ │ │ ├── FixVersion.java │ │ │ │ │ ├── IssueType.java │ │ │ │ │ ├── JiraCardCollection.java │ │ │ │ │ ├── JiraCardDTO.java │ │ │ │ │ ├── JiraColumnDTO.java │ │ │ │ │ ├── JiraProject.java │ │ │ │ │ ├── JiraVerifyResponse.java │ │ │ │ │ ├── Priority.java │ │ │ │ │ ├── Reporter.java │ │ │ │ │ ├── ReworkTimesInfo.java │ │ │ │ │ ├── Status.java │ │ │ │ │ ├── StatusChangedItem.java │ │ │ │ │ ├── StatusTimeStamp.java │ │ │ │ │ ├── StepsDay.java │ │ │ │ │ └── TargetField.java │ │ │ ├── crypto │ │ │ │ ├── CryptoController.java │ │ │ │ ├── request │ │ │ │ │ ├── DecryptRequest.java │ │ │ │ │ └── EncryptRequest.java │ │ │ │ └── response │ │ │ │ │ ├── DecryptResponse.java │ │ │ │ │ └── EncryptResponse.java │ │ │ ├── pipeline │ │ │ │ ├── PipelineController.java │ │ │ │ └── dto │ │ │ │ │ ├── request │ │ │ │ │ ├── DeploymentEnvironment.java │ │ │ │ │ ├── PipelineStepsParam.java │ │ │ │ │ ├── PipelineType.java │ │ │ │ │ └── TokenParam.java │ │ │ │ │ └── response │ │ │ │ │ ├── BuildKiteResponseDTO.java │ │ │ │ │ ├── Pipeline.java │ │ │ │ │ ├── PipelineStepsDTO.java │ │ │ │ │ └── PipelineTransformer.java │ │ │ ├── report │ │ │ │ ├── ReportController.java │ │ │ │ └── dto │ │ │ │ │ ├── request │ │ │ │ │ ├── BuildKiteSetting.java │ │ │ │ │ ├── CodebaseSetting.java │ │ │ │ │ ├── GenerateReportRequest.java │ │ │ │ │ ├── JiraBoardSetting.java │ │ │ │ │ ├── MetricEnum.java │ │ │ │ │ ├── MetricType.java │ │ │ │ │ └── ReportType.java │ │ │ │ │ └── response │ │ │ │ │ ├── AvgDeploymentFrequency.java │ │ │ │ │ ├── AvgDevChangeFailureRate.java │ │ │ │ │ ├── AvgDevMeanTimeToRecovery.java │ │ │ │ │ ├── AvgLeadTimeForChanges.java │ │ │ │ │ ├── BoardCSVConfig.java │ │ │ │ │ ├── BoardCSVConfigEnum.java │ │ │ │ │ ├── CallbackResponse.java │ │ │ │ │ ├── Classification.java │ │ │ │ │ ├── ClassificationNameValuePair.java │ │ │ │ │ ├── CycleTime.java │ │ │ │ │ ├── CycleTimeForSelectedStepItem.java │ │ │ │ │ ├── CycleTimeResult.java │ │ │ │ │ ├── DailyDeploymentCount.java │ │ │ │ │ ├── DeploymentDateCount.java │ │ │ │ │ ├── DeploymentFrequency.java │ │ │ │ │ ├── DeploymentFrequencyOfPipeline.java │ │ │ │ │ ├── DevChangeFailureRate.java │ │ │ │ │ ├── DevChangeFailureRateOfPipeline.java │ │ │ │ │ ├── DevMeanTimeToRecovery.java │ │ │ │ │ ├── DevMeanTimeToRecoveryOfPipeline.java │ │ │ │ │ ├── ErrorInfo.java │ │ │ │ │ ├── LeadTimeForChanges.java │ │ │ │ │ ├── LeadTimeForChangesOfPipelines.java │ │ │ │ │ ├── LeadTimeInfo.java │ │ │ │ │ ├── MetricsDataCompleted.java │ │ │ │ │ ├── PipelineCSVInfo.java │ │ │ │ │ ├── ReportMetricsError.java │ │ │ │ │ ├── ReportResponse.java │ │ │ │ │ ├── Rework.java │ │ │ │ │ ├── TotalTimeAndRecoveryTimes.java │ │ │ │ │ └── Velocity.java │ │ │ ├── source │ │ │ │ ├── SourceController.java │ │ │ │ ├── SourceType.java │ │ │ │ └── dto │ │ │ │ │ ├── SourceControlDTO.java │ │ │ │ │ └── VerifyBranchRequest.java │ │ │ └── version │ │ │ │ └── VersionController.java │ │ │ ├── exception │ │ │ ├── BadRequestException.java │ │ │ ├── BaseException.java │ │ │ ├── DecryptDataOrPasswordWrongException.java │ │ │ ├── EncryptDecryptProcessException.java │ │ │ ├── FileIOException.java │ │ │ ├── GenerateReportException.java │ │ │ ├── GithubRepoEmptyException.java │ │ │ ├── InternalServerErrorException.java │ │ │ ├── NoContentException.java │ │ │ ├── NotFoundException.java │ │ │ ├── PermissionDenyException.java │ │ │ ├── RequestFailedException.java │ │ │ ├── RestApiErrorResponse.java │ │ │ ├── RestResponseEntityExceptionHandler.java │ │ │ ├── ServiceUnavailableException.java │ │ │ └── UnauthorizedException.java │ │ │ ├── handler │ │ │ ├── AsyncExceptionHandler.java │ │ │ ├── AsyncMetricsDataHandler.java │ │ │ ├── AsyncReportRequestHandler.java │ │ │ └── base │ │ │ │ ├── AsyncDataBaseHandler.java │ │ │ │ ├── AsyncExceptionDTO.java │ │ │ │ └── FIleType.java │ │ │ ├── service │ │ │ ├── board │ │ │ │ └── jira │ │ │ │ │ ├── AssigneeFilterMethod.java │ │ │ │ │ ├── JiraColumnResult.java │ │ │ │ │ └── JiraService.java │ │ │ ├── codebase │ │ │ │ └── ICodebase.java │ │ │ ├── crypto │ │ │ │ └── EncryptDecryptService.java │ │ │ ├── pipeline │ │ │ │ └── buildkite │ │ │ │ │ ├── BuildKiteService.java │ │ │ │ │ └── CachePageService.java │ │ │ ├── report │ │ │ │ ├── BoardSheetGenerator.java │ │ │ │ ├── CSVFileGenerator.java │ │ │ │ ├── CSVFileNameEnum.java │ │ │ │ ├── GenerateReporterService.java │ │ │ │ ├── ICardFieldDisplayName.java │ │ │ │ ├── KanbanCsvService.java │ │ │ │ ├── KanbanService.java │ │ │ │ ├── MetricsDataDTO.java │ │ │ │ ├── PipelineService.java │ │ │ │ ├── ReportService.java │ │ │ │ ├── WorkDay.java │ │ │ │ ├── calculator │ │ │ │ │ ├── ClassificationCalculator.java │ │ │ │ │ ├── CycleTimeCalculator.java │ │ │ │ │ ├── DeploymentFrequencyCalculator.java │ │ │ │ │ ├── DevChangeFailureRateCalculator.java │ │ │ │ │ ├── LeadTimeForChangesCalculator.java │ │ │ │ │ ├── MeanToRecoveryCalculator.java │ │ │ │ │ ├── ReportGenerator.java │ │ │ │ │ ├── ReworkCalculator.java │ │ │ │ │ ├── VelocityCalculator.java │ │ │ │ │ └── model │ │ │ │ │ │ └── FetchedData.java │ │ │ │ └── scheduler │ │ │ │ │ └── DeleteExpireCSVScheduler.java │ │ │ └── source │ │ │ │ └── github │ │ │ │ ├── GitHubService.java │ │ │ │ └── model │ │ │ │ └── PipelineInfoOfRepository.java │ │ │ └── util │ │ │ ├── BoardUtil.java │ │ │ ├── DecimalUtil.java │ │ │ ├── EncryptDecryptUtil.java │ │ │ ├── ExceptionUtil.java │ │ │ ├── GithubUtil.java │ │ │ ├── IdUtil.java │ │ │ ├── MetricsUtil.java │ │ │ ├── SystemUtil.java │ │ │ ├── TimeUtil.java │ │ │ ├── TokenUtil.java │ │ │ └── ValueUtil.java │ └── resources │ │ ├── allowed-licenses.json │ │ ├── application-e2e.yml │ │ ├── application-local.yml │ │ ├── application.yml │ │ ├── log4j2.xml │ │ └── pmd.xml │ └── test │ ├── java │ └── heartbeat │ │ ├── TestFixtures.java │ │ ├── client │ │ ├── component │ │ │ ├── MockJiraUriGeneratorTest.java │ │ │ └── ProductJiraUriGeneratorTest.java │ │ └── dto │ │ │ └── pipeline │ │ │ └── buildkite │ │ │ └── BuildKiteBuildInfoTest.java │ │ ├── controller │ │ ├── HealthControllerTest.java │ │ ├── board │ │ │ ├── BoardConfigResponseFixture.java │ │ │ ├── BoardRequestFixture.java │ │ │ ├── JiraControllerTest.java │ │ │ └── dto │ │ │ │ ├── request │ │ │ │ ├── BoardTypeTest.java │ │ │ │ └── BoardVerifyRequestFixture.java │ │ │ │ └── response │ │ │ │ └── JiraCardDTOTest.java │ │ ├── crypto │ │ │ └── CryptoControllerTest.java │ │ ├── pipeline │ │ │ ├── BuildKiteControllerTest.java │ │ │ ├── buildKitePipelineInfoData.json │ │ │ ├── dto │ │ │ │ └── response │ │ │ │ │ ├── PipelineTransformerTest.java │ │ │ │ │ └── PipelineTypeTest.java │ │ │ └── pipelineInfoData.json │ │ ├── report │ │ │ ├── DataTypeTest.java │ │ │ ├── ReporterControllerTest.java │ │ │ ├── dto │ │ │ │ ├── request │ │ │ │ │ ├── GenerateReportRequestTest.java │ │ │ │ │ ├── MetricTypeTest.java │ │ │ │ │ └── ReportTypeTest.java │ │ │ │ └── response │ │ │ │ │ └── LeadTimeInfoTest.java │ │ │ ├── reportResponse.json │ │ │ └── request.json │ │ ├── source │ │ │ └── SourceControllerTest.java │ │ └── version │ │ │ └── VersionControllerTest.java │ │ ├── decoder │ │ ├── BuildKiteFeignClientDecoderTest.java │ │ ├── GitHubFeignClientDecoderTest.java │ │ ├── JiraFeignClientDecoderTest.java │ │ └── ResponseMockUtil.java │ │ ├── exception │ │ ├── CustomFeignClientException.java │ │ └── RestResponseEntityExceptionHandlerTest.java │ │ ├── handler │ │ ├── AsyncExceptionHandlerTest.java │ │ ├── AsyncMetricsDataHandlerTest.java │ │ ├── AsyncReportRequestHandlerTest.java │ │ └── base │ │ │ ├── AsyncDataBaseHandlerTest.java │ │ │ └── AsyncExceptionTest.java │ │ ├── service │ │ ├── crypto │ │ │ └── EncryptDecryptServiceTest.java │ │ ├── jira │ │ │ ├── JiraBoardConfigDTOFixture.java │ │ │ ├── JiraBoardVerifyDTOFixture.java │ │ │ └── JiraServiceTest.java │ │ ├── pipeline │ │ │ └── buildkite │ │ │ │ ├── BuildKiteServiceTest.java │ │ │ │ ├── CachePageServiceTest.java │ │ │ │ └── builder │ │ │ │ ├── BuildKiteBuildInfoBuilder.java │ │ │ │ ├── BuildKiteJobBuilder.java │ │ │ │ ├── DeployInfoBuilder.java │ │ │ │ ├── DeployTimesBuilder.java │ │ │ │ └── DeploymentEnvironmentBuilder.java │ │ ├── report │ │ │ ├── BoardCsvFixture.java │ │ │ ├── CSVFileGeneratorTest.java │ │ │ ├── ClassificationFixture.java │ │ │ ├── CycleTimeCalculatorTest.java │ │ │ ├── CycleTimeFixture.java │ │ │ ├── DeleteExpireCSVSchedulerTest.java │ │ │ ├── GenerateReporterServiceTest.java │ │ │ ├── KanbanCsvServiceTest.java │ │ │ ├── KanbanFixture.java │ │ │ ├── KanbanServiceTest.java │ │ │ ├── MeanToRecoveryCalculatorTest.java │ │ │ ├── MetricCsvFixture.java │ │ │ ├── PipelineCsvFixture.java │ │ │ ├── PipelineServiceTest.java │ │ │ ├── ReportServiceTest.java │ │ │ ├── ReworkCalculatorTest.java │ │ │ ├── ReworkFixture.java │ │ │ ├── VelocityCalculatorTest.java │ │ │ ├── WorkDayFixture.java │ │ │ ├── WorkDayTest.java │ │ │ └── calculator │ │ │ │ ├── CalculateDeploymentFrequencyTest.java │ │ │ │ ├── CalculateDevChangeFailureRateTest.java │ │ │ │ ├── CalculateLeadTimeForChangesTest.java │ │ │ │ ├── ClassificationCalculatorTest.java │ │ │ │ └── ReportGeneratorTest.java │ │ └── source │ │ │ └── github │ │ │ └── GithubServiceTest.java │ │ ├── tools │ │ └── TimeUtils.java │ │ └── util │ │ ├── BoardUtilTest.java │ │ ├── DecimalUtilTest.java │ │ ├── EncryptDecryptUtilTest.java │ │ ├── ExceptionUtilTest.java │ │ ├── GithubUtilTest.java │ │ ├── IdUtilTest.java │ │ ├── JsonFileReader.java │ │ ├── MetricsUtilTest.java │ │ ├── StatusChangedItemsListAndCycleTimeInfosListFixture.java │ │ ├── TimeUtilTest.java │ │ ├── TokenUtilTest.java │ │ └── ValueUtilTest.java │ └── resources │ └── fields.json ├── contribution.md ├── docs ├── .editorconfig ├── .eslintignore ├── .eslintrc.js ├── .gitignore ├── .npmrc ├── .nvmrc ├── .prettierignore ├── .prettierrc ├── astro.config.ts ├── integrations │ ├── astro-asides.ts │ ├── expressive-code.ts │ ├── sitemap.ts │ ├── syntax-highlighting-theme.ts │ └── utils │ │ ├── isMDX.ts │ │ └── makeComponentNode.ts ├── package.json ├── plugins │ ├── rehype-autolink-config.ts │ ├── rehype-i18n-autolink-headings.ts │ ├── rehype-optimize-static.ts │ ├── rehype-tasklist-enhancer.ts │ ├── remark-diagram.mjs │ └── remark-fallback-lang.ts ├── pnpm-lock.yaml ├── public │ ├── assets │ │ ├── arc.webp │ │ ├── full-logo-dark.png │ │ ├── full-logo-light.png │ │ ├── logomark-dark.png │ │ ├── logomark-light.png │ │ └── rays.webp │ ├── captions │ │ ├── en │ │ │ └── 197398760-8fd30eff-4d13-449d-a598-00a6a1ac4644.vtt │ │ └── pt-br │ │ │ └── 197398760-8fd30eff-4d13-449d-a598-00a6a1ac4644.vtt │ ├── default-og-image.png │ ├── favicon.ico │ ├── favicon.svg │ ├── houston_chef.webp │ ├── index.css │ ├── logos │ │ ├── alpine-js.svg │ │ ├── appwriteio.svg │ │ ├── astro-image.svg │ │ ├── aws.svg │ │ ├── buddy.svg │ │ ├── builderio.svg │ │ ├── buttercms.svg │ │ ├── caisy.svg │ │ ├── cleavr.svg │ │ ├── cloudcannon.svg │ │ ├── cloudflare-pages.svg │ │ ├── contentful.svg │ │ ├── cosmic.svg │ │ ├── create-react-app.svg │ │ ├── crystallize.svg │ │ ├── datocms.svg │ │ ├── decap-cms.svg │ │ ├── deno.svg │ │ ├── directus.svg │ │ ├── docker.svg │ │ ├── docusaurus.svg │ │ ├── edgio.svg │ │ ├── eleventy.svg │ │ ├── firebase.svg │ │ ├── flightcontrol.svg │ │ ├── frontmatter-cms.svg │ │ ├── gatsby.svg │ │ ├── ghost.png │ │ ├── gitbook.svg │ │ ├── github.svg │ │ ├── gitlab.svg │ │ ├── google-cloud.svg │ │ ├── gridsome.svg │ │ ├── heroku.svg │ │ ├── hugo.svg │ │ ├── hygraph.svg │ │ ├── jekyll.png │ │ ├── keystatic.svg │ │ ├── keystonejs.svg │ │ ├── kinsta.svg │ │ ├── kontent-ai.svg │ │ ├── lit.svg │ │ ├── markdoc.svg │ │ ├── mdx.svg │ │ ├── microcms.svg │ │ ├── microsoft-azure.svg │ │ ├── netlify.svg │ │ ├── nextjs.svg │ │ ├── node.svg │ │ ├── nuxtjs.svg │ │ ├── partytown.svg │ │ ├── payload.svg │ │ ├── pelican.svg │ │ ├── preact.svg │ │ ├── prefetch.svg │ │ ├── preprcms.svg │ │ ├── prismic.svg │ │ ├── react.svg │ │ ├── render.svg │ │ ├── sanity.svg │ │ ├── sitemap.svg │ │ ├── solid.svg │ │ ├── space.svg │ │ ├── spinal.svg │ │ ├── sst.svg │ │ ├── statamic.svg │ │ ├── storyblok.svg │ │ ├── strapi.svg │ │ ├── supabase.svg │ │ ├── surge.svg │ │ ├── svelte.svg │ │ ├── sveltekit.svg │ │ ├── tailwind.svg │ │ ├── tigris.svg │ │ ├── tina-cms.svg │ │ ├── vercel.svg │ │ ├── vue.svg │ │ ├── vuepress.png │ │ ├── wordpress.svg │ │ └── xata.svg │ ├── make-scrollable-code-focusable.js │ ├── theme.css │ ├── tutorial │ │ ├── check-list.svg │ │ ├── minimal.png │ │ ├── puzzle-piece.svg │ │ └── question-mark.svg │ └── videos │ │ └── stores-example.mp4 ├── scripts │ ├── add-language.mjs │ ├── docgen.mjs │ ├── error-docgen.mjs │ ├── generate-integration-pages.ts │ ├── generateAndUploadToAlgolia.ts │ ├── lib │ │ ├── filter-warnings.cjs │ │ ├── github-get.mjs │ │ ├── github-issues.mjs │ │ ├── linkcheck │ │ │ ├── base │ │ │ │ ├── base.ts │ │ │ │ ├── check.ts │ │ │ │ ├── issue.ts │ │ │ │ └── page.ts │ │ │ ├── checks │ │ │ │ ├── canonical-url.ts │ │ │ │ ├── good-link-label.ts │ │ │ │ ├── relative-url.ts │ │ │ │ ├── same-language.ts │ │ │ │ └── target-exists.ts │ │ │ └── steps │ │ │ │ ├── build-index.ts │ │ │ │ ├── find-issues.ts │ │ │ │ ├── optional-autofix.ts │ │ │ │ └── output-issues.ts │ │ ├── output.mjs │ │ └── translation-status │ │ │ ├── builder.ts │ │ │ ├── template.html │ │ │ ├── types.ts │ │ │ └── utils.ts │ ├── lint-linkcheck.ts │ ├── lint-slugcheck.mjs │ ├── translation-status.ts │ └── tuesday-bot.ts ├── src │ ├── components │ │ ├── Aside.astro │ │ ├── BackendGuidesNav.astro │ │ ├── Badge.astro │ │ ├── BrandLogo.astro │ │ ├── Button.astro │ │ ├── CMSGuidesNav.astro │ │ ├── Card.astro │ │ ├── Checklist.astro │ │ ├── ContributorList.astro │ │ ├── DeployGuidesNav.astro │ │ ├── DontEditWarning.astro │ │ ├── FacePile.astro │ │ ├── FallbackNotice.astro │ │ ├── FileTree.astro │ │ ├── Footer │ │ │ └── Footer.astro │ │ ├── HeadCommon.astro │ │ ├── HeadSEO.astro │ │ ├── Header │ │ │ ├── AstroLogo.astro │ │ │ ├── DocSearch.css │ │ │ ├── DocSearch.tsx │ │ │ ├── Header.astro │ │ │ ├── HeaderButton.css │ │ │ ├── LanguageSelect.css │ │ │ ├── LanguageSelect.tsx │ │ │ ├── Search.astro │ │ │ ├── SidebarToggle.css │ │ │ ├── SidebarToggle.tsx │ │ │ ├── SkipToContent.astro │ │ │ ├── ThemeToggleButton.css │ │ │ └── ThemeToggleButton.tsx │ │ ├── IntegrationsNav.astro │ │ ├── IslandsDiagram.astro │ │ ├── LeftSidebar │ │ │ ├── LeftSidebar.astro │ │ │ ├── SidebarContent.astro │ │ │ └── Sponsors.astro │ │ ├── LoopingVideo.astro │ │ ├── MigrationGuidesNav.astro │ │ ├── NavGrid │ │ │ ├── Card.astro │ │ │ ├── CardsNav.astro │ │ │ └── Grid.astro │ │ ├── PageContent │ │ │ ├── ArticleNavigationButton.astro │ │ │ └── PageContent.astro │ │ ├── RecipeLinks.astro │ │ ├── RecipesNav.astro │ │ ├── RightSidebar │ │ │ ├── CommunityMenu.astro │ │ │ ├── ContributeMenu.astro │ │ │ ├── EditButton.astro │ │ │ ├── RightSidebar.astro │ │ │ ├── TableOfContents.css │ │ │ └── TableOfContents.tsx │ │ ├── Since.astro │ │ ├── Spoiler.astro │ │ ├── TabBox.astro │ │ ├── TabGroup │ │ │ ├── InstallGuideTabGroup.astro │ │ │ ├── SidebarToggleTabGroup.tsx │ │ │ └── TabGroup.css │ │ ├── TranslatorList.astro │ │ ├── UIString.astro │ │ ├── Version.astro │ │ ├── Video.astro │ │ ├── internal │ │ │ ├── Spoiler.tsx │ │ │ ├── file-tree-icons.ts │ │ │ └── rehype-file-tree.ts │ │ ├── tabs │ │ │ ├── AstroJSXTabs.astro │ │ │ ├── AstroVueTabs.astro │ │ │ ├── JavascriptFlavorTabs.astro │ │ │ ├── PackageManagerTabs.astro │ │ │ ├── StaticSsrTabs.astro │ │ │ ├── TabListItem.astro │ │ │ ├── TabPanel.astro │ │ │ ├── TabbedContent.astro │ │ │ ├── Tabs.module.css │ │ │ ├── Tabs.tsx │ │ │ ├── TypeScriptSettingTabs.astro │ │ │ ├── UIFrameworkTabs.astro │ │ │ ├── store.ts │ │ │ └── useTabState.ts │ │ └── tutorial │ │ │ ├── Blanks.astro │ │ │ ├── Box.astro │ │ │ ├── CompletionConfetti.astro │ │ │ ├── FeedbackButton.astro │ │ │ ├── Lede.astro │ │ │ ├── MobileTutorialNav.astro │ │ │ ├── MultipleChoice.astro │ │ │ ├── Option.astro │ │ │ ├── PreCheck.astro │ │ │ ├── Progress.astro │ │ │ ├── ProgressStore.ts │ │ │ ├── RightSidebar.astro │ │ │ ├── TutorialNav.astro │ │ │ ├── UnitProgressIcon.astro │ │ │ ├── geometry.ts │ │ │ └── houston-happy.webp │ ├── config.ts │ ├── content.ts │ ├── content │ │ ├── config.ts │ │ └── docs │ │ │ └── en │ │ │ ├── arch │ │ │ └── architecture.mdx │ │ │ ├── biz │ │ │ └── business-context.mdx │ │ │ ├── commons │ │ │ └── useful-scripts-and-tools.mdx │ │ │ ├── contribute.mdx │ │ │ ├── designs │ │ │ ├── cycle-time-calculation.mdx │ │ │ ├── e2e-testing.mdx │ │ │ ├── emoji-flow.mdx │ │ │ ├── error-handle.mdx │ │ │ ├── export-csv.mdx │ │ │ ├── flow-diagrams.mdx │ │ │ ├── frontend-structure.mdx │ │ │ ├── optimize-generate-report.mdx │ │ │ ├── origin-cycle-time-calculation.mdx │ │ │ ├── refinement-on-generate-report.mdx │ │ │ ├── sequence-diagrams.mdx │ │ │ └── support-multiple-columns.mdx │ │ │ ├── devops │ │ │ └── how-to-deploy-heartbeat-in-multiple-instances-by-k8s.mdx │ │ │ ├── getting-started.mdx │ │ │ ├── guides │ │ │ ├── guideline-and-best-practices.mdx │ │ │ └── start-e2e-test-in-local.mdx │ │ │ ├── issue-solutions │ │ │ └── solution-of-buildKite-issue.mdx │ │ │ ├── onboarding │ │ │ ├── conventions.mdx │ │ │ ├── glossary.mdx │ │ │ ├── onboarding-flow.mdx │ │ │ └── way-of-working.mdx │ │ │ ├── spikes │ │ │ ├── tech-spikes-buildkite-graphql-api-about-replacing-existing-rest-api.mdx │ │ │ ├── tech-spikes-calculate-rework-of-board-card.mdx │ │ │ ├── tech-spikes-calculating-pipeline-metrics-with-selected-user.mdx │ │ │ ├── tech-spikes-chart-api-solutions.mdx │ │ │ ├── tech-spikes-chart-tools-solutions.mdx │ │ │ ├── tech-spikes-encrypt-decrypt-configuration.mdx │ │ │ ├── tech-spikes-export-all-metrics-button-polling-api-backend-change.mdx │ │ │ ├── tech-spikes-github-graphql-api-about-replacing-existing-rest-api.mdx │ │ │ ├── tech-spikes-impact-of-status-and-column-name-change.mdx │ │ │ ├── tech-spikes-jira-graphql-api-about-replacing-existing-rest-api.mdx │ │ │ ├── tech-spikes-react-hook-form.mdx │ │ │ ├── tech-spikes-split-verification-of-board.mdx │ │ │ ├── tech-spikes-split-verification-of-github.mdx │ │ │ ├── tech-spikes-split-verify-of-buildkite.mdx │ │ │ ├── tech-spikes-support-multiple-instances-for-backend-service.mdx │ │ │ └── tech-timezone-problem.mdx │ │ │ ├── teams │ │ │ ├── responsibilities-TL.mdx │ │ │ ├── team-activity-calendar.mdx │ │ │ └── team-infos.mdx │ │ │ └── tests │ │ │ └── test-strategies.mdx │ ├── data │ │ └── logos.ts │ ├── docs-logo.png │ ├── env.d.ts │ ├── i18n │ │ ├── bcp-normalize.ts │ │ ├── en │ │ │ ├── docsearch.ts │ │ │ ├── nav.ts │ │ │ └── ui.ts │ │ ├── languages.ts │ │ ├── translation-checkers.ts │ │ └── util.ts │ ├── layouts │ │ ├── BackendLayout.astro │ │ ├── BaseLayout.astro │ │ ├── CMSLayout.astro │ │ ├── DeployGuideLayout.astro │ │ ├── IntegrationLayout.astro │ │ ├── LayoutSwitcher.astro │ │ ├── MainLayout.astro │ │ ├── MigrationLayout.astro │ │ ├── RecipeLayout.astro │ │ ├── SplashLayout.astro │ │ └── TutorialLayout.astro │ ├── pages │ │ ├── 404.astro │ │ ├── [...enRedirectSlug].astro │ │ ├── [lang] │ │ │ ├── [...fallback].astro │ │ │ ├── [...slug].astro │ │ │ ├── index.astro │ │ │ ├── install.astro │ │ │ └── tutorial.astro │ │ ├── index.astro │ │ └── open-graph │ │ │ ├── [...path].ts │ │ │ └── _fonts │ │ │ ├── noto-sans │ │ │ ├── arabic-400-normal.ttf │ │ │ ├── arabic-800-normal.ttf │ │ │ ├── chinese-simplified-400-normal.otf │ │ │ ├── chinese-simplified-900-normal.otf │ │ │ ├── chinese-traditional-400-normal.otf │ │ │ ├── chinese-traditional-900-normal.otf │ │ │ ├── cyrillic-400-normal.ttf │ │ │ ├── cyrillic-900-normal.ttf │ │ │ ├── japanese-400-normal.ttf │ │ │ ├── japanese-900-normal.ttf │ │ │ ├── korean-400-normal.otf │ │ │ └── korean-900-normal.otf │ │ │ └── work-sans │ │ │ ├── latin-400-normal.ttf │ │ │ └── latin-800-normal.ttf │ ├── util-server.ts │ ├── util.ts │ └── util │ │ ├── generateToc.test.ts │ │ ├── generateToc.ts │ │ ├── getContributors.ts │ │ ├── getGithubEditUrl.ts │ │ ├── getNav.ts │ │ ├── getNavLinks.ts │ │ ├── getOgImageUrl.ts │ │ ├── getPageCategory.ts │ │ ├── getTutorialPages.ts │ │ ├── groupPagesByLang.ts │ │ ├── html-entities.ts │ │ └── isSubPage.ts └── tsconfig.json ├── frontend ├── .eslintrc.json ├── .gitignore ├── .husky │ ├── pre-commit │ ├── pre-push │ └── pre-tag ├── .license-compliance.json ├── .prettierignore ├── .prettierrc ├── README.md ├── __tests__ │ ├── App.test.tsx │ ├── __mocks__ │ │ └── svgTransformer.ts │ ├── client │ │ ├── BoardClient.test.ts │ │ ├── CSVClient.test.ts │ │ ├── HeaderClient.test.ts │ │ ├── MetricsClient.test.ts │ │ ├── PipelineToolClient.test.ts │ │ ├── ReportClient.test.ts │ │ └── SourceControlClient.test.ts │ ├── components │ │ ├── Common │ │ │ ├── DateRangeViewer │ │ │ │ └── DateRangeViewer.test.tsx │ │ │ ├── EllipsisText │ │ │ │ └── EllipsisText.test.tsx │ │ │ ├── EmptyContent │ │ │ │ └── EmptyContent.test.tsx │ │ │ ├── NotificationButton │ │ │ │ └── NotificationButton.test.tsx │ │ │ ├── ReportForThreeColumns.test.tsx │ │ │ └── ReportGrid │ │ │ │ └── ReportCard.test.tsx │ │ ├── DateDisplay │ │ │ └── CollectionDuration.test.tsx │ │ ├── ErrorContent │ │ │ └── index.test.tsx │ │ ├── ErrorNotification │ │ │ ├── ErrorNotification.test.tsx │ │ │ └── WarningNotification.test.tsx │ │ ├── HomeGuide │ │ │ └── HomeGuide.test.tsx │ │ ├── Loading │ │ │ └── Loading.test.tsx │ │ └── ProjectDescripution.test.tsx │ ├── constants │ │ ├── emojis │ │ │ └── emoji.test.tsx │ │ └── fileConfig │ │ │ └── fileConfig.test.ts │ ├── containers │ │ ├── ConfigStep │ │ │ ├── Board.test.tsx │ │ │ ├── ConfigButtonGroup.test.tsx │ │ │ ├── ConfigStep.test.tsx │ │ │ ├── DateRangePicker.test.tsx │ │ │ ├── MetricsTypeCheckbox.test.tsx │ │ │ ├── NoCardPop.test.tsx │ │ │ ├── PipelineTool.test.tsx │ │ │ ├── SourceControl.test.tsx │ │ │ └── TimeoutAlet.test.tsx │ │ ├── MetricsStep │ │ │ ├── Advance.test.tsx │ │ │ ├── Classification.test.tsx │ │ │ ├── Crews.test.tsx │ │ │ ├── CycleTime.test.tsx │ │ │ ├── DeploymentFrequencySettings │ │ │ │ ├── BranchSelection.test.tsx │ │ │ │ ├── DeploymentFrequencySettings.test.tsx │ │ │ │ ├── PipelineMetricSelection.test.tsx │ │ │ │ ├── PresentationForErrorCases.test.tsx │ │ │ │ └── SingleSelection.test.tsx │ │ │ ├── MetricsStep.test.tsx │ │ │ ├── MultiAutoComplete.test.tsx │ │ │ ├── RealDone.test.tsx │ │ │ ├── ReworkSettings │ │ │ │ ├── ReworkDialog.test.tsx │ │ │ │ └── SingleSelection.test.tsx │ │ │ ├── TokenAccessAlert.test.tsx │ │ │ └── reworkSettings.test.tsx │ │ ├── MetricsStepper │ │ │ ├── ConfirmDialog.test.tsx │ │ │ └── MetricsStepper.test.tsx │ │ ├── ReportButtonGroup.test.tsx │ │ └── ReportStep │ │ │ ├── BoardMetrics.test.tsx │ │ │ ├── DoraMetrics.test.tsx │ │ │ ├── ExpiredDialog.test.tsx │ │ │ ├── ReportDetail │ │ │ ├── board.test.tsx │ │ │ ├── dora.test.tsx │ │ │ └── withBack.test.tsx │ │ │ ├── ReportStep.test.tsx │ │ │ └── style.test.tsx │ ├── context │ │ ├── boardSlice.test.ts │ │ ├── configSlice.test.ts │ │ ├── metaSlice.test.ts │ │ ├── metricsSlice.test.ts │ │ ├── notificationSlice.test.ts │ │ ├── pipelineToolSlice.test.ts │ │ ├── sourceControlSlice.test.ts │ │ └── stepperSlice.test.ts │ ├── fixtures.ts │ ├── hooks │ │ ├── reportMapper │ │ │ ├── changeFailureRate.test.tsx │ │ │ ├── classfication.test.tsx │ │ │ ├── cycleTime.test.tsx │ │ │ ├── deploymentFrequency.test.tsx │ │ │ ├── exportValidityTime.test.tsx │ │ │ ├── leadTimeForChanges.test.tsx │ │ │ ├── meanTimeToRecovery.test.tsx │ │ │ ├── report.test.tsx │ │ │ └── velocity.test.tsx │ │ ├── useExportCsvEffect.test.tsx │ │ ├── useGenerateReportEffect.test.tsx │ │ ├── useGetBoardInfo.test.tsx │ │ ├── useGetMetricsStepsEffect.test.tsx │ │ ├── useGetPipelineToolInfoEffect.test.tsx │ │ ├── useMetricsStepValidationCheckContext.test.tsx │ │ ├── useVerifyBoardEffect.test.tsx │ │ ├── useVerifyPipelineToolEffect.test.tsx │ │ └── useVerifySourceControlTokenEffect.test.tsx │ ├── initialConfigState.ts │ ├── layouts │ │ └── Header.test.tsx │ ├── pages │ │ ├── Home.test.tsx │ │ └── Metrics.test.tsx │ ├── router.test.tsx │ ├── routerErrorBoundary.test.tsx │ ├── setupTests.ts │ ├── testUtils.ts │ ├── updatedConfigState.ts │ └── utils │ │ ├── Util.test.tsx │ │ └── setupStoreUtil.tsx ├── audit-ci.jsonc ├── e2e │ ├── .env.ci │ ├── .env.e2e │ ├── .env.example │ ├── .gitignore │ ├── fixtures │ │ ├── create-new │ │ │ ├── board-data.csv │ │ │ ├── config-step.ts │ │ │ ├── metric-data.csv │ │ │ ├── metrics-step.ts │ │ │ ├── pipeline-data.csv │ │ │ └── report-result.ts │ │ ├── cycle-time-by-status │ │ │ ├── board-data-by-status.csv │ │ │ ├── cycle-time-by-column-fixture.ts │ │ │ ├── cycle-time-by-status-fixture.ts │ │ │ ├── cycle-time-by-status-report-result.ts │ │ │ └── metric-data-by-status.csv │ │ ├── import-file │ │ │ ├── board-data.csv │ │ │ ├── metric-data.csv │ │ │ ├── multiple-done-config-file.ts │ │ │ ├── pipeline-data.csv │ │ │ └── unhappy-path-file.ts │ │ ├── index.ts │ │ ├── input-files │ │ │ ├── add-flag-as-block-config-file.template.json │ │ │ ├── cycle-time-by-status-config-file.template.json │ │ │ ├── hb-e2e-for-importing-file.template.json │ │ │ ├── multiple-done-config-file.template.json │ │ │ ├── pipeline-no-org-config-file.template.json │ │ │ └── unhappy-path-config-file.template.json │ │ └── test-with-extend-fixtures.ts │ ├── pages │ │ ├── Home.ts │ │ └── metrics │ │ │ ├── config-step.ts │ │ │ ├── metrics-step.ts │ │ │ └── report-step.ts │ ├── specs │ │ ├── major-path │ │ │ ├── create-a-new-project.spec.ts │ │ │ ├── cycle-time-by-status-test.spec.ts │ │ │ ├── import-project-from-file.spec.ts │ │ │ └── page-jumps.spec.ts │ │ └── side-path │ │ │ ├── config-select-one-metrics.spec.ts │ │ │ └── unhappy-path.spec.ts │ └── utils │ │ ├── clear-temp-dir.ts │ │ ├── date-time.ts │ │ └── download.ts ├── global-setup.ts ├── index.html ├── jest.config.json ├── package.json ├── playwright.config.ts ├── pnpm-lock.yaml ├── public │ ├── android-chrome-192x192.png │ ├── android-chrome-512x512.png │ ├── apple-touch-icon.png │ ├── browserconfig.xml │ ├── favicon-16x16.png │ ├── favicon-32x32.png │ ├── favicon.ico │ ├── mstile-150x150.png │ ├── safari-pinned-tab.svg │ └── site.webmanifest ├── scripts │ ├── generate-config-files.sh │ └── runE2eWithServer.ts ├── src │ ├── App.css │ ├── App.tsx │ ├── assets │ │ ├── DividingLine.svg │ │ ├── EmptyBox.svg │ │ ├── ErrorIcon.svg │ │ ├── Logo.svg │ │ ├── PipelineInfoError.svg │ │ ├── ReworkSelectedInDev.svg │ │ ├── ReworkSelectedWaitingForTest.svg │ │ ├── StepOfExcludeJira.svg │ │ ├── StepOfReworkJira.svg │ │ ├── appleEmojis.json │ │ └── buildkiteEmojis.json │ ├── clients │ │ ├── HttpClient.ts │ │ ├── MetricsClient.ts │ │ ├── board │ │ │ ├── BoardClient.ts │ │ │ ├── BoardInfoClient.ts │ │ │ └── dto │ │ │ │ └── request.ts │ │ ├── header │ │ │ ├── HeaderClient.ts │ │ │ └── dto │ │ │ │ └── request.ts │ │ ├── pipeline │ │ │ ├── PipelineToolClient.ts │ │ │ └── dto │ │ │ │ ├── request.ts │ │ │ │ └── response.ts │ │ ├── report │ │ │ ├── CSVClient.ts │ │ │ ├── ReportClient.ts │ │ │ └── dto │ │ │ │ ├── request.ts │ │ │ │ └── response.ts │ │ └── sourceControl │ │ │ ├── SourceControlClient.ts │ │ │ └── dto │ │ │ └── request.ts │ ├── components │ │ ├── Common │ │ │ ├── AddButtonOneLine │ │ │ │ ├── index.tsx │ │ │ │ └── style.tsx │ │ │ ├── BoldText │ │ │ │ ├── index.tsx │ │ │ │ └── style.tsx │ │ │ ├── Buttons.ts │ │ │ ├── ChipExtended │ │ │ │ ├── index.tsx │ │ │ │ └── style.tsx │ │ │ ├── CollectionDuration │ │ │ │ ├── index.tsx │ │ │ │ └── style.tsx │ │ │ ├── ConfigForms.ts │ │ │ ├── DateRangeViewer │ │ │ │ ├── index.tsx │ │ │ │ └── style.tsx │ │ │ ├── EllipsisText │ │ │ │ ├── index.tsx │ │ │ │ └── style.tsx │ │ │ ├── EmptyContent │ │ │ │ ├── index.tsx │ │ │ │ └── styles.tsx │ │ │ ├── MetricsSettingTitle │ │ │ │ ├── index.tsx │ │ │ │ └── style.tsx │ │ │ ├── MultiAutoComplete │ │ │ │ ├── index.tsx │ │ │ │ └── styles.ts │ │ │ ├── NotificationButton │ │ │ │ ├── index.tsx │ │ │ │ └── style.tsx │ │ │ ├── ReportForThreeColumns │ │ │ │ └── index.tsx │ │ │ ├── ReportForTwoColumns │ │ │ │ ├── index.tsx │ │ │ │ └── style.tsx │ │ │ ├── ReportGrid │ │ │ │ ├── ReportCard │ │ │ │ │ ├── index.tsx │ │ │ │ │ └── style.tsx │ │ │ │ ├── ReportCardItem │ │ │ │ │ ├── index.tsx │ │ │ │ │ └── style.tsx │ │ │ │ ├── ReportTitle │ │ │ │ │ ├── index.tsx │ │ │ │ │ └── style.tsx │ │ │ │ └── index.tsx │ │ │ ├── SectionTitleWithTooltip │ │ │ │ ├── index.tsx │ │ │ │ ├── style.tsx │ │ │ │ └── types.ts │ │ │ └── WarningNotification │ │ │ │ ├── index.tsx │ │ │ │ └── style.tsx │ │ ├── ErrorContent │ │ │ ├── index.tsx │ │ │ └── style.tsx │ │ ├── ErrorMessagePrompt │ │ │ ├── index.tsx │ │ │ └── style.tsx │ │ ├── ErrorNotification │ │ │ ├── index.tsx │ │ │ └── style.tsx │ │ ├── HomeGuide │ │ │ ├── index.tsx │ │ │ └── style.tsx │ │ ├── Loading │ │ │ ├── index.tsx │ │ │ └── style.tsx │ │ ├── Metrics │ │ │ └── MetricsStep │ │ │ │ └── DeploymentFrequencySettings │ │ │ │ └── PresentationForErrorCases │ │ │ │ ├── index.tsx │ │ │ │ └── style.tsx │ │ └── ProjectDescription.tsx │ ├── config │ │ └── routes.ts │ ├── constants │ │ ├── commons.ts │ │ ├── emojis │ │ │ ├── emoji.ts │ │ │ └── style.tsx │ │ ├── fileConfig.ts │ │ ├── regex.ts │ │ ├── resources.ts │ │ ├── router.ts │ │ └── template.ts │ ├── containers │ │ ├── ConfigStep │ │ │ ├── BasicInfo │ │ │ │ ├── RequiredMetrics │ │ │ │ │ ├── index.tsx │ │ │ │ │ └── style.tsx │ │ │ │ ├── index.tsx │ │ │ │ └── style.tsx │ │ │ ├── Board │ │ │ │ └── index.tsx │ │ │ ├── ConfigButton │ │ │ │ └── index.tsx │ │ │ ├── DateRangePicker │ │ │ │ ├── DateRangePicker.tsx │ │ │ │ ├── DateRangePickerGroup.tsx │ │ │ │ ├── index.tsx │ │ │ │ ├── style.tsx │ │ │ │ ├── types.ts │ │ │ │ └── validation.ts │ │ │ ├── MetricsTypeCheckbox │ │ │ │ └── index.tsx │ │ │ ├── NoDoneCardPop │ │ │ │ ├── index.tsx │ │ │ │ └── style.tsx │ │ │ ├── PipelineTool │ │ │ │ └── index.tsx │ │ │ ├── SourceControl │ │ │ │ └── index.tsx │ │ │ ├── TimeoutAlert │ │ │ │ ├── index.tsx │ │ │ │ └── style.tsx │ │ │ ├── index.tsx │ │ │ └── style.tsx │ │ ├── MetricsStep │ │ │ ├── Advance │ │ │ │ ├── Advance.tsx │ │ │ │ └── style.tsx │ │ │ ├── Classification │ │ │ │ └── index.tsx │ │ │ ├── Crews │ │ │ │ ├── AssigneeFilter.tsx │ │ │ │ ├── index.tsx │ │ │ │ └── style.tsx │ │ │ ├── CycleTime │ │ │ │ ├── FlagCard.tsx │ │ │ │ ├── Table │ │ │ │ │ ├── CellAutoComplete.tsx │ │ │ │ │ ├── index.tsx │ │ │ │ │ └── style.tsx │ │ │ │ ├── index.tsx │ │ │ │ └── style.tsx │ │ │ ├── DeploymentFrequencySettings │ │ │ │ ├── BranchSelection │ │ │ │ │ ├── BranchChip │ │ │ │ │ │ └── index.tsx │ │ │ │ │ └── index.tsx │ │ │ │ ├── PipelineMetricSelection │ │ │ │ │ ├── index.tsx │ │ │ │ │ └── style.tsx │ │ │ │ ├── SingleSelection │ │ │ │ │ ├── index.tsx │ │ │ │ │ └── style.tsx │ │ │ │ └── index.tsx │ │ │ ├── RealDone │ │ │ │ ├── index.tsx │ │ │ │ └── style.tsx │ │ │ ├── ReworkSettings │ │ │ │ ├── ReworkDialog │ │ │ │ │ ├── index.tsx │ │ │ │ │ └── style.tsx │ │ │ │ ├── SingleSelection │ │ │ │ │ ├── index.tsx │ │ │ │ │ └── style.tsx │ │ │ │ ├── index.tsx │ │ │ │ └── style.tsx │ │ │ ├── TokenAccessAlert │ │ │ │ ├── index.tsx │ │ │ │ └── style.tsx │ │ │ ├── index.tsx │ │ │ └── style.tsx │ │ ├── MetricsStepper │ │ │ ├── ConfirmDialog │ │ │ │ ├── index.tsx │ │ │ │ └── style.tsx │ │ │ ├── index.tsx │ │ │ └── style.tsx │ │ ├── ReportButtonGroup │ │ │ ├── index.tsx │ │ │ └── style.tsx │ │ └── ReportStep │ │ │ ├── BoardMetrics │ │ │ ├── BoardMetrics.tsx │ │ │ ├── index.tsx │ │ │ └── style.tsx │ │ │ ├── DoraMetrics │ │ │ ├── index.tsx │ │ │ └── style.tsx │ │ │ ├── ExpiredDialog │ │ │ ├── index.tsx │ │ │ └── style.tsx │ │ │ ├── ReportDetail │ │ │ ├── board.tsx │ │ │ ├── dora.tsx │ │ │ ├── index.ts │ │ │ └── withBack.tsx │ │ │ ├── index.tsx │ │ │ └── style.tsx │ ├── context │ │ ├── Metrics │ │ │ └── metricsSlice.ts │ │ ├── config │ │ │ ├── board │ │ │ │ └── boardSlice.ts │ │ │ ├── configSlice.ts │ │ │ ├── pipelineTool │ │ │ │ ├── pipelineToolSlice.ts │ │ │ │ └── verifyResponseSlice.ts │ │ │ └── sourceControl │ │ │ │ ├── sourceControlSlice.ts │ │ │ │ └── verifyResponseSlice.ts │ │ ├── meta │ │ │ └── metaSlice.tsx │ │ ├── notification │ │ │ └── NotificationSlice.tsx │ │ └── stepper │ │ │ └── StepperSlice.tsx │ ├── errors │ │ ├── BadRequestError.ts │ │ ├── ErrorType.ts │ │ ├── ForbiddenError.ts │ │ ├── InternalServerError.ts │ │ ├── NotFoundError.ts │ │ ├── TimeoutError.ts │ │ ├── UnauthorizedError.ts │ │ ├── UnknownError.ts │ │ └── index.ts │ ├── hooks │ │ ├── index.ts │ │ ├── reportMapper │ │ │ ├── classification.ts │ │ │ ├── cycleTime.ts │ │ │ ├── deploymentFrequency.ts │ │ │ ├── devChangeFailureRate.ts │ │ │ ├── devMeanTimeToRecovery.ts │ │ │ ├── exportValidityTime.ts │ │ │ ├── leadTimeForChanges.ts │ │ │ ├── report.ts │ │ │ ├── reportUIDataStructure.ts │ │ │ ├── reworkMapper.tsx │ │ │ └── velocity.ts │ │ ├── useAppDispatch.ts │ │ ├── useExportCsvEffect.ts │ │ ├── useGenerateReportEffect.ts │ │ ├── useGetBoardInfo.ts │ │ ├── useGetMetricsStepsEffect.ts │ │ ├── useGetPipelineToolInfoEffect.ts │ │ ├── useMetricsStepValidationCheckContext.tsx │ │ ├── useVerifyBoardEffect.ts │ │ ├── useVerifyPipelineToolEffect.ts │ │ └── useVerifySourceControlTokenEffect.ts │ ├── layouts │ │ ├── Header.tsx │ │ └── style.tsx │ ├── main.tsx │ ├── pages │ │ ├── ErrorPage.tsx │ │ ├── Home.tsx │ │ └── Metrics.tsx │ ├── router.tsx │ ├── store.ts │ ├── theme.ts │ ├── utils │ │ ├── types.ts │ │ └── util.ts │ └── vite-env.d.ts ├── tsconfig.json ├── tsconfig.node.json ├── tsconfig.scripts.json └── vite.config.ts ├── ops ├── base.sh ├── build.sh ├── check.sh ├── deploy.sh └── infra │ ├── Dockerfile.backend │ ├── Dockerfile.e2e │ ├── Dockerfile.frontend │ ├── cloudformation.yml │ ├── docker-compose.yml │ ├── nginx.conf │ └── updateAwsResource.sh ├── release-notes ├── 20230228.md ├── 20230726.md ├── 20231009.md ├── 20231106.md ├── 20231121.md ├── 20231204.md ├── 20240229.md └── 20240402.md └── renovate.json /.buildkite/hooks/pre-exit: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # The `pre-exit` hook will run just before your build job finishes 4 | 5 | # Note that as the script is sourced not run directly, the shebang line will be ignored 6 | # See https://buildkite.com/docs/agent/v3/hooks#creating-hook-scripts 7 | 8 | set -euo pipefail 9 | 10 | echo "Start to perform cleanup" 11 | 12 | rm -rf /var/lib/buildkite-agent/builds/* 13 | rm -rf /var/lib/buildkite-agent/.cache/* 14 | rm -rf /var/lib/buildkite-agent/.local/* 15 | 16 | docker system df 17 | docker system prune -a --volumes --force 18 | 19 | echo "Finished to perform cleanup" 20 | -------------------------------------------------------------------------------- /.dockerignore: -------------------------------------------------------------------------------- 1 | frontend/node_modules 2 | frontend/dist 3 | frontend/coverage 4 | 5 | backend/.gradle 6 | backend/.idea 7 | backend/bin 8 | backend/build 9 | backend/csv 10 | backend/logs 11 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # Editor configuration, see https://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | charset = utf-8 6 | indent_style = space 7 | indent_size = 2 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | 11 | [*.ts] 12 | quote_type = single 13 | 14 | [*.java] 15 | indent_style = tab 16 | indent_size = 4 17 | 18 | [*.md] 19 | max_line_length = off 20 | trim_trailing_whitespace = false 21 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: false 2 | -------------------------------------------------------------------------------- /.github/auto-request-reviewer.yml: -------------------------------------------------------------------------------- 1 | reviewers: 2 | defaults: 3 | - team:heartbeat 4 | options: 5 | ignore_draft: true 6 | ignored_keywords: 7 | - DO NOT REVIEW 8 | number_of_reviewers: 3 9 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | 4 | # Maintain dependencies for GitHub Actions 5 | - package-ecosystem: "github-actions" 6 | directory: "/" 7 | schedule: 8 | interval: "weekly" 9 | 10 | # Maintain dependencies for yarn 11 | - package-ecosystem: "npm" 12 | directory: "/" 13 | schedule: 14 | interval: "weekly" -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | ## Summary 2 | 3 | ... 4 | 5 | ## Before 6 | 7 | _Description_ 8 | 9 | **Screenshots** 10 | If applicable, add screenshots to help explain behavior of your code. 11 | 12 | ## After 13 | 14 | _Description_ 15 | 16 | **Screenshots** 17 | If applicable, add screenshots to help explain behavior of your code. 18 | 19 | ## Note 20 | 21 | _Null_ 22 | -------------------------------------------------------------------------------- /.github/workflows/auto-request-review.yml: -------------------------------------------------------------------------------- 1 | name: Auto Request Review 2 | 3 | on: 4 | pull_request: 5 | types: [opened, ready_for_review, reopened] 6 | 7 | jobs: 8 | auto-request-review: 9 | name: Auto Request Review 10 | runs-on: ubuntu-latest 11 | steps: 12 | - name: Request review 13 | uses: necojackarc/auto-request-review@v0.13.0 14 | with: 15 | token: ${{ secrets.GITHUB_TOKEN }} 16 | config: .github/auto-request-reviewer.yml 17 | use_local: true 18 | -------------------------------------------------------------------------------- /.trivyignore: -------------------------------------------------------------------------------- 1 | CVE-2022-2048 2 | CVE-2022-1471 3 | CVE-2022-25857 4 | AVD-AWS-0124 5 | AVD-AWS-0107 6 | CVE-2023-6246 7 | CVE-2023-6779 8 | CVE-2023-36478 9 | CVE-2023-49465 10 | CVE-2023-49467 11 | CVE-2023-49468 12 | CVE-2024-0553 13 | CVE-2024-0567 14 | CVE-2024-22201 15 | CVE-2024-22259 16 | CVE-2024-28085 17 | -------------------------------------------------------------------------------- /backend/.gitignore: -------------------------------------------------------------------------------- 1 | HELP.md 2 | .gradle 3 | build/ 4 | !gradle/wrapper/gradle-wrapper.jar 5 | !**/src/main/**/build/ 6 | !**/src/test/**/build/ 7 | app/ 8 | 9 | ### STS ### 10 | .apt_generated 11 | .classpath 12 | .factorypath 13 | .project 14 | .settings 15 | .springBeans 16 | .sts4-cache 17 | bin/ 18 | !**/src/main/**/bin/ 19 | !**/src/test/**/bin/ 20 | 21 | ### IntelliJ IDEA ### 22 | .idea 23 | *.iws 24 | *.iml 25 | *.ipr 26 | out/ 27 | !**/src/main/**/out/ 28 | !**/src/test/**/out/ 29 | 30 | ### NetBeans ### 31 | /nbproject/private/ 32 | /nbbuild/ 33 | /dist/ 34 | /nbdist/ 35 | /.nb-gradle/ 36 | 37 | ### VS Code ### 38 | .vscode/ 39 | 40 | logs 41 | -------------------------------------------------------------------------------- /backend/gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thoughtworks/HeartBeat/702cb41d75fce4670b4c57cb69aa538caeee88ae/backend/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /backend/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-bin.zip 4 | networkTimeout=10000 5 | validateDistributionUrl=true 6 | zipStoreBase=GRADLE_USER_HOME 7 | zipStorePath=wrapper/dists 8 | -------------------------------------------------------------------------------- /backend/lombok.config: -------------------------------------------------------------------------------- 1 | config.stopBubbling = true 2 | lombok.addLombokGeneratedAnnotation = true 3 | -------------------------------------------------------------------------------- /backend/settings.gradle: -------------------------------------------------------------------------------- 1 | rootProject.name = 'heartbeat-backend' 2 | -------------------------------------------------------------------------------- /backend/src/main/java/heartbeat/client/component/JiraUriGenerator.java: -------------------------------------------------------------------------------- 1 | package heartbeat.client.component; 2 | 3 | import java.net.URI; 4 | 5 | public interface JiraUriGenerator { 6 | 7 | URI getUri(String site); 8 | 9 | } 10 | -------------------------------------------------------------------------------- /backend/src/main/java/heartbeat/client/component/ProductJiraUriGenerator.java: -------------------------------------------------------------------------------- 1 | package heartbeat.client.component; 2 | 3 | import org.springframework.beans.factory.annotation.Value; 4 | import org.springframework.context.annotation.Profile; 5 | import org.springframework.stereotype.Component; 6 | 7 | import java.net.URI; 8 | 9 | @Component 10 | @Profile(value = { "default", "local" }) 11 | public class ProductJiraUriGenerator implements JiraUriGenerator { 12 | 13 | @Value("${jira.url}") 14 | private String url; 15 | 16 | @Override 17 | public URI getUri(String site) { 18 | String baseUrl = String.format(url, site); 19 | return URI.create(baseUrl); 20 | } 21 | 22 | } 23 | -------------------------------------------------------------------------------- /backend/src/main/java/heartbeat/client/dto/board/jira/AllCardsResponseDTO.java: -------------------------------------------------------------------------------- 1 | package heartbeat.client.dto.board.jira; 2 | 3 | import com.fasterxml.jackson.annotation.JsonIgnoreProperties; 4 | import lombok.AllArgsConstructor; 5 | import lombok.Builder; 6 | import lombok.Data; 7 | import lombok.NoArgsConstructor; 8 | 9 | import java.util.List; 10 | 11 | @AllArgsConstructor 12 | @Data 13 | @Builder 14 | @NoArgsConstructor 15 | @JsonIgnoreProperties(ignoreUnknown = true) 16 | public class AllCardsResponseDTO { 17 | 18 | private String total; 19 | 20 | private List issues; 21 | 22 | } 23 | -------------------------------------------------------------------------------- /backend/src/main/java/heartbeat/client/dto/board/jira/Assignee.java: -------------------------------------------------------------------------------- 1 | package heartbeat.client.dto.board.jira; 2 | 3 | import heartbeat.service.report.ICardFieldDisplayName; 4 | import com.fasterxml.jackson.annotation.JsonIgnoreProperties; 5 | import lombok.AllArgsConstructor; 6 | import lombok.Builder; 7 | import lombok.Data; 8 | import lombok.NoArgsConstructor; 9 | 10 | @Data 11 | @NoArgsConstructor 12 | @AllArgsConstructor 13 | @Builder 14 | 15 | @JsonIgnoreProperties(ignoreUnknown = true) 16 | public class Assignee implements ICardFieldDisplayName { 17 | 18 | private String displayName; 19 | 20 | public String getDisplayName() { 21 | return displayName; 22 | } 23 | 24 | } 25 | -------------------------------------------------------------------------------- /backend/src/main/java/heartbeat/client/dto/board/jira/CardHistoryResponseDTO.java: -------------------------------------------------------------------------------- 1 | package heartbeat.client.dto.board.jira; 2 | 3 | import java.io.Serializable; 4 | import lombok.AllArgsConstructor; 5 | import lombok.Builder; 6 | import lombok.Data; 7 | import lombok.NoArgsConstructor; 8 | 9 | import java.util.List; 10 | 11 | @Data 12 | @Builder 13 | @NoArgsConstructor 14 | @AllArgsConstructor 15 | public class CardHistoryResponseDTO implements Serializable { 16 | 17 | private Boolean isLast; 18 | 19 | private List items; 20 | 21 | } 22 | -------------------------------------------------------------------------------- /backend/src/main/java/heartbeat/client/dto/board/jira/DoneCardFields.java: -------------------------------------------------------------------------------- 1 | package heartbeat.client.dto.board.jira; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Data; 5 | import lombok.NoArgsConstructor; 6 | 7 | @Data 8 | @NoArgsConstructor 9 | @AllArgsConstructor 10 | public class DoneCardFields { 11 | 12 | private Assignee assignee; 13 | 14 | } 15 | -------------------------------------------------------------------------------- /backend/src/main/java/heartbeat/client/dto/board/jira/FieldResponseDTO.java: -------------------------------------------------------------------------------- 1 | package heartbeat.client.dto.board.jira; 2 | 3 | import com.fasterxml.jackson.annotation.JsonIgnoreProperties; 4 | import java.io.Serializable; 5 | import lombok.AllArgsConstructor; 6 | import lombok.Builder; 7 | import lombok.Data; 8 | import lombok.NoArgsConstructor; 9 | 10 | import java.util.List; 11 | 12 | @Data 13 | @Builder 14 | @AllArgsConstructor 15 | @NoArgsConstructor 16 | @JsonIgnoreProperties(ignoreUnknown = true) 17 | public class FieldResponseDTO implements Serializable { 18 | 19 | private List projects; 20 | 21 | } 22 | -------------------------------------------------------------------------------- /backend/src/main/java/heartbeat/client/dto/board/jira/HolidayDTO.java: -------------------------------------------------------------------------------- 1 | package heartbeat.client.dto.board.jira; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Builder; 5 | import lombok.Data; 6 | import lombok.NoArgsConstructor; 7 | 8 | import java.io.Serializable; 9 | 10 | @Data 11 | @Builder 12 | @NoArgsConstructor 13 | @AllArgsConstructor 14 | public class HolidayDTO implements Serializable { 15 | 16 | private String name; 17 | 18 | private String date; 19 | 20 | private Boolean isOffDay; 21 | 22 | } 23 | -------------------------------------------------------------------------------- /backend/src/main/java/heartbeat/client/dto/board/jira/HolidayResponseDTO.java: -------------------------------------------------------------------------------- 1 | package heartbeat.client.dto.board.jira; 2 | 3 | import com.fasterxml.jackson.annotation.JsonIgnoreProperties; 4 | import lombok.AllArgsConstructor; 5 | import lombok.Builder; 6 | import lombok.Data; 7 | import lombok.NoArgsConstructor; 8 | 9 | import java.util.List; 10 | 11 | @Data 12 | @Builder 13 | @NoArgsConstructor 14 | @AllArgsConstructor 15 | @JsonIgnoreProperties(ignoreUnknown = true) 16 | public class HolidayResponseDTO { 17 | 18 | private List days; 19 | 20 | } 21 | -------------------------------------------------------------------------------- /backend/src/main/java/heartbeat/client/dto/board/jira/HolidaysResponseDTO.java: -------------------------------------------------------------------------------- 1 | package heartbeat.client.dto.board.jira; 2 | 3 | import com.fasterxml.jackson.annotation.JsonIgnoreProperties; 4 | import lombok.AllArgsConstructor; 5 | import lombok.Builder; 6 | import lombok.Data; 7 | import lombok.NoArgsConstructor; 8 | 9 | import java.io.Serializable; 10 | import java.util.List; 11 | 12 | @Data 13 | @Builder 14 | @NoArgsConstructor 15 | @AllArgsConstructor 16 | @JsonIgnoreProperties(ignoreUnknown = true) 17 | public class HolidaysResponseDTO implements Serializable { 18 | 19 | private List days; 20 | 21 | } 22 | -------------------------------------------------------------------------------- /backend/src/main/java/heartbeat/client/dto/board/jira/IssueField.java: -------------------------------------------------------------------------------- 1 | package heartbeat.client.dto.board.jira; 2 | 3 | import com.fasterxml.jackson.annotation.JsonIgnoreProperties; 4 | import java.io.Serializable; 5 | import lombok.AllArgsConstructor; 6 | import lombok.Builder; 7 | import lombok.Data; 8 | import lombok.NoArgsConstructor; 9 | 10 | @Data 11 | @Builder 12 | @AllArgsConstructor 13 | @NoArgsConstructor 14 | @JsonIgnoreProperties(ignoreUnknown = true) 15 | public class IssueField implements Serializable { 16 | 17 | private String key; 18 | 19 | private String name; 20 | 21 | } 22 | -------------------------------------------------------------------------------- /backend/src/main/java/heartbeat/client/dto/board/jira/Issuetype.java: -------------------------------------------------------------------------------- 1 | package heartbeat.client.dto.board.jira; 2 | 3 | import com.fasterxml.jackson.annotation.JsonIgnoreProperties; 4 | import java.io.Serializable; 5 | import java.util.Map; 6 | import lombok.AllArgsConstructor; 7 | import lombok.Builder; 8 | import lombok.Data; 9 | import lombok.NoArgsConstructor; 10 | 11 | @Data 12 | @Builder 13 | @AllArgsConstructor 14 | @NoArgsConstructor 15 | @JsonIgnoreProperties(ignoreUnknown = true) 16 | public class Issuetype implements Serializable { 17 | 18 | private Map fields; 19 | 20 | } 21 | -------------------------------------------------------------------------------- /backend/src/main/java/heartbeat/client/dto/board/jira/JiraBoardConfigDTO.java: -------------------------------------------------------------------------------- 1 | package heartbeat.client.dto.board.jira; 2 | 3 | import com.fasterxml.jackson.annotation.JsonIgnoreProperties; 4 | import java.io.Serializable; 5 | import lombok.AllArgsConstructor; 6 | import lombok.Builder; 7 | import lombok.Data; 8 | import lombok.NoArgsConstructor; 9 | 10 | @Data 11 | @Builder 12 | @AllArgsConstructor 13 | @NoArgsConstructor 14 | @JsonIgnoreProperties(ignoreUnknown = true) 15 | public class JiraBoardConfigDTO implements Serializable { 16 | 17 | private String id; 18 | 19 | private String name; 20 | 21 | private JiraColumnConfig columnConfig; 22 | 23 | } 24 | -------------------------------------------------------------------------------- /backend/src/main/java/heartbeat/client/dto/board/jira/JiraBoardProject.java: -------------------------------------------------------------------------------- 1 | package heartbeat.client.dto.board.jira; 2 | 3 | import com.fasterxml.jackson.annotation.JsonIgnoreProperties; 4 | import lombok.AllArgsConstructor; 5 | import lombok.Builder; 6 | import lombok.Data; 7 | import lombok.NoArgsConstructor; 8 | 9 | import java.io.Serializable; 10 | 11 | @AllArgsConstructor 12 | @Data 13 | @Builder 14 | @NoArgsConstructor 15 | @JsonIgnoreProperties(ignoreUnknown = true) 16 | public class JiraBoardProject implements Serializable { 17 | 18 | private String style; 19 | 20 | } 21 | -------------------------------------------------------------------------------- /backend/src/main/java/heartbeat/client/dto/board/jira/JiraBoardVerifyDTO.java: -------------------------------------------------------------------------------- 1 | package heartbeat.client.dto.board.jira; 2 | 3 | import com.fasterxml.jackson.annotation.JsonIgnoreProperties; 4 | import lombok.AllArgsConstructor; 5 | import lombok.Builder; 6 | import lombok.Data; 7 | import lombok.NoArgsConstructor; 8 | 9 | import java.io.Serializable; 10 | 11 | @Data 12 | @Builder 13 | @AllArgsConstructor 14 | @NoArgsConstructor 15 | @JsonIgnoreProperties(ignoreUnknown = true) 16 | public class JiraBoardVerifyDTO implements Serializable { 17 | 18 | private String id; 19 | 20 | private String name; 21 | 22 | private String type; 23 | 24 | private Location location; 25 | 26 | } 27 | -------------------------------------------------------------------------------- /backend/src/main/java/heartbeat/client/dto/board/jira/JiraCard.java: -------------------------------------------------------------------------------- 1 | package heartbeat.client.dto.board.jira; 2 | 3 | import com.fasterxml.jackson.annotation.JsonIgnoreProperties; 4 | import lombok.AllArgsConstructor; 5 | import lombok.Builder; 6 | import lombok.Data; 7 | import lombok.NoArgsConstructor; 8 | 9 | @Data 10 | @AllArgsConstructor 11 | @NoArgsConstructor 12 | @Builder 13 | @JsonIgnoreProperties(ignoreUnknown = true) 14 | public class JiraCard { 15 | 16 | private String key; 17 | 18 | private JiraCardField fields; 19 | 20 | } 21 | -------------------------------------------------------------------------------- /backend/src/main/java/heartbeat/client/dto/board/jira/JiraCardWithFields.java: -------------------------------------------------------------------------------- 1 | package heartbeat.client.dto.board.jira; 2 | 3 | import heartbeat.controller.board.dto.response.TargetField; 4 | import lombok.AllArgsConstructor; 5 | import lombok.Builder; 6 | import lombok.Data; 7 | import lombok.NoArgsConstructor; 8 | 9 | import java.util.List; 10 | 11 | @Data 12 | @AllArgsConstructor 13 | @NoArgsConstructor 14 | @Builder 15 | public class JiraCardWithFields { 16 | 17 | private List jiraCards; 18 | 19 | private List targetFields; 20 | 21 | } 22 | -------------------------------------------------------------------------------- /backend/src/main/java/heartbeat/client/dto/board/jira/JiraColumn.java: -------------------------------------------------------------------------------- 1 | package heartbeat.client.dto.board.jira; 2 | 3 | import com.fasterxml.jackson.annotation.JsonIgnoreProperties; 4 | import java.io.Serializable; 5 | import lombok.AllArgsConstructor; 6 | import lombok.Builder; 7 | import lombok.Data; 8 | import lombok.NoArgsConstructor; 9 | 10 | import java.util.List; 11 | 12 | @Data 13 | @Builder 14 | @AllArgsConstructor 15 | @NoArgsConstructor 16 | @JsonIgnoreProperties(ignoreUnknown = true) 17 | public class JiraColumn implements Serializable { 18 | 19 | private String name; 20 | 21 | private List statuses; 22 | 23 | } 24 | -------------------------------------------------------------------------------- /backend/src/main/java/heartbeat/client/dto/board/jira/JiraColumnConfig.java: -------------------------------------------------------------------------------- 1 | package heartbeat.client.dto.board.jira; 2 | 3 | import com.fasterxml.jackson.annotation.JsonIgnoreProperties; 4 | import java.io.Serializable; 5 | import lombok.AllArgsConstructor; 6 | import lombok.Builder; 7 | import lombok.Data; 8 | import lombok.NoArgsConstructor; 9 | 10 | import java.util.List; 11 | 12 | @Data 13 | @Builder 14 | @AllArgsConstructor 15 | @NoArgsConstructor 16 | @JsonIgnoreProperties(ignoreUnknown = true) 17 | public class JiraColumnConfig implements Serializable { 18 | 19 | private List columns; 20 | 21 | } 22 | -------------------------------------------------------------------------------- /backend/src/main/java/heartbeat/client/dto/board/jira/JiraColumnStatus.java: -------------------------------------------------------------------------------- 1 | package heartbeat.client.dto.board.jira; 2 | 3 | import com.fasterxml.jackson.annotation.JsonIgnoreProperties; 4 | import java.io.Serializable; 5 | import lombok.AllArgsConstructor; 6 | import lombok.Builder; 7 | import lombok.Data; 8 | import lombok.NoArgsConstructor; 9 | 10 | @Data 11 | @Builder 12 | @AllArgsConstructor 13 | @NoArgsConstructor 14 | @JsonIgnoreProperties(ignoreUnknown = true) 15 | public class JiraColumnStatus implements Serializable { 16 | 17 | private String id; 18 | 19 | } 20 | -------------------------------------------------------------------------------- /backend/src/main/java/heartbeat/client/dto/board/jira/Location.java: -------------------------------------------------------------------------------- 1 | package heartbeat.client.dto.board.jira; 2 | 3 | import com.fasterxml.jackson.annotation.JsonIgnoreProperties; 4 | import lombok.AllArgsConstructor; 5 | import lombok.Builder; 6 | import lombok.Data; 7 | import lombok.NoArgsConstructor; 8 | 9 | import java.io.Serializable; 10 | 11 | @Data 12 | @Builder 13 | @AllArgsConstructor 14 | @NoArgsConstructor 15 | @JsonIgnoreProperties(ignoreUnknown = true) 16 | public class Location implements Serializable { 17 | 18 | private String projectKey; 19 | 20 | } 21 | -------------------------------------------------------------------------------- /backend/src/main/java/heartbeat/client/dto/board/jira/Project.java: -------------------------------------------------------------------------------- 1 | package heartbeat.client.dto.board.jira; 2 | 3 | import com.fasterxml.jackson.annotation.JsonIgnoreProperties; 4 | import java.io.Serializable; 5 | import lombok.AllArgsConstructor; 6 | import lombok.Builder; 7 | import lombok.Data; 8 | import lombok.NoArgsConstructor; 9 | 10 | import java.util.List; 11 | 12 | @Data 13 | @Builder 14 | @AllArgsConstructor 15 | @NoArgsConstructor 16 | @JsonIgnoreProperties(ignoreUnknown = true) 17 | public class Project implements Serializable { 18 | 19 | private List issuetypes; 20 | 21 | } 22 | -------------------------------------------------------------------------------- /backend/src/main/java/heartbeat/client/dto/board/jira/Sprint.java: -------------------------------------------------------------------------------- 1 | package heartbeat.client.dto.board.jira; 2 | 3 | import com.fasterxml.jackson.annotation.JsonIgnoreProperties; 4 | import lombok.AllArgsConstructor; 5 | import lombok.Builder; 6 | import lombok.Getter; 7 | import lombok.NoArgsConstructor; 8 | import lombok.Setter; 9 | 10 | @Getter 11 | @Setter 12 | @NoArgsConstructor 13 | @Builder 14 | @AllArgsConstructor 15 | @JsonIgnoreProperties(ignoreUnknown = true) 16 | public class Sprint { 17 | 18 | private String name; 19 | 20 | private String completeDate; 21 | 22 | } 23 | -------------------------------------------------------------------------------- /backend/src/main/java/heartbeat/client/dto/board/jira/StatusCategory.java: -------------------------------------------------------------------------------- 1 | package heartbeat.client.dto.board.jira; 2 | 3 | import java.io.Serializable; 4 | import lombok.AllArgsConstructor; 5 | import lombok.Data; 6 | 7 | @AllArgsConstructor 8 | @Data 9 | public class StatusCategory implements Serializable { 10 | 11 | private String key; 12 | 13 | private String name; 14 | 15 | } 16 | -------------------------------------------------------------------------------- /backend/src/main/java/heartbeat/client/dto/board/jira/StatusSelfDTO.java: -------------------------------------------------------------------------------- 1 | package heartbeat.client.dto.board.jira; 2 | 3 | import java.io.Serializable; 4 | import lombok.AllArgsConstructor; 5 | import lombok.Builder; 6 | import lombok.Data; 7 | 8 | @AllArgsConstructor 9 | @Data 10 | @Builder 11 | public class StatusSelfDTO implements Serializable { 12 | 13 | private String untranslatedName; 14 | 15 | private StatusCategory statusCategory; 16 | 17 | } 18 | -------------------------------------------------------------------------------- /backend/src/main/java/heartbeat/client/dto/codebase/github/Author.java: -------------------------------------------------------------------------------- 1 | package heartbeat.client.dto.codebase.github; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Builder; 5 | import lombok.Data; 6 | import lombok.NoArgsConstructor; 7 | 8 | import java.io.Serializable; 9 | 10 | @Data 11 | @Builder 12 | @NoArgsConstructor 13 | @AllArgsConstructor 14 | public class Author implements Serializable { 15 | 16 | private String name; 17 | 18 | private String email; 19 | 20 | private String date; 21 | 22 | } 23 | -------------------------------------------------------------------------------- /backend/src/main/java/heartbeat/client/dto/codebase/github/Commit.java: -------------------------------------------------------------------------------- 1 | package heartbeat.client.dto.codebase.github; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Builder; 5 | import lombok.Data; 6 | import lombok.NoArgsConstructor; 7 | 8 | import java.io.Serializable; 9 | 10 | @Data 11 | @Builder 12 | @NoArgsConstructor 13 | @AllArgsConstructor 14 | public class Commit implements Serializable { 15 | 16 | private Author author; 17 | 18 | private Committer committer; 19 | 20 | private String message; 21 | 22 | } 23 | -------------------------------------------------------------------------------- /backend/src/main/java/heartbeat/client/dto/codebase/github/CommitInfo.java: -------------------------------------------------------------------------------- 1 | package heartbeat.client.dto.codebase.github; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Builder; 5 | import lombok.Data; 6 | import lombok.NoArgsConstructor; 7 | 8 | import java.io.Serializable; 9 | 10 | @Data 11 | @Builder 12 | @NoArgsConstructor 13 | @AllArgsConstructor 14 | public class CommitInfo implements Serializable { 15 | 16 | private Commit commit; 17 | 18 | } 19 | -------------------------------------------------------------------------------- /backend/src/main/java/heartbeat/client/dto/codebase/github/Committer.java: -------------------------------------------------------------------------------- 1 | package heartbeat.client.dto.codebase.github; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Builder; 5 | import lombok.Data; 6 | import lombok.NoArgsConstructor; 7 | 8 | import java.io.Serializable; 9 | 10 | @Data 11 | @Builder 12 | @NoArgsConstructor 13 | @AllArgsConstructor 14 | public class Committer implements Serializable { 15 | 16 | private String name; 17 | 18 | private String email; 19 | 20 | private String date; 21 | 22 | } 23 | -------------------------------------------------------------------------------- /backend/src/main/java/heartbeat/client/dto/codebase/github/PipelineLeadTime.java: -------------------------------------------------------------------------------- 1 | package heartbeat.client.dto.codebase.github; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Builder; 5 | import lombok.Data; 6 | import lombok.NoArgsConstructor; 7 | 8 | import java.util.List; 9 | 10 | @Data 11 | @Builder 12 | @NoArgsConstructor 13 | @AllArgsConstructor 14 | public class PipelineLeadTime { 15 | 16 | private String pipelineName; 17 | 18 | private String pipelineStep; 19 | 20 | private List leadTimes; 21 | 22 | } 23 | -------------------------------------------------------------------------------- /backend/src/main/java/heartbeat/client/dto/pipeline/buildkite/BuildKiteBuildsRequest.java: -------------------------------------------------------------------------------- 1 | package heartbeat.client.dto.pipeline.buildkite; 2 | 3 | import com.fasterxml.jackson.annotation.JsonProperty; 4 | import lombok.AllArgsConstructor; 5 | import lombok.Builder; 6 | import lombok.Data; 7 | import lombok.NoArgsConstructor; 8 | 9 | @Data 10 | @Builder 11 | @NoArgsConstructor 12 | @AllArgsConstructor 13 | public class BuildKiteBuildsRequest { 14 | 15 | private String page; 16 | 17 | @JsonProperty("per_page") 18 | private String perPage; 19 | 20 | @JsonProperty("created_to") 21 | private String createdTo; 22 | 23 | @JsonProperty("finished_from") 24 | private String finishedFrom; 25 | 26 | } 27 | -------------------------------------------------------------------------------- /backend/src/main/java/heartbeat/client/dto/pipeline/buildkite/BuildKiteOrganizationsInfo.java: -------------------------------------------------------------------------------- 1 | package heartbeat.client.dto.pipeline.buildkite; 2 | 3 | import com.fasterxml.jackson.annotation.JsonIgnoreProperties; 4 | import lombok.AllArgsConstructor; 5 | import lombok.Builder; 6 | import lombok.Data; 7 | import lombok.NoArgsConstructor; 8 | 9 | import java.io.Serializable; 10 | 11 | @Data 12 | @Builder 13 | @AllArgsConstructor 14 | @NoArgsConstructor 15 | @JsonIgnoreProperties(ignoreUnknown = true) 16 | public class BuildKiteOrganizationsInfo implements Serializable { 17 | 18 | private String name; 19 | 20 | private String slug; 21 | 22 | } 23 | -------------------------------------------------------------------------------- /backend/src/main/java/heartbeat/client/dto/pipeline/buildkite/BuildKitePipelineDTO.java: -------------------------------------------------------------------------------- 1 | package heartbeat.client.dto.pipeline.buildkite; 2 | 3 | import com.fasterxml.jackson.annotation.JsonIgnoreProperties; 4 | import lombok.AllArgsConstructor; 5 | import lombok.Builder; 6 | import lombok.Data; 7 | import lombok.NoArgsConstructor; 8 | 9 | import java.io.Serializable; 10 | import java.util.List; 11 | 12 | @Data 13 | @Builder 14 | @AllArgsConstructor 15 | @NoArgsConstructor 16 | @JsonIgnoreProperties(ignoreUnknown = true) 17 | public class BuildKitePipelineDTO implements Serializable { 18 | 19 | private String name; 20 | 21 | private String slug; 22 | 23 | private String repository; 24 | 25 | private List steps; 26 | 27 | } 28 | -------------------------------------------------------------------------------- /backend/src/main/java/heartbeat/client/dto/pipeline/buildkite/BuildKiteTokenInfo.java: -------------------------------------------------------------------------------- 1 | package heartbeat.client.dto.pipeline.buildkite; 2 | 3 | import com.fasterxml.jackson.annotation.JsonIgnoreProperties; 4 | import lombok.AllArgsConstructor; 5 | import lombok.Builder; 6 | import lombok.Getter; 7 | import lombok.NoArgsConstructor; 8 | 9 | import java.io.Serializable; 10 | import java.util.List; 11 | 12 | @AllArgsConstructor 13 | @NoArgsConstructor 14 | @Getter 15 | @Builder 16 | @JsonIgnoreProperties(ignoreUnknown = true) 17 | public class BuildKiteTokenInfo implements Serializable { 18 | 19 | private List scopes; 20 | 21 | } 22 | -------------------------------------------------------------------------------- /backend/src/main/java/heartbeat/client/dto/pipeline/buildkite/DeployInfo.java: -------------------------------------------------------------------------------- 1 | package heartbeat.client.dto.pipeline.buildkite; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Builder; 5 | import lombok.Data; 6 | import lombok.NoArgsConstructor; 7 | 8 | @Data 9 | @Builder 10 | @NoArgsConstructor 11 | @AllArgsConstructor 12 | public class DeployInfo { 13 | 14 | private String pipelineCreateTime; 15 | 16 | private String jobStartTime; 17 | 18 | private String jobFinishTime; 19 | 20 | private String commitId; 21 | 22 | private String state; 23 | 24 | private boolean isPipelineCanceled; 25 | 26 | private String jobName; 27 | 28 | } 29 | -------------------------------------------------------------------------------- /backend/src/main/java/heartbeat/client/dto/pipeline/buildkite/DeployTimes.java: -------------------------------------------------------------------------------- 1 | package heartbeat.client.dto.pipeline.buildkite; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Builder; 5 | import lombok.Data; 6 | import lombok.Getter; 7 | import lombok.NoArgsConstructor; 8 | 9 | import java.util.List; 10 | 11 | @Data 12 | @Builder 13 | @NoArgsConstructor 14 | @AllArgsConstructor 15 | public class DeployTimes { 16 | 17 | private String pipelineId; 18 | 19 | private String pipelineName; 20 | 21 | private String pipelineStep; 22 | 23 | private List passed; 24 | 25 | private List failed; 26 | 27 | } 28 | -------------------------------------------------------------------------------- /backend/src/main/java/heartbeat/client/dto/pipeline/buildkite/EnvDTO.java: -------------------------------------------------------------------------------- 1 | package heartbeat.client.dto.pipeline.buildkite; 2 | 3 | import com.fasterxml.jackson.annotation.JsonIgnoreProperties; 4 | import lombok.AllArgsConstructor; 5 | import lombok.Builder; 6 | import lombok.Data; 7 | 8 | import java.io.Serializable; 9 | 10 | @Data 11 | @Builder 12 | @AllArgsConstructor 13 | @JsonIgnoreProperties(ignoreUnknown = true) 14 | public class EnvDTO implements Serializable { 15 | 16 | } 17 | -------------------------------------------------------------------------------- /backend/src/main/java/heartbeat/client/dto/pipeline/buildkite/PageBuildKitePipelineInfoDTO.java: -------------------------------------------------------------------------------- 1 | package heartbeat.client.dto.pipeline.buildkite; 2 | 3 | import com.fasterxml.jackson.annotation.JsonIgnoreProperties; 4 | import lombok.AllArgsConstructor; 5 | import lombok.Builder; 6 | import lombok.Data; 7 | import lombok.NoArgsConstructor; 8 | 9 | import java.io.Serializable; 10 | import java.util.List; 11 | 12 | @Data 13 | @JsonIgnoreProperties(ignoreUnknown = true) 14 | @AllArgsConstructor 15 | @NoArgsConstructor 16 | @Builder 17 | public class PageBuildKitePipelineInfoDTO implements Serializable { 18 | 19 | private int totalPage; 20 | 21 | private List firstPageInfo; 22 | 23 | } 24 | -------------------------------------------------------------------------------- /backend/src/main/java/heartbeat/client/dto/pipeline/buildkite/PageStepsInfoDto.java: -------------------------------------------------------------------------------- 1 | package heartbeat.client.dto.pipeline.buildkite; 2 | 3 | import com.fasterxml.jackson.annotation.JsonIgnoreProperties; 4 | import lombok.AllArgsConstructor; 5 | import lombok.Builder; 6 | import lombok.Data; 7 | import lombok.NoArgsConstructor; 8 | 9 | import java.io.Serializable; 10 | import java.util.List; 11 | 12 | @Data 13 | @JsonIgnoreProperties(ignoreUnknown = true) 14 | @AllArgsConstructor 15 | @NoArgsConstructor 16 | @Builder 17 | public class PageStepsInfoDto implements Serializable { 18 | 19 | private int totalPage; 20 | 21 | private List firstPageStepsInfo; 22 | 23 | } 24 | -------------------------------------------------------------------------------- /backend/src/main/java/heartbeat/client/dto/pipeline/buildkite/ProviderDTO.java: -------------------------------------------------------------------------------- 1 | package heartbeat.client.dto.pipeline.buildkite; 2 | 3 | import com.fasterxml.jackson.annotation.JsonIgnoreProperties; 4 | import com.fasterxml.jackson.annotation.JsonProperty; 5 | import lombok.AllArgsConstructor; 6 | import lombok.Builder; 7 | import lombok.Data; 8 | import lombok.NoArgsConstructor; 9 | 10 | import java.io.Serializable; 11 | 12 | @Data 13 | @Builder 14 | @AllArgsConstructor 15 | @NoArgsConstructor 16 | @JsonIgnoreProperties(ignoreUnknown = true) 17 | public class ProviderDTO implements Serializable { 18 | 19 | private String id; 20 | 21 | private ProviderSettingsDTO settings; 22 | 23 | @JsonProperty("webhook_url") 24 | private String webhookUrl; 25 | 26 | } 27 | -------------------------------------------------------------------------------- /backend/src/main/java/heartbeat/config/DataType.java: -------------------------------------------------------------------------------- 1 | package heartbeat.config; 2 | 3 | public enum DataType { 4 | 5 | METRIC, BOARD, PIPELINE; 6 | 7 | public static DataType fromValue(String type) { 8 | return switch (type) { 9 | case "metric" -> METRIC; 10 | case "board" -> BOARD; 11 | case "pipeline" -> PIPELINE; 12 | default -> throw new IllegalArgumentException("Data type does not find!"); 13 | }; 14 | } 15 | 16 | } 17 | -------------------------------------------------------------------------------- /backend/src/main/java/heartbeat/controller/board/dto/request/RequestJiraBoardColumnSetting.java: -------------------------------------------------------------------------------- 1 | package heartbeat.controller.board.dto.request; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Builder; 5 | import lombok.Data; 6 | import lombok.NoArgsConstructor; 7 | 8 | @Data 9 | @Builder 10 | @NoArgsConstructor 11 | @AllArgsConstructor 12 | public class RequestJiraBoardColumnSetting { 13 | 14 | private String name; 15 | 16 | private String value; 17 | 18 | } 19 | -------------------------------------------------------------------------------- /backend/src/main/java/heartbeat/controller/board/dto/request/ReworkTimesSetting.java: -------------------------------------------------------------------------------- 1 | package heartbeat.controller.board.dto.request; 2 | 3 | import lombok.Builder; 4 | import lombok.Getter; 5 | import lombok.Setter; 6 | 7 | import java.util.List; 8 | 9 | @Getter 10 | @Setter 11 | @Builder 12 | public class ReworkTimesSetting { 13 | 14 | private String reworkState; 15 | 16 | private List excludedStates; 17 | 18 | public CardStepsEnum getEnumReworkState() { 19 | return CardStepsEnum.fromValue(reworkState); 20 | } 21 | 22 | public List getEnumExcludeStates() { 23 | return excludedStates.stream().map(CardStepsEnum::fromValue).toList(); 24 | } 25 | 26 | } 27 | -------------------------------------------------------------------------------- /backend/src/main/java/heartbeat/controller/board/dto/response/Assignee.java: -------------------------------------------------------------------------------- 1 | package heartbeat.controller.board.dto.response; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Builder; 5 | import lombok.Data; 6 | import lombok.NoArgsConstructor; 7 | 8 | @Data 9 | @Builder 10 | @AllArgsConstructor 11 | @NoArgsConstructor 12 | public class Assignee { 13 | 14 | private String accountId; 15 | 16 | private String displayName; 17 | 18 | } 19 | -------------------------------------------------------------------------------- /backend/src/main/java/heartbeat/controller/board/dto/response/BoardConfigDTO.java: -------------------------------------------------------------------------------- 1 | package heartbeat.controller.board.dto.response; 2 | 3 | import com.fasterxml.jackson.annotation.JsonProperty; 4 | import java.util.List; 5 | 6 | import lombok.AllArgsConstructor; 7 | import lombok.Builder; 8 | import lombok.Data; 9 | import lombok.NoArgsConstructor; 10 | 11 | @Data 12 | @Builder 13 | @AllArgsConstructor 14 | @NoArgsConstructor 15 | public class BoardConfigDTO { 16 | 17 | @JsonProperty("jiraColumns") 18 | private List jiraColumnResponse; 19 | 20 | private List users; 21 | 22 | private List targetFields; 23 | 24 | private List ignoredTargetFields; 25 | 26 | } 27 | -------------------------------------------------------------------------------- /backend/src/main/java/heartbeat/controller/board/dto/response/CardCollection.java: -------------------------------------------------------------------------------- 1 | package heartbeat.controller.board.dto.response; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Builder; 5 | import lombok.Data; 6 | import lombok.NoArgsConstructor; 7 | 8 | import java.util.List; 9 | 10 | @Data 11 | @Builder 12 | @NoArgsConstructor 13 | @AllArgsConstructor 14 | public class CardCollection { 15 | 16 | private double storyPointSum; 17 | 18 | private int cardsNumber; 19 | 20 | private List jiraCardDTOList; 21 | 22 | private int reworkCardNumber; 23 | 24 | private double reworkRatio; 25 | 26 | } 27 | -------------------------------------------------------------------------------- /backend/src/main/java/heartbeat/controller/board/dto/response/CardCustomFieldKey.java: -------------------------------------------------------------------------------- 1 | package heartbeat.controller.board.dto.response; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Builder; 5 | import lombok.Data; 6 | import lombok.NoArgsConstructor; 7 | 8 | @Data 9 | @Builder 10 | @AllArgsConstructor 11 | @NoArgsConstructor 12 | public class CardCustomFieldKey { 13 | 14 | private String storyPoints; 15 | 16 | private String sprint; 17 | 18 | private String flagged; 19 | 20 | private boolean isInTargetField; 21 | 22 | } 23 | -------------------------------------------------------------------------------- /backend/src/main/java/heartbeat/controller/board/dto/response/CardCycleTime.java: -------------------------------------------------------------------------------- 1 | package heartbeat.controller.board.dto.response; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Builder; 5 | import lombok.Data; 6 | import lombok.NoArgsConstructor; 7 | 8 | @Data 9 | @Builder 10 | @AllArgsConstructor 11 | @NoArgsConstructor 12 | public class CardCycleTime { 13 | 14 | private String name; 15 | 16 | private StepsDay steps; 17 | 18 | private double total; 19 | 20 | } 21 | -------------------------------------------------------------------------------- /backend/src/main/java/heartbeat/controller/board/dto/response/CardParent.java: -------------------------------------------------------------------------------- 1 | package heartbeat.controller.board.dto.response; 2 | 3 | import heartbeat.service.report.ICardFieldDisplayName; 4 | import com.fasterxml.jackson.annotation.JsonIgnoreProperties; 5 | import lombok.AllArgsConstructor; 6 | import lombok.Builder; 7 | import lombok.Data; 8 | import lombok.NoArgsConstructor; 9 | 10 | @Data 11 | @Builder 12 | @AllArgsConstructor 13 | @NoArgsConstructor 14 | 15 | @JsonIgnoreProperties(ignoreUnknown = true) 16 | public class CardParent implements ICardFieldDisplayName { 17 | 18 | private Fields fields; 19 | 20 | private String name; 21 | 22 | private String key; 23 | 24 | public String getDisplayName() { 25 | return key; 26 | } 27 | 28 | } 29 | -------------------------------------------------------------------------------- /backend/src/main/java/heartbeat/controller/board/dto/response/CardParentField.java: -------------------------------------------------------------------------------- 1 | package heartbeat.controller.board.dto.response; 2 | 3 | import com.fasterxml.jackson.annotation.JsonIgnoreProperties; 4 | import lombok.AllArgsConstructor; 5 | import lombok.Builder; 6 | import lombok.Data; 7 | import lombok.NoArgsConstructor; 8 | 9 | @Data 10 | @Builder 11 | @AllArgsConstructor 12 | @NoArgsConstructor 13 | @JsonIgnoreProperties(ignoreUnknown = true) 14 | public class CardParentField { 15 | 16 | private String summary; 17 | 18 | } 19 | -------------------------------------------------------------------------------- /backend/src/main/java/heartbeat/controller/board/dto/response/ColumnValue.java: -------------------------------------------------------------------------------- 1 | package heartbeat.controller.board.dto.response; 2 | 3 | import java.util.List; 4 | 5 | import lombok.AllArgsConstructor; 6 | import lombok.Builder; 7 | import lombok.Data; 8 | import lombok.NoArgsConstructor; 9 | 10 | @Data 11 | @Builder 12 | @AllArgsConstructor 13 | @NoArgsConstructor 14 | public class ColumnValue { 15 | 16 | private String name; 17 | 18 | private List statuses; 19 | 20 | } 21 | -------------------------------------------------------------------------------- /backend/src/main/java/heartbeat/controller/board/dto/response/CycleTimeInfo.java: -------------------------------------------------------------------------------- 1 | package heartbeat.controller.board.dto.response; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Builder; 5 | import lombok.Data; 6 | import lombok.NoArgsConstructor; 7 | 8 | @Data 9 | @Builder 10 | @AllArgsConstructor 11 | @NoArgsConstructor 12 | public class CycleTimeInfo { 13 | 14 | private String column; 15 | 16 | private Double day; 17 | 18 | } 19 | -------------------------------------------------------------------------------- /backend/src/main/java/heartbeat/controller/board/dto/response/CycleTimeInfoDTO.java: -------------------------------------------------------------------------------- 1 | package heartbeat.controller.board.dto.response; 2 | 3 | import heartbeat.controller.board.dto.response.CycleTimeInfo; 4 | import lombok.AllArgsConstructor; 5 | import lombok.Builder; 6 | import lombok.Data; 7 | import lombok.NoArgsConstructor; 8 | 9 | import java.util.List; 10 | 11 | @Builder 12 | @Data 13 | @AllArgsConstructor 14 | @NoArgsConstructor 15 | public class CycleTimeInfoDTO { 16 | 17 | private List cycleTimeInfos; 18 | 19 | private List originCycleTimeInfos; 20 | 21 | } 22 | -------------------------------------------------------------------------------- /backend/src/main/java/heartbeat/controller/board/dto/response/Fields.java: -------------------------------------------------------------------------------- 1 | package heartbeat.controller.board.dto.response; 2 | 3 | import com.fasterxml.jackson.annotation.JsonIgnoreProperties; 4 | import lombok.AllArgsConstructor; 5 | import lombok.Builder; 6 | import lombok.Data; 7 | import lombok.NoArgsConstructor; 8 | 9 | @Data 10 | @Builder 11 | @AllArgsConstructor 12 | @NoArgsConstructor 13 | 14 | @JsonIgnoreProperties(ignoreUnknown = true) 15 | public class Fields { 16 | 17 | private String summary; 18 | 19 | } 20 | -------------------------------------------------------------------------------- /backend/src/main/java/heartbeat/controller/board/dto/response/FixVersion.java: -------------------------------------------------------------------------------- 1 | package heartbeat.controller.board.dto.response; 2 | 3 | import heartbeat.service.report.ICardFieldDisplayName; 4 | import com.fasterxml.jackson.annotation.JsonIgnoreProperties; 5 | import lombok.AllArgsConstructor; 6 | import lombok.Builder; 7 | import lombok.Data; 8 | import lombok.NoArgsConstructor; 9 | 10 | @Data 11 | @Builder 12 | @AllArgsConstructor 13 | @NoArgsConstructor 14 | 15 | @JsonIgnoreProperties(ignoreUnknown = true) 16 | public class FixVersion implements ICardFieldDisplayName { 17 | 18 | private String name; 19 | 20 | public String getDisplayName() { 21 | return name; 22 | } 23 | 24 | } 25 | -------------------------------------------------------------------------------- /backend/src/main/java/heartbeat/controller/board/dto/response/IssueType.java: -------------------------------------------------------------------------------- 1 | package heartbeat.controller.board.dto.response; 2 | 3 | import heartbeat.service.report.ICardFieldDisplayName; 4 | import com.fasterxml.jackson.annotation.JsonIgnoreProperties; 5 | import lombok.AllArgsConstructor; 6 | import lombok.Builder; 7 | import lombok.Data; 8 | import lombok.NoArgsConstructor; 9 | 10 | @Data 11 | @Builder 12 | @AllArgsConstructor 13 | @NoArgsConstructor 14 | 15 | @JsonIgnoreProperties(ignoreUnknown = true) 16 | public class IssueType implements ICardFieldDisplayName { 17 | 18 | private String name; 19 | 20 | public String getDisplayName() { 21 | return name; 22 | } 23 | 24 | } 25 | -------------------------------------------------------------------------------- /backend/src/main/java/heartbeat/controller/board/dto/response/JiraCardCollection.java: -------------------------------------------------------------------------------- 1 | package heartbeat.controller.board.dto.response; 2 | 3 | import heartbeat.client.dto.board.jira.JiraCard; 4 | import lombok.AllArgsConstructor; 5 | import lombok.Builder; 6 | import lombok.Data; 7 | import lombok.NoArgsConstructor; 8 | 9 | import java.util.List; 10 | 11 | @Data 12 | @Builder 13 | @NoArgsConstructor 14 | @AllArgsConstructor 15 | public class JiraCardCollection { 16 | 17 | private Integer total; 18 | 19 | private List issues; 20 | 21 | } 22 | -------------------------------------------------------------------------------- /backend/src/main/java/heartbeat/controller/board/dto/response/JiraColumnDTO.java: -------------------------------------------------------------------------------- 1 | package heartbeat.controller.board.dto.response; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Builder; 5 | import lombok.Data; 6 | import lombok.NoArgsConstructor; 7 | 8 | @Data 9 | @Builder 10 | @AllArgsConstructor 11 | @NoArgsConstructor 12 | public class JiraColumnDTO { 13 | 14 | private String key; 15 | 16 | private ColumnValue value; 17 | 18 | } 19 | -------------------------------------------------------------------------------- /backend/src/main/java/heartbeat/controller/board/dto/response/JiraProject.java: -------------------------------------------------------------------------------- 1 | package heartbeat.controller.board.dto.response; 2 | 3 | import heartbeat.service.report.ICardFieldDisplayName; 4 | import com.fasterxml.jackson.annotation.JsonIgnoreProperties; 5 | import lombok.AllArgsConstructor; 6 | import lombok.Builder; 7 | import lombok.Data; 8 | import lombok.NoArgsConstructor; 9 | 10 | @Data 11 | @Builder 12 | @AllArgsConstructor 13 | @NoArgsConstructor 14 | 15 | @JsonIgnoreProperties(ignoreUnknown = true) 16 | public class JiraProject implements ICardFieldDisplayName { 17 | 18 | private String id; 19 | 20 | private String key; 21 | 22 | private String name; 23 | 24 | public String getDisplayName() { 25 | return name; 26 | } 27 | 28 | } 29 | -------------------------------------------------------------------------------- /backend/src/main/java/heartbeat/controller/board/dto/response/JiraVerifyResponse.java: -------------------------------------------------------------------------------- 1 | package heartbeat.controller.board.dto.response; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Builder; 5 | import lombok.Data; 6 | import lombok.NoArgsConstructor; 7 | 8 | @Data 9 | @Builder 10 | @AllArgsConstructor 11 | @NoArgsConstructor 12 | public class JiraVerifyResponse { 13 | 14 | private String projectKey; 15 | 16 | } 17 | -------------------------------------------------------------------------------- /backend/src/main/java/heartbeat/controller/board/dto/response/Priority.java: -------------------------------------------------------------------------------- 1 | package heartbeat.controller.board.dto.response; 2 | 3 | import heartbeat.service.report.ICardFieldDisplayName; 4 | import com.fasterxml.jackson.annotation.JsonIgnoreProperties; 5 | import lombok.AllArgsConstructor; 6 | import lombok.Builder; 7 | import lombok.Data; 8 | import lombok.NoArgsConstructor; 9 | 10 | @Data 11 | @Builder 12 | @AllArgsConstructor 13 | @NoArgsConstructor 14 | 15 | @JsonIgnoreProperties(ignoreUnknown = true) 16 | public class Priority implements ICardFieldDisplayName { 17 | 18 | private String name; 19 | 20 | public String getDisplayName() { 21 | return name; 22 | } 23 | 24 | } 25 | -------------------------------------------------------------------------------- /backend/src/main/java/heartbeat/controller/board/dto/response/Reporter.java: -------------------------------------------------------------------------------- 1 | package heartbeat.controller.board.dto.response; 2 | 3 | import heartbeat.service.report.ICardFieldDisplayName; 4 | import com.fasterxml.jackson.annotation.JsonIgnoreProperties; 5 | import lombok.AllArgsConstructor; 6 | import lombok.Builder; 7 | import lombok.Data; 8 | import lombok.NoArgsConstructor; 9 | 10 | @Data 11 | @Builder 12 | @AllArgsConstructor 13 | @NoArgsConstructor 14 | 15 | @JsonIgnoreProperties(ignoreUnknown = true) 16 | public class Reporter implements ICardFieldDisplayName { 17 | 18 | private String displayName; 19 | 20 | public String getDisplayName() { 21 | return displayName; 22 | } 23 | 24 | } 25 | -------------------------------------------------------------------------------- /backend/src/main/java/heartbeat/controller/board/dto/response/ReworkTimesInfo.java: -------------------------------------------------------------------------------- 1 | package heartbeat.controller.board.dto.response; 2 | 3 | import heartbeat.controller.board.dto.request.CardStepsEnum; 4 | import lombok.AllArgsConstructor; 5 | import lombok.Builder; 6 | import lombok.Getter; 7 | import lombok.Setter; 8 | 9 | @Getter 10 | @Setter 11 | @AllArgsConstructor 12 | @Builder 13 | public class ReworkTimesInfo { 14 | 15 | private CardStepsEnum state; 16 | 17 | private Integer times; 18 | 19 | } 20 | -------------------------------------------------------------------------------- /backend/src/main/java/heartbeat/controller/board/dto/response/Status.java: -------------------------------------------------------------------------------- 1 | package heartbeat.controller.board.dto.response; 2 | 3 | import lombok.Builder; 4 | import lombok.Data; 5 | 6 | @Data 7 | @Builder 8 | public class Status { 9 | 10 | private String name; 11 | 12 | } 13 | -------------------------------------------------------------------------------- /backend/src/main/java/heartbeat/controller/board/dto/response/StatusChangedItem.java: -------------------------------------------------------------------------------- 1 | package heartbeat.controller.board.dto.response; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Builder; 5 | import lombok.Data; 6 | import lombok.NoArgsConstructor; 7 | 8 | @Builder 9 | @Data 10 | @AllArgsConstructor 11 | @NoArgsConstructor 12 | public class StatusChangedItem { 13 | 14 | private long timestamp; 15 | 16 | private String status; 17 | 18 | } 19 | -------------------------------------------------------------------------------- /backend/src/main/java/heartbeat/controller/board/dto/response/StatusTimeStamp.java: -------------------------------------------------------------------------------- 1 | package heartbeat.controller.board.dto.response; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Builder; 5 | import lombok.Data; 6 | import lombok.NoArgsConstructor; 7 | 8 | @Builder 9 | @Data 10 | @AllArgsConstructor 11 | @NoArgsConstructor 12 | public class StatusTimeStamp { 13 | 14 | private long startTimestamp; 15 | 16 | private long endTimestamp; 17 | 18 | private String status; 19 | 20 | } 21 | -------------------------------------------------------------------------------- /backend/src/main/java/heartbeat/controller/board/dto/response/StepsDay.java: -------------------------------------------------------------------------------- 1 | package heartbeat.controller.board.dto.response; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Builder; 5 | import lombok.Data; 6 | import lombok.NoArgsConstructor; 7 | 8 | @Data 9 | @Builder 10 | @AllArgsConstructor 11 | @NoArgsConstructor 12 | public class StepsDay { 13 | 14 | private double analyse; 15 | 16 | private double development; 17 | 18 | private double waiting; 19 | 20 | private double testing; 21 | 22 | private double blocked; 23 | 24 | private double review; 25 | 26 | } 27 | -------------------------------------------------------------------------------- /backend/src/main/java/heartbeat/controller/board/dto/response/TargetField.java: -------------------------------------------------------------------------------- 1 | package heartbeat.controller.board.dto.response; 2 | 3 | import java.io.Serializable; 4 | 5 | import lombok.AllArgsConstructor; 6 | import lombok.Builder; 7 | import lombok.Data; 8 | import lombok.NoArgsConstructor; 9 | 10 | @Data 11 | @NoArgsConstructor 12 | @Builder 13 | @AllArgsConstructor 14 | public class TargetField implements Serializable { 15 | 16 | private String key; 17 | 18 | private String name; 19 | 20 | private boolean flag; 21 | 22 | } 23 | -------------------------------------------------------------------------------- /backend/src/main/java/heartbeat/controller/crypto/response/DecryptResponse.java: -------------------------------------------------------------------------------- 1 | package heartbeat.controller.crypto.response; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Builder; 5 | import lombok.Getter; 6 | import lombok.NoArgsConstructor; 7 | import lombok.Setter; 8 | 9 | @Setter 10 | @Getter 11 | @Builder 12 | @AllArgsConstructor 13 | @NoArgsConstructor 14 | public class DecryptResponse { 15 | 16 | private String configData; 17 | 18 | } 19 | -------------------------------------------------------------------------------- /backend/src/main/java/heartbeat/controller/crypto/response/EncryptResponse.java: -------------------------------------------------------------------------------- 1 | package heartbeat.controller.crypto.response; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Builder; 5 | import lombok.Getter; 6 | import lombok.NoArgsConstructor; 7 | import lombok.Setter; 8 | 9 | @Setter 10 | @Getter 11 | @Builder 12 | @AllArgsConstructor 13 | @NoArgsConstructor 14 | public class EncryptResponse { 15 | 16 | private String encryptedData; 17 | 18 | } 19 | -------------------------------------------------------------------------------- /backend/src/main/java/heartbeat/controller/pipeline/dto/request/DeploymentEnvironment.java: -------------------------------------------------------------------------------- 1 | package heartbeat.controller.pipeline.dto.request; 2 | 3 | import jakarta.annotation.Nullable; 4 | import lombok.AllArgsConstructor; 5 | import lombok.Builder; 6 | import lombok.Data; 7 | import lombok.NoArgsConstructor; 8 | 9 | import java.util.List; 10 | 11 | @Data 12 | @Builder 13 | @NoArgsConstructor 14 | @AllArgsConstructor 15 | public class DeploymentEnvironment { 16 | 17 | private String orgId; 18 | 19 | private String orgName; 20 | 21 | private String id; 22 | 23 | private String name; 24 | 25 | private String step; 26 | 27 | @Nullable 28 | private String repository; 29 | 30 | private List branches; 31 | 32 | } 33 | -------------------------------------------------------------------------------- /backend/src/main/java/heartbeat/controller/pipeline/dto/request/PipelineType.java: -------------------------------------------------------------------------------- 1 | package heartbeat.controller.pipeline.dto.request; 2 | 3 | import lombok.extern.log4j.Log4j2; 4 | 5 | @Log4j2 6 | public enum PipelineType { 7 | 8 | BUILDKITE("buildkite"); 9 | 10 | public final String pipelineType; 11 | 12 | PipelineType(String pipelineType) { 13 | this.pipelineType = pipelineType; 14 | } 15 | 16 | public static PipelineType fromValue(String type) { 17 | return switch (type) { 18 | case "buildkite" -> BUILDKITE; 19 | default -> { 20 | log.error("Failed to match Pipeline type: {} ", type); 21 | throw new IllegalArgumentException("Pipeline type does not find!"); 22 | } 23 | }; 24 | } 25 | 26 | } 27 | -------------------------------------------------------------------------------- /backend/src/main/java/heartbeat/controller/pipeline/dto/request/TokenParam.java: -------------------------------------------------------------------------------- 1 | package heartbeat.controller.pipeline.dto.request; 2 | 3 | import jakarta.validation.Valid; 4 | import jakarta.validation.constraints.NotBlank; 5 | import lombok.AllArgsConstructor; 6 | import lombok.Builder; 7 | import lombok.Data; 8 | import lombok.NoArgsConstructor; 9 | 10 | @Data 11 | @Builder 12 | @NoArgsConstructor 13 | @AllArgsConstructor 14 | public class TokenParam { 15 | 16 | @Valid 17 | @NotBlank(message = "Token cannot be empty.") 18 | private String token; 19 | 20 | } 21 | -------------------------------------------------------------------------------- /backend/src/main/java/heartbeat/controller/pipeline/dto/response/BuildKiteResponseDTO.java: -------------------------------------------------------------------------------- 1 | package heartbeat.controller.pipeline.dto.response; 2 | 3 | import java.util.List; 4 | import lombok.Builder; 5 | import lombok.Data; 6 | 7 | @Data 8 | @Builder 9 | public class BuildKiteResponseDTO { 10 | 11 | private List pipelineList; 12 | 13 | } 14 | -------------------------------------------------------------------------------- /backend/src/main/java/heartbeat/controller/pipeline/dto/response/Pipeline.java: -------------------------------------------------------------------------------- 1 | package heartbeat.controller.pipeline.dto.response; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Builder; 5 | import lombok.Data; 6 | import lombok.NoArgsConstructor; 7 | 8 | import java.util.List; 9 | 10 | @Data 11 | @Builder 12 | @AllArgsConstructor 13 | @NoArgsConstructor 14 | public class Pipeline { 15 | 16 | String id; 17 | 18 | String name; 19 | 20 | String orgId; 21 | 22 | String orgName; 23 | 24 | String repository; 25 | 26 | List steps; 27 | 28 | } 29 | -------------------------------------------------------------------------------- /backend/src/main/java/heartbeat/controller/pipeline/dto/response/PipelineStepsDTO.java: -------------------------------------------------------------------------------- 1 | package heartbeat.controller.pipeline.dto.response; 2 | 3 | import java.util.List; 4 | import lombok.AllArgsConstructor; 5 | import lombok.Builder; 6 | import lombok.Data; 7 | import lombok.NoArgsConstructor; 8 | 9 | @Data 10 | @NoArgsConstructor 11 | @AllArgsConstructor 12 | @Builder 13 | public class PipelineStepsDTO { 14 | 15 | private String pipelineId; 16 | 17 | private String name; 18 | 19 | private List steps; 20 | 21 | private String repository; 22 | 23 | private String orgId; 24 | 25 | private String orgName; 26 | 27 | private List branches; 28 | 29 | private List pipelineCrews; 30 | 31 | } 32 | -------------------------------------------------------------------------------- /backend/src/main/java/heartbeat/controller/report/dto/request/BuildKiteSetting.java: -------------------------------------------------------------------------------- 1 | package heartbeat.controller.report.dto.request; 2 | 3 | import heartbeat.controller.pipeline.dto.request.DeploymentEnvironment; 4 | import lombok.AllArgsConstructor; 5 | import lombok.Builder; 6 | import lombok.Data; 7 | import lombok.NoArgsConstructor; 8 | 9 | import java.util.List; 10 | 11 | @Data 12 | @Builder 13 | @NoArgsConstructor 14 | @AllArgsConstructor 15 | public class BuildKiteSetting { 16 | 17 | private String type; 18 | 19 | private String token; 20 | 21 | private List deploymentEnvList; 22 | 23 | private List pipelineCrews; 24 | 25 | } 26 | -------------------------------------------------------------------------------- /backend/src/main/java/heartbeat/controller/report/dto/request/CodebaseSetting.java: -------------------------------------------------------------------------------- 1 | package heartbeat.controller.report.dto.request; 2 | 3 | import heartbeat.controller.pipeline.dto.request.DeploymentEnvironment; 4 | import lombok.AllArgsConstructor; 5 | import lombok.Builder; 6 | import lombok.Data; 7 | import lombok.NoArgsConstructor; 8 | 9 | import java.util.List; 10 | 11 | @Data 12 | @Builder 13 | @NoArgsConstructor 14 | @AllArgsConstructor 15 | public class CodebaseSetting { 16 | 17 | private String type; 18 | 19 | private String token; 20 | 21 | private List leadTime; 22 | 23 | } 24 | -------------------------------------------------------------------------------- /backend/src/main/java/heartbeat/controller/report/dto/request/MetricEnum.java: -------------------------------------------------------------------------------- 1 | package heartbeat.controller.report.dto.request; 2 | 3 | public enum MetricEnum { 4 | 5 | VELOCITY("velocity"), CYCLE_TIME("cycle time"), CLASSIFICATION("classification"), 6 | DEPLOYMENT_FREQUENCY("deployment frequency"), DEV_CHANGE_FAILURE_RATE("dev change failure rate"), 7 | DEV_MEAN_TIME_TO_RECOVERY("dev mean time to recovery"), LEAD_TIME_FOR_CHANGES("lead time for changes"), 8 | REWORK_TIMES("rework times"); 9 | 10 | private final String value; 11 | 12 | MetricEnum(String value) { 13 | this.value = value; 14 | } 15 | 16 | public String getValue() { 17 | return value; 18 | } 19 | 20 | } 21 | -------------------------------------------------------------------------------- /backend/src/main/java/heartbeat/controller/report/dto/request/MetricType.java: -------------------------------------------------------------------------------- 1 | package heartbeat.controller.report.dto.request; 2 | 3 | public enum MetricType { 4 | 5 | BOARD("board"), DORA("dora"); 6 | 7 | public final String metricType; 8 | 9 | MetricType(String metricType) { 10 | this.metricType = metricType; 11 | } 12 | 13 | public static MetricType fromValue(String type) { 14 | return switch (type) { 15 | case "board" -> BOARD; 16 | case "dora" -> DORA; 17 | default -> throw new IllegalArgumentException("MetricType not found!"); 18 | }; 19 | } 20 | 21 | } 22 | -------------------------------------------------------------------------------- /backend/src/main/java/heartbeat/controller/report/dto/response/AvgDeploymentFrequency.java: -------------------------------------------------------------------------------- 1 | package heartbeat.controller.report.dto.response; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Builder; 5 | import lombok.Data; 6 | import lombok.NoArgsConstructor; 7 | 8 | @Data 9 | @Builder 10 | @NoArgsConstructor 11 | @AllArgsConstructor 12 | public class AvgDeploymentFrequency { 13 | 14 | @Builder.Default 15 | private String name = "Average"; 16 | 17 | private float deploymentFrequency; 18 | 19 | } 20 | -------------------------------------------------------------------------------- /backend/src/main/java/heartbeat/controller/report/dto/response/AvgDevChangeFailureRate.java: -------------------------------------------------------------------------------- 1 | package heartbeat.controller.report.dto.response; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Builder; 5 | import lombok.Data; 6 | import lombok.NoArgsConstructor; 7 | 8 | @Data 9 | @Builder 10 | @NoArgsConstructor 11 | @AllArgsConstructor 12 | public class AvgDevChangeFailureRate { 13 | 14 | @Builder.Default 15 | private String name = "Average"; 16 | 17 | private int totalTimes; 18 | 19 | private int totalFailedTimes; 20 | 21 | private float failureRate; 22 | 23 | } 24 | -------------------------------------------------------------------------------- /backend/src/main/java/heartbeat/controller/report/dto/response/AvgDevMeanTimeToRecovery.java: -------------------------------------------------------------------------------- 1 | package heartbeat.controller.report.dto.response; 2 | 3 | import java.math.BigDecimal; 4 | import lombok.AllArgsConstructor; 5 | import lombok.Builder; 6 | import lombok.Data; 7 | import lombok.NoArgsConstructor; 8 | 9 | @Data 10 | @NoArgsConstructor 11 | @AllArgsConstructor 12 | @Builder 13 | public class AvgDevMeanTimeToRecovery { 14 | 15 | @Builder.Default 16 | private String name = "Average"; 17 | 18 | private BigDecimal timeToRecovery; 19 | 20 | } 21 | -------------------------------------------------------------------------------- /backend/src/main/java/heartbeat/controller/report/dto/response/AvgLeadTimeForChanges.java: -------------------------------------------------------------------------------- 1 | package heartbeat.controller.report.dto.response; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Builder; 5 | import lombok.Data; 6 | import lombok.NoArgsConstructor; 7 | 8 | @Data 9 | @Builder 10 | @NoArgsConstructor 11 | @AllArgsConstructor 12 | public class AvgLeadTimeForChanges { 13 | 14 | @Builder.Default 15 | private String name = "Average"; 16 | 17 | private Double prLeadTime; 18 | 19 | private Double pipelineLeadTime; 20 | 21 | private Double totalDelayTime; 22 | 23 | } 24 | -------------------------------------------------------------------------------- /backend/src/main/java/heartbeat/controller/report/dto/response/BoardCSVConfig.java: -------------------------------------------------------------------------------- 1 | package heartbeat.controller.report.dto.response; 2 | 3 | import io.micrometer.common.lang.Nullable; 4 | import lombok.AllArgsConstructor; 5 | import lombok.Builder; 6 | import lombok.Data; 7 | import lombok.NoArgsConstructor; 8 | 9 | @Data 10 | @NoArgsConstructor 11 | @AllArgsConstructor 12 | @Builder 13 | public class BoardCSVConfig { 14 | 15 | private String label; 16 | 17 | private String value; 18 | 19 | @Nullable 20 | private String originKey; 21 | 22 | } 23 | -------------------------------------------------------------------------------- /backend/src/main/java/heartbeat/controller/report/dto/response/CallbackResponse.java: -------------------------------------------------------------------------------- 1 | package heartbeat.controller.report.dto.response; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Builder; 5 | import lombok.Getter; 6 | import lombok.NoArgsConstructor; 7 | import lombok.Setter; 8 | 9 | @Getter 10 | @Setter 11 | @Builder 12 | @AllArgsConstructor 13 | @NoArgsConstructor 14 | public class CallbackResponse { 15 | 16 | private String callbackUrl; 17 | 18 | private Integer interval; 19 | 20 | } 21 | -------------------------------------------------------------------------------- /backend/src/main/java/heartbeat/controller/report/dto/response/Classification.java: -------------------------------------------------------------------------------- 1 | package heartbeat.controller.report.dto.response; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Builder; 5 | import lombok.Data; 6 | import lombok.NoArgsConstructor; 7 | 8 | import java.util.List; 9 | 10 | @Data 11 | @NoArgsConstructor 12 | @AllArgsConstructor 13 | @Builder 14 | public class Classification { 15 | 16 | private String fieldName; 17 | 18 | private List pairList; 19 | 20 | } 21 | -------------------------------------------------------------------------------- /backend/src/main/java/heartbeat/controller/report/dto/response/ClassificationNameValuePair.java: -------------------------------------------------------------------------------- 1 | package heartbeat.controller.report.dto.response; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Builder; 5 | import lombok.Data; 6 | import lombok.NoArgsConstructor; 7 | 8 | @Data 9 | @NoArgsConstructor 10 | @AllArgsConstructor 11 | @Builder 12 | public class ClassificationNameValuePair { 13 | 14 | private String name; 15 | 16 | private Double value; 17 | 18 | } 19 | -------------------------------------------------------------------------------- /backend/src/main/java/heartbeat/controller/report/dto/response/CycleTime.java: -------------------------------------------------------------------------------- 1 | package heartbeat.controller.report.dto.response; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Builder; 5 | import lombok.Data; 6 | import lombok.NoArgsConstructor; 7 | 8 | import java.util.List; 9 | 10 | @Data 11 | @Builder 12 | @NoArgsConstructor 13 | @AllArgsConstructor 14 | public class CycleTime { 15 | 16 | private double totalTimeForCards; 17 | 18 | private double averageCycleTimePerCard; 19 | 20 | private double averageCycleTimePerSP; 21 | 22 | private List swimlaneList; 23 | 24 | } 25 | -------------------------------------------------------------------------------- /backend/src/main/java/heartbeat/controller/report/dto/response/CycleTimeForSelectedStepItem.java: -------------------------------------------------------------------------------- 1 | package heartbeat.controller.report.dto.response; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Builder; 5 | import lombok.Data; 6 | import lombok.NoArgsConstructor; 7 | 8 | @Data 9 | @Builder 10 | @NoArgsConstructor 11 | @AllArgsConstructor 12 | public class CycleTimeForSelectedStepItem { 13 | 14 | private String optionalItemName; 15 | 16 | private double averageTimeForSP; 17 | 18 | private double averageTimeForCards; 19 | 20 | private double totalTime; 21 | 22 | } 23 | -------------------------------------------------------------------------------- /backend/src/main/java/heartbeat/controller/report/dto/response/CycleTimeResult.java: -------------------------------------------------------------------------------- 1 | package heartbeat.controller.report.dto.response; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Builder; 5 | import lombok.Data; 6 | import lombok.NoArgsConstructor; 7 | 8 | import java.util.List; 9 | 10 | @Data 11 | @Builder 12 | @NoArgsConstructor 13 | @AllArgsConstructor 14 | public class CycleTimeResult { 15 | 16 | private List cycleTimeForSelectedStepsList; 17 | 18 | private double totalTime; 19 | 20 | } 21 | -------------------------------------------------------------------------------- /backend/src/main/java/heartbeat/controller/report/dto/response/DailyDeploymentCount.java: -------------------------------------------------------------------------------- 1 | package heartbeat.controller.report.dto.response; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Builder; 5 | import lombok.Data; 6 | import lombok.NoArgsConstructor; 7 | 8 | @Data 9 | @Builder 10 | @NoArgsConstructor 11 | @AllArgsConstructor 12 | public class DailyDeploymentCount { 13 | 14 | private String date; 15 | 16 | private Integer count; 17 | 18 | } 19 | -------------------------------------------------------------------------------- /backend/src/main/java/heartbeat/controller/report/dto/response/DeploymentDateCount.java: -------------------------------------------------------------------------------- 1 | package heartbeat.controller.report.dto.response; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Builder; 5 | import lombok.Data; 6 | import lombok.NoArgsConstructor; 7 | 8 | @Data 9 | @NoArgsConstructor 10 | @AllArgsConstructor 11 | @Builder 12 | public class DeploymentDateCount { 13 | 14 | private String date; 15 | 16 | private Integer count; 17 | 18 | } 19 | -------------------------------------------------------------------------------- /backend/src/main/java/heartbeat/controller/report/dto/response/DeploymentFrequency.java: -------------------------------------------------------------------------------- 1 | package heartbeat.controller.report.dto.response; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Builder; 5 | import lombok.Data; 6 | import lombok.NoArgsConstructor; 7 | 8 | import java.util.List; 9 | 10 | @Data 11 | @Builder 12 | @NoArgsConstructor 13 | @AllArgsConstructor 14 | public class DeploymentFrequency { 15 | 16 | private AvgDeploymentFrequency avgDeploymentFrequency; 17 | 18 | private List deploymentFrequencyOfPipelines; 19 | 20 | } 21 | -------------------------------------------------------------------------------- /backend/src/main/java/heartbeat/controller/report/dto/response/DeploymentFrequencyOfPipeline.java: -------------------------------------------------------------------------------- 1 | package heartbeat.controller.report.dto.response; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Builder; 5 | import lombok.Data; 6 | import lombok.NoArgsConstructor; 7 | 8 | import java.util.List; 9 | 10 | @Data 11 | @Builder 12 | @NoArgsConstructor 13 | @AllArgsConstructor 14 | public class DeploymentFrequencyOfPipeline { 15 | 16 | private String name; 17 | 18 | private String step; 19 | 20 | private float deploymentFrequency; 21 | 22 | private List dailyDeploymentCounts; 23 | 24 | } 25 | -------------------------------------------------------------------------------- /backend/src/main/java/heartbeat/controller/report/dto/response/DevChangeFailureRate.java: -------------------------------------------------------------------------------- 1 | package heartbeat.controller.report.dto.response; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Builder; 5 | import lombok.Data; 6 | import lombok.NoArgsConstructor; 7 | 8 | import java.util.List; 9 | 10 | @Data 11 | @Builder 12 | @NoArgsConstructor 13 | @AllArgsConstructor 14 | public class DevChangeFailureRate { 15 | 16 | private AvgDevChangeFailureRate avgDevChangeFailureRate; 17 | 18 | private List devChangeFailureRateOfPipelines; 19 | 20 | } 21 | -------------------------------------------------------------------------------- /backend/src/main/java/heartbeat/controller/report/dto/response/DevChangeFailureRateOfPipeline.java: -------------------------------------------------------------------------------- 1 | package heartbeat.controller.report.dto.response; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Builder; 5 | import lombok.Data; 6 | import lombok.NoArgsConstructor; 7 | 8 | @Data 9 | @Builder 10 | @NoArgsConstructor 11 | @AllArgsConstructor 12 | public class DevChangeFailureRateOfPipeline { 13 | 14 | private String name; 15 | 16 | private String step; 17 | 18 | private int failedTimesOfPipeline; 19 | 20 | private int totalTimesOfPipeline; 21 | 22 | private float failureRate; 23 | 24 | } 25 | -------------------------------------------------------------------------------- /backend/src/main/java/heartbeat/controller/report/dto/response/DevMeanTimeToRecovery.java: -------------------------------------------------------------------------------- 1 | package heartbeat.controller.report.dto.response; 2 | 3 | import java.util.List; 4 | import lombok.AllArgsConstructor; 5 | import lombok.Builder; 6 | import lombok.Data; 7 | import lombok.NoArgsConstructor; 8 | 9 | @Data 10 | @NoArgsConstructor 11 | @AllArgsConstructor 12 | @Builder 13 | public class DevMeanTimeToRecovery { 14 | 15 | private AvgDevMeanTimeToRecovery avgDevMeanTimeToRecovery; 16 | 17 | private List devMeanTimeToRecoveryOfPipelines; 18 | 19 | } 20 | -------------------------------------------------------------------------------- /backend/src/main/java/heartbeat/controller/report/dto/response/DevMeanTimeToRecoveryOfPipeline.java: -------------------------------------------------------------------------------- 1 | package heartbeat.controller.report.dto.response; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Builder; 5 | import lombok.Data; 6 | import lombok.NoArgsConstructor; 7 | 8 | import java.math.BigDecimal; 9 | 10 | @Data 11 | @NoArgsConstructor 12 | @AllArgsConstructor 13 | @Builder 14 | public class DevMeanTimeToRecoveryOfPipeline { 15 | 16 | private String name; 17 | 18 | private String step; 19 | 20 | private BigDecimal timeToRecovery; 21 | 22 | } 23 | -------------------------------------------------------------------------------- /backend/src/main/java/heartbeat/controller/report/dto/response/ErrorInfo.java: -------------------------------------------------------------------------------- 1 | package heartbeat.controller.report.dto.response; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Builder; 5 | import lombok.Data; 6 | import lombok.NoArgsConstructor; 7 | 8 | @Data 9 | @NoArgsConstructor 10 | @AllArgsConstructor 11 | @Builder 12 | public class ErrorInfo { 13 | 14 | private int status; 15 | 16 | private String errorMessage; 17 | 18 | } 19 | -------------------------------------------------------------------------------- /backend/src/main/java/heartbeat/controller/report/dto/response/LeadTimeForChanges.java: -------------------------------------------------------------------------------- 1 | package heartbeat.controller.report.dto.response; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Builder; 5 | import lombok.Data; 6 | import lombok.NoArgsConstructor; 7 | 8 | import java.util.List; 9 | 10 | @Data 11 | @NoArgsConstructor 12 | @AllArgsConstructor 13 | @Builder 14 | public class LeadTimeForChanges { 15 | 16 | private List leadTimeForChangesOfPipelines; 17 | 18 | private AvgLeadTimeForChanges avgLeadTimeForChanges; 19 | 20 | } 21 | -------------------------------------------------------------------------------- /backend/src/main/java/heartbeat/controller/report/dto/response/LeadTimeForChangesOfPipelines.java: -------------------------------------------------------------------------------- 1 | package heartbeat.controller.report.dto.response; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Builder; 5 | import lombok.Data; 6 | import lombok.NoArgsConstructor; 7 | 8 | @Data 9 | @NoArgsConstructor 10 | @AllArgsConstructor 11 | @Builder 12 | public class LeadTimeForChangesOfPipelines { 13 | 14 | private String name; 15 | 16 | private String step; 17 | 18 | private Double prLeadTime; 19 | 20 | private Double pipelineLeadTime; 21 | 22 | private Double totalDelayTime; 23 | 24 | } 25 | -------------------------------------------------------------------------------- /backend/src/main/java/heartbeat/controller/report/dto/response/ReportMetricsError.java: -------------------------------------------------------------------------------- 1 | package heartbeat.controller.report.dto.response; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Builder; 5 | import lombok.Data; 6 | import lombok.NoArgsConstructor; 7 | 8 | @Data 9 | @NoArgsConstructor 10 | @AllArgsConstructor 11 | @Builder 12 | public class ReportMetricsError { 13 | 14 | private ErrorInfo boardMetricsError; 15 | 16 | private ErrorInfo pipelineMetricsError; 17 | 18 | private ErrorInfo sourceControlMetricsError; 19 | 20 | } 21 | -------------------------------------------------------------------------------- /backend/src/main/java/heartbeat/controller/report/dto/response/TotalTimeAndRecoveryTimes.java: -------------------------------------------------------------------------------- 1 | package heartbeat.controller.report.dto.response; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Builder; 5 | import lombok.Data; 6 | import lombok.NoArgsConstructor; 7 | 8 | @Data 9 | @NoArgsConstructor 10 | @AllArgsConstructor 11 | @Builder 12 | public class TotalTimeAndRecoveryTimes { 13 | 14 | private long totalTimeToRecovery; 15 | 16 | private int recoveryTimes; 17 | 18 | } 19 | -------------------------------------------------------------------------------- /backend/src/main/java/heartbeat/controller/report/dto/response/Velocity.java: -------------------------------------------------------------------------------- 1 | package heartbeat.controller.report.dto.response; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Builder; 5 | import lombok.Data; 6 | import lombok.NoArgsConstructor; 7 | 8 | @Data 9 | @NoArgsConstructor 10 | @AllArgsConstructor 11 | @Builder 12 | public class Velocity { 13 | 14 | private double velocityForSP; 15 | 16 | private int velocityForCards; 17 | 18 | } 19 | -------------------------------------------------------------------------------- /backend/src/main/java/heartbeat/controller/source/SourceType.java: -------------------------------------------------------------------------------- 1 | package heartbeat.controller.source; 2 | 3 | import heartbeat.exception.BadRequestException; 4 | import lombok.extern.log4j.Log4j2; 5 | 6 | @Log4j2 7 | public enum SourceType { 8 | 9 | GITHUB("GitHub"); 10 | 11 | public final String sourceType; 12 | 13 | SourceType(String sourceType) { 14 | this.sourceType = sourceType; 15 | } 16 | 17 | public static SourceType fromValue(String type) { 18 | return switch (type) { 19 | case "github" -> GITHUB; 20 | default -> { 21 | log.error("Failed to match Source type: {} ", type); 22 | throw new BadRequestException("Source type is incorrect."); 23 | } 24 | }; 25 | } 26 | 27 | } 28 | -------------------------------------------------------------------------------- /backend/src/main/java/heartbeat/exception/BadRequestException.java: -------------------------------------------------------------------------------- 1 | package heartbeat.exception; 2 | 3 | import lombok.Getter; 4 | 5 | @Getter 6 | public class BadRequestException extends BaseException { 7 | 8 | public BadRequestException(String message) { 9 | super(message, 400); 10 | } 11 | 12 | } 13 | -------------------------------------------------------------------------------- /backend/src/main/java/heartbeat/exception/BaseException.java: -------------------------------------------------------------------------------- 1 | package heartbeat.exception; 2 | 3 | import lombok.Getter; 4 | 5 | @Getter 6 | public abstract class BaseException extends RuntimeException { 7 | 8 | protected int status; 9 | 10 | public BaseException(String message, int status) { 11 | super(message); 12 | this.status = status; 13 | } 14 | 15 | } 16 | -------------------------------------------------------------------------------- /backend/src/main/java/heartbeat/exception/DecryptDataOrPasswordWrongException.java: -------------------------------------------------------------------------------- 1 | package heartbeat.exception; 2 | 3 | public class DecryptDataOrPasswordWrongException extends BaseException { 4 | 5 | public DecryptDataOrPasswordWrongException(String message, int statusCode) { 6 | super(message, statusCode); 7 | } 8 | 9 | } 10 | -------------------------------------------------------------------------------- /backend/src/main/java/heartbeat/exception/EncryptDecryptProcessException.java: -------------------------------------------------------------------------------- 1 | package heartbeat.exception; 2 | 3 | import org.springframework.http.HttpStatus; 4 | 5 | public class EncryptDecryptProcessException extends BaseException { 6 | 7 | public EncryptDecryptProcessException(String message) { 8 | super(message, HttpStatus.INTERNAL_SERVER_ERROR.value()); 9 | } 10 | 11 | } 12 | -------------------------------------------------------------------------------- /backend/src/main/java/heartbeat/exception/FileIOException.java: -------------------------------------------------------------------------------- 1 | package heartbeat.exception; 2 | 3 | import java.io.IOException; 4 | 5 | public class FileIOException extends BaseException { 6 | 7 | public FileIOException(IOException e) { 8 | super(String.format("File handle error: %s", e.getMessage()), 500); 9 | } 10 | 11 | } 12 | -------------------------------------------------------------------------------- /backend/src/main/java/heartbeat/exception/GenerateReportException.java: -------------------------------------------------------------------------------- 1 | package heartbeat.exception; 2 | 3 | import org.springframework.http.HttpStatus; 4 | 5 | public class GenerateReportException extends BaseException { 6 | 7 | public GenerateReportException(String message) { 8 | super(message, HttpStatus.INTERNAL_SERVER_ERROR.value()); 9 | } 10 | 11 | } 12 | -------------------------------------------------------------------------------- /backend/src/main/java/heartbeat/exception/GithubRepoEmptyException.java: -------------------------------------------------------------------------------- 1 | package heartbeat.exception; 2 | 3 | import lombok.Getter; 4 | import org.springframework.http.HttpStatus; 5 | 6 | @Getter 7 | public class GithubRepoEmptyException extends BaseException { 8 | 9 | public GithubRepoEmptyException(String message) { 10 | super(message, HttpStatus.BAD_REQUEST.value()); 11 | } 12 | 13 | } 14 | -------------------------------------------------------------------------------- /backend/src/main/java/heartbeat/exception/InternalServerErrorException.java: -------------------------------------------------------------------------------- 1 | package heartbeat.exception; 2 | 3 | import lombok.Getter; 4 | 5 | @Getter 6 | public class InternalServerErrorException extends BaseException { 7 | 8 | public InternalServerErrorException(String message) { 9 | super(message, 500); 10 | } 11 | 12 | } 13 | -------------------------------------------------------------------------------- /backend/src/main/java/heartbeat/exception/NoContentException.java: -------------------------------------------------------------------------------- 1 | package heartbeat.exception; 2 | 3 | import lombok.Getter; 4 | 5 | @Getter 6 | public class NoContentException extends BaseException { 7 | 8 | public NoContentException(String message) { 9 | super(message, 204); 10 | } 11 | 12 | } 13 | -------------------------------------------------------------------------------- /backend/src/main/java/heartbeat/exception/NotFoundException.java: -------------------------------------------------------------------------------- 1 | package heartbeat.exception; 2 | 3 | import lombok.Getter; 4 | 5 | @Getter 6 | public class NotFoundException extends BaseException { 7 | 8 | public NotFoundException(String message) { 9 | super(message, 404); 10 | } 11 | 12 | } 13 | -------------------------------------------------------------------------------- /backend/src/main/java/heartbeat/exception/PermissionDenyException.java: -------------------------------------------------------------------------------- 1 | package heartbeat.exception; 2 | 3 | import lombok.Getter; 4 | 5 | @Getter 6 | public class PermissionDenyException extends BaseException { 7 | 8 | public PermissionDenyException(String message) { 9 | super(message, 403); 10 | } 11 | 12 | } 13 | -------------------------------------------------------------------------------- /backend/src/main/java/heartbeat/exception/RequestFailedException.java: -------------------------------------------------------------------------------- 1 | package heartbeat.exception; 2 | 3 | import lombok.Getter; 4 | 5 | @Getter 6 | public class RequestFailedException extends BaseException { 7 | 8 | public RequestFailedException(int statusCode, String message) { 9 | super(String.format("Request failed with status statusCode %d, error: %s", statusCode, message), statusCode); 10 | } 11 | 12 | } 13 | -------------------------------------------------------------------------------- /backend/src/main/java/heartbeat/exception/RestApiErrorResponse.java: -------------------------------------------------------------------------------- 1 | package heartbeat.exception; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Data; 5 | 6 | @Data 7 | @AllArgsConstructor 8 | public class RestApiErrorResponse { 9 | 10 | private int status; 11 | 12 | private String message; 13 | 14 | private String hintInfo; 15 | 16 | RestApiErrorResponse(String message) { 17 | this.message = message; 18 | } 19 | 20 | } 21 | -------------------------------------------------------------------------------- /backend/src/main/java/heartbeat/exception/ServiceUnavailableException.java: -------------------------------------------------------------------------------- 1 | package heartbeat.exception; 2 | 3 | import lombok.Getter; 4 | 5 | @Getter 6 | public class ServiceUnavailableException extends BaseException { 7 | 8 | public ServiceUnavailableException(String message) { 9 | super(message, 503); 10 | } 11 | 12 | } 13 | -------------------------------------------------------------------------------- /backend/src/main/java/heartbeat/exception/UnauthorizedException.java: -------------------------------------------------------------------------------- 1 | package heartbeat.exception; 2 | 3 | import lombok.Getter; 4 | 5 | @Getter 6 | public class UnauthorizedException extends BaseException { 7 | 8 | public UnauthorizedException(String message) { 9 | super(message, 401); 10 | } 11 | 12 | } 13 | -------------------------------------------------------------------------------- /backend/src/main/java/heartbeat/handler/base/AsyncExceptionDTO.java: -------------------------------------------------------------------------------- 1 | package heartbeat.handler.base; 2 | 3 | import heartbeat.exception.BaseException; 4 | import lombok.AllArgsConstructor; 5 | import lombok.Data; 6 | 7 | @Data 8 | @AllArgsConstructor 9 | public class AsyncExceptionDTO { 10 | 11 | private String message; 12 | 13 | private int status; 14 | 15 | public AsyncExceptionDTO(BaseException e) { 16 | this.message = e.getMessage(); 17 | this.status = e.getStatus(); 18 | } 19 | 20 | } 21 | -------------------------------------------------------------------------------- /backend/src/main/java/heartbeat/handler/base/FIleType.java: -------------------------------------------------------------------------------- 1 | package heartbeat.handler.base; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Getter; 5 | 6 | @Getter 7 | @AllArgsConstructor 8 | public enum FIleType { 9 | 10 | ERROR("error", "error/"), REPORT("report", "report/"), 11 | METRICS_DATA_COMPLETED("metrics-data-completed", "metrics-data-completed/"); 12 | 13 | private final String type; 14 | 15 | private final String path; 16 | 17 | } 18 | -------------------------------------------------------------------------------- /backend/src/main/java/heartbeat/service/board/jira/AssigneeFilterMethod.java: -------------------------------------------------------------------------------- 1 | package heartbeat.service.board.jira; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Getter; 5 | 6 | @Getter 7 | @AllArgsConstructor 8 | public enum AssigneeFilterMethod { 9 | 10 | ASSIGNEE_FIELD_ID("assignee"), 11 | 12 | LAST_ASSIGNEE("lastAssignee"), 13 | 14 | HISTORICAL_ASSIGNEE("historicalAssignee"); 15 | 16 | private final String description; 17 | 18 | } 19 | -------------------------------------------------------------------------------- /backend/src/main/java/heartbeat/service/board/jira/JiraColumnResult.java: -------------------------------------------------------------------------------- 1 | package heartbeat.service.board.jira; 2 | 3 | import heartbeat.controller.board.dto.response.JiraColumnDTO; 4 | 5 | import java.util.List; 6 | 7 | import lombok.Builder; 8 | import lombok.Data; 9 | 10 | @Data 11 | @Builder 12 | public class JiraColumnResult { 13 | 14 | private List jiraColumnResponse; 15 | 16 | private List jiraColumns; 17 | 18 | } 19 | -------------------------------------------------------------------------------- /backend/src/main/java/heartbeat/service/codebase/ICodebase.java: -------------------------------------------------------------------------------- 1 | package heartbeat.service.codebase; 2 | 3 | import heartbeat.client.dto.codebase.github.PipelineLeadTime; 4 | import heartbeat.client.dto.pipeline.buildkite.DeployTimes; 5 | 6 | import java.util.List; 7 | import java.util.Map; 8 | 9 | public interface ICodebase { 10 | 11 | public List fetchPipelinesLeadTime(List deployTimes, 12 | Map repositories); 13 | 14 | } 15 | -------------------------------------------------------------------------------- /backend/src/main/java/heartbeat/service/report/CSVFileNameEnum.java: -------------------------------------------------------------------------------- 1 | package heartbeat.service.report; 2 | 3 | public enum CSVFileNameEnum { 4 | 5 | METRIC("./app/output/csv/metric"), BOARD("./app/output/csv/board"), PIPELINE("./app/output/csv/pipeline"); 6 | 7 | private final String value; 8 | 9 | CSVFileNameEnum(String value) { 10 | this.value = value; 11 | } 12 | 13 | public String getValue() { 14 | return value; 15 | } 16 | 17 | } 18 | -------------------------------------------------------------------------------- /backend/src/main/java/heartbeat/service/report/ICardFieldDisplayName.java: -------------------------------------------------------------------------------- 1 | package heartbeat.service.report; 2 | 3 | public interface ICardFieldDisplayName { 4 | 5 | String getDisplayName(); 6 | 7 | } 8 | -------------------------------------------------------------------------------- /backend/src/main/java/heartbeat/service/report/MetricsDataDTO.java: -------------------------------------------------------------------------------- 1 | package heartbeat.service.report; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Getter; 5 | 6 | @AllArgsConstructor 7 | @Getter 8 | public class MetricsDataDTO { 9 | 10 | boolean isBoardReady; 11 | 12 | boolean isDoraReady; 13 | 14 | boolean isAllMetricsReady; 15 | 16 | } 17 | -------------------------------------------------------------------------------- /backend/src/main/java/heartbeat/service/report/calculator/VelocityCalculator.java: -------------------------------------------------------------------------------- 1 | package heartbeat.service.report.calculator; 2 | 3 | import heartbeat.controller.board.dto.response.CardCollection; 4 | import heartbeat.controller.report.dto.response.Velocity; 5 | import lombok.RequiredArgsConstructor; 6 | import org.springframework.stereotype.Component; 7 | 8 | @RequiredArgsConstructor 9 | @Component 10 | public class VelocityCalculator { 11 | 12 | public Velocity calculateVelocity(CardCollection cardCollection) { 13 | return Velocity.builder() 14 | .velocityForSP(cardCollection.getStoryPointSum()) 15 | .velocityForCards(cardCollection.getCardsNumber()) 16 | .build(); 17 | } 18 | 19 | } 20 | -------------------------------------------------------------------------------- /backend/src/main/java/heartbeat/service/source/github/model/PipelineInfoOfRepository.java: -------------------------------------------------------------------------------- 1 | package heartbeat.service.source.github.model; 2 | 3 | import heartbeat.client.dto.pipeline.buildkite.DeployInfo; 4 | import lombok.Builder; 5 | import lombok.Data; 6 | 7 | import java.util.List; 8 | 9 | @Builder 10 | @Data 11 | public class PipelineInfoOfRepository { 12 | 13 | private String repository; 14 | 15 | private List passedDeploy; 16 | 17 | private String pipelineName; 18 | 19 | private String pipelineStep; 20 | 21 | } 22 | -------------------------------------------------------------------------------- /backend/src/main/java/heartbeat/util/GithubUtil.java: -------------------------------------------------------------------------------- 1 | package heartbeat.util; 2 | 3 | public interface GithubUtil { 4 | 5 | static String getGithubUrlFullName(String url) { 6 | if (url == null) { 7 | return ""; 8 | } 9 | if (url.startsWith("https://")) { 10 | return url.replaceFirst("^(.*?github.com/)", "").replaceFirst("(\\.git)?$", ""); 11 | } 12 | else { 13 | return url.replaceFirst("^(.*?:)", "").replaceFirst("(\\.git)?$", ""); 14 | } 15 | } 16 | 17 | } 18 | -------------------------------------------------------------------------------- /backend/src/main/java/heartbeat/util/SystemUtil.java: -------------------------------------------------------------------------------- 1 | package heartbeat.util; 2 | 3 | import lombok.RequiredArgsConstructor; 4 | import org.springframework.stereotype.Component; 5 | 6 | import java.util.Map; 7 | 8 | @Component 9 | @RequiredArgsConstructor 10 | public class SystemUtil { 11 | 12 | public Map getEnvMap() { 13 | return System.getenv(); 14 | } 15 | 16 | } 17 | -------------------------------------------------------------------------------- /backend/src/main/java/heartbeat/util/TokenUtil.java: -------------------------------------------------------------------------------- 1 | package heartbeat.util; 2 | 3 | public interface TokenUtil { 4 | 5 | static String mask(String token) { 6 | String prefix = token.substring(0, 4); 7 | String suffix = token.substring(token.length() - 4); 8 | return prefix + "*".repeat(Math.max(0, token.length() - 8)) + suffix; 9 | } 10 | 11 | } 12 | -------------------------------------------------------------------------------- /backend/src/main/java/heartbeat/util/ValueUtil.java: -------------------------------------------------------------------------------- 1 | package heartbeat.util; 2 | 3 | import java.util.function.Function; 4 | 5 | public interface ValueUtil { 6 | 7 | static R getValueOrNull(T object, Function getter) { 8 | return object != null ? getter.apply(object) : null; 9 | } 10 | 11 | static T valueOrDefault(T defaultValue, T value) { 12 | return value == null ? defaultValue : value; 13 | } 14 | 15 | } 16 | -------------------------------------------------------------------------------- /backend/src/main/resources/application-e2e.yml: -------------------------------------------------------------------------------- 1 | server: 2 | port: 4322 3 | servlet: 4 | context-path: /api/v1 5 | logging: 6 | level: 7 | root: INFO 8 | 9 | github: 10 | url: https://api.github.com 11 | 12 | jira: 13 | url: https://%s.atlassian.net 14 | 15 | buildKite: 16 | url: https://api.buildkite.com 17 | -------------------------------------------------------------------------------- /backend/src/main/resources/application-local.yml: -------------------------------------------------------------------------------- 1 | server: 2 | port: 4322 3 | servlet: 4 | context-path: /api/v1 5 | logging: 6 | level: 7 | root: INFO 8 | 9 | github: 10 | url: https://api.github.com 11 | 12 | jira: 13 | url: https://%s.atlassian.net 14 | 15 | buildKite: 16 | url: https://api.buildkite.com 17 | -------------------------------------------------------------------------------- /backend/src/main/resources/application.yml: -------------------------------------------------------------------------------- 1 | spring: 2 | profiles: 3 | active: default 4 | 5 | server: 6 | port: 4322 7 | servlet: 8 | context-path: /api/v1 9 | logging: 10 | level: 11 | root: INFO 12 | 13 | github: 14 | url: https://api.github.com 15 | 16 | jira: 17 | url: https://%s.atlassian.net 18 | 19 | buildKite: 20 | url: https://api.buildkite.com 21 | 22 | callback: 23 | interval: 10 24 | 25 | springdoc: 26 | swagger-ui: 27 | path: /docs 28 | api-docs: 29 | path: /api-docs 30 | 31 | heartbeat: 32 | swagger: 33 | host: ${SWAGGER_HOST:http://localhost:4322} 34 | version: 1.1.6 35 | 36 | -------------------------------------------------------------------------------- /backend/src/main/resources/pmd.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /backend/src/test/java/heartbeat/TestFixtures.java: -------------------------------------------------------------------------------- 1 | package heartbeat; 2 | 3 | public class TestFixtures { 4 | 5 | public static final String GITHUB_TOKEN = "ghp_" + "12345j".repeat(6); 6 | 7 | public static final String BUILDKITE_TOKEN = "bkua_6xxxafcc3bxxxxxxb8xxx8d8dxxxf7897cc8b2f1"; 8 | 9 | public static final String GITHUB_REPOSITORY = "git@github.com:fake/repo.git"; 10 | 11 | } 12 | -------------------------------------------------------------------------------- /backend/src/test/java/heartbeat/client/component/ProductJiraUriGeneratorTest.java: -------------------------------------------------------------------------------- 1 | package heartbeat.client.component; 2 | 3 | import org.junit.jupiter.api.Test; 4 | import org.springframework.test.util.ReflectionTestUtils; 5 | 6 | import java.net.URI; 7 | 8 | import static org.junit.jupiter.api.Assertions.assertEquals; 9 | 10 | class ProductJiraUriGeneratorTest { 11 | 12 | @Test 13 | public void shouldReturnCorrectUri() { 14 | ProductJiraUriGenerator generator = new ProductJiraUriGenerator(); 15 | ReflectionTestUtils.setField(generator, "url", "https://%s.atlassian.net"); 16 | 17 | URI uri = generator.getUri("site"); 18 | 19 | assertEquals("https://site.atlassian.net", uri.toString()); 20 | } 21 | 22 | } 23 | -------------------------------------------------------------------------------- /backend/src/test/java/heartbeat/controller/board/BoardRequestFixture.java: -------------------------------------------------------------------------------- 1 | package heartbeat.controller.board; 2 | 3 | import heartbeat.controller.board.dto.request.BoardRequestParam; 4 | 5 | import static heartbeat.service.jira.JiraBoardConfigDTOFixture.BOARD_ID; 6 | 7 | public class BoardRequestFixture { 8 | 9 | public static final String BOARD_ID = "unknown"; 10 | 11 | public static BoardRequestParam.BoardRequestParamBuilder BOARD_REQUEST_BUILDER() { 12 | return BoardRequestParam.builder() 13 | .boardId(BOARD_ID) 14 | .projectKey("project key") 15 | .site("site") 16 | .token("token") 17 | .startTime("1672556350000") 18 | .endTime("1676908799000"); 19 | } 20 | 21 | } 22 | -------------------------------------------------------------------------------- /backend/src/test/java/heartbeat/controller/board/dto/request/BoardVerifyRequestFixture.java: -------------------------------------------------------------------------------- 1 | package heartbeat.controller.board.dto.request; 2 | 3 | public class BoardVerifyRequestFixture { 4 | 5 | public static final String PROJECT_KEY = "ADM"; 6 | 7 | public static BoardVerifyRequestParam.BoardVerifyRequestParamBuilder BOARD_VERIFY_REQUEST_BUILDER() { 8 | return BoardVerifyRequestParam.builder().boardId("unknown").site("site").token("token"); 9 | } 10 | 11 | } 12 | -------------------------------------------------------------------------------- /backend/src/test/java/heartbeat/controller/pipeline/pipelineInfoData.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "id": "payment-selector-ui", 4 | "name": "payment-selector-ui", 5 | "orgId": "orgId", 6 | "orgName": "orgName", 7 | "repository": "https://github.com/XXXX-fs/fs-platform-payment-selector-ui.git", 8 | "steps": ["step1"] 9 | } 10 | ] 11 | -------------------------------------------------------------------------------- /backend/src/test/java/heartbeat/decoder/ResponseMockUtil.java: -------------------------------------------------------------------------------- 1 | package heartbeat.decoder; 2 | 3 | import feign.Request; 4 | import feign.Request.HttpMethod; 5 | import feign.Response; 6 | import java.util.Collection; 7 | import java.util.HashMap; 8 | 9 | public class ResponseMockUtil { 10 | 11 | Response getMockResponse(int statusCode) { 12 | return Response.builder() 13 | .status(statusCode) 14 | .request(Request.create(HttpMethod.GET, "", new HashMap>(), null, null, null)) 15 | .build(); 16 | } 17 | 18 | } 19 | -------------------------------------------------------------------------------- /backend/src/test/java/heartbeat/exception/CustomFeignClientException.java: -------------------------------------------------------------------------------- 1 | package heartbeat.exception; 2 | 3 | import feign.FeignException; 4 | 5 | public class CustomFeignClientException extends FeignException { 6 | 7 | public CustomFeignClientException(int status, String message) { 8 | super(status, message); 9 | } 10 | 11 | } 12 | -------------------------------------------------------------------------------- /backend/src/test/java/heartbeat/handler/base/AsyncDataBaseHandlerTest.java: -------------------------------------------------------------------------------- 1 | package heartbeat.handler.base; 2 | 3 | import org.junit.jupiter.api.Test; 4 | 5 | import java.io.File; 6 | 7 | import static org.junit.jupiter.api.Assertions.assertFalse; 8 | 9 | class AsyncDataBaseHandlerTest { 10 | 11 | AsyncDataBaseHandler asyncDataBaseHandler = new AsyncDataBaseHandler(); 12 | 13 | @Test 14 | void shouldReturnFalseGivenCreateFileThrowExceptionWhenTryLock() { 15 | File file = new File("./app/output/lock"); 16 | 17 | boolean result = asyncDataBaseHandler.tryLock(file); 18 | 19 | assertFalse(result); 20 | } 21 | 22 | } 23 | -------------------------------------------------------------------------------- /backend/src/test/java/heartbeat/handler/base/AsyncExceptionTest.java: -------------------------------------------------------------------------------- 1 | package heartbeat.handler.base; 2 | 3 | import org.junit.jupiter.api.Test; 4 | 5 | import static org.junit.jupiter.api.Assertions.assertEquals; 6 | 7 | class AsyncExceptionDTOTest { 8 | 9 | @Test 10 | void testAsyncExceptionDTOInheritance() { 11 | String expectedMessage = "Test Message"; 12 | int expectedStatus = 404; 13 | 14 | AsyncExceptionDTO baseException = new AsyncExceptionDTO(expectedMessage, expectedStatus); 15 | 16 | assertEquals(expectedMessage, baseException.getMessage()); 17 | assertEquals(expectedStatus, baseException.getStatus()); 18 | } 19 | 20 | } 21 | -------------------------------------------------------------------------------- /backend/src/test/java/heartbeat/tools/TimeUtils.java: -------------------------------------------------------------------------------- 1 | package heartbeat.tools; 2 | 3 | import java.time.LocalDateTime; 4 | import java.time.ZoneOffset; 5 | 6 | public class TimeUtils { 7 | 8 | public static Long mockTimeStamp(int year, int month, int day, int hour, int minute, int second) { 9 | return LocalDateTime.of(year, month, day, hour, minute, second).toInstant(ZoneOffset.UTC).toEpochMilli(); 10 | } 11 | 12 | } 13 | -------------------------------------------------------------------------------- /backend/src/test/java/heartbeat/util/TokenUtilTest.java: -------------------------------------------------------------------------------- 1 | package heartbeat.util; 2 | 3 | import org.junit.jupiter.api.Test; 4 | 5 | import static org.junit.jupiter.api.Assertions.assertEquals; 6 | 7 | class TokenUtilTest { 8 | 9 | @Test 10 | void shouldReturnTokenBeingMasked() { 11 | String mockToken = "1234ABCDE5678"; 12 | String maskToken = TokenUtil.mask(mockToken); 13 | assertEquals("1234*****5678", maskToken); 14 | } 15 | 16 | } 17 | -------------------------------------------------------------------------------- /docs/.editorconfig: -------------------------------------------------------------------------------- 1 | # https://EditorConfig.org 2 | 3 | # top-most EditorConfig file 4 | root = true 5 | 6 | [*] 7 | charset = utf-8 8 | end_of_line = lf 9 | indent_size = 2 10 | indent_style = tab 11 | insert_final_newline = true 12 | trim_trailing_whitespace = false 13 | 14 | [{.*,*.md,*.mdx,*.json,*.toml,*.yml,}] 15 | indent_style = space 16 | -------------------------------------------------------------------------------- /docs/.eslintignore: -------------------------------------------------------------------------------- 1 | dist 2 | node_modules 3 | .github 4 | .changeset 5 | -------------------------------------------------------------------------------- /docs/.npmrc: -------------------------------------------------------------------------------- 1 | shell-emulator=true 2 | -------------------------------------------------------------------------------- /docs/.nvmrc: -------------------------------------------------------------------------------- 1 | 18.14.1 2 | -------------------------------------------------------------------------------- /docs/.prettierignore: -------------------------------------------------------------------------------- 1 | dist 2 | node_modules 3 | .github 4 | .changeset 5 | *.md 6 | *.mdx 7 | pnpm-lock.yaml 8 | -------------------------------------------------------------------------------- /docs/.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "printWidth": 100, 3 | "semi": true, 4 | "singleQuote": true, 5 | "tabWidth": 2, 6 | "trailingComma": "es5", 7 | "useTabs": true, 8 | "plugins": ["prettier-plugin-astro"], 9 | "overrides": [ 10 | { 11 | "files": [".*", "*.json", "*.md", "*.mdx", "*.toml", "*.yml"], 12 | "options": { 13 | "useTabs": false 14 | } 15 | }, 16 | { 17 | "files": ["**/*.astro"], 18 | "options": { 19 | "parser": "astro" 20 | } 21 | } 22 | ] 23 | } 24 | -------------------------------------------------------------------------------- /docs/integrations/utils/isMDX.ts: -------------------------------------------------------------------------------- 1 | import type { VFile } from 'vfile'; 2 | 3 | export function isMDXFile(file: VFile) { 4 | return file.history[0].endsWith('.mdx'); 5 | } 6 | -------------------------------------------------------------------------------- /docs/public/assets/arc.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thoughtworks/HeartBeat/702cb41d75fce4670b4c57cb69aa538caeee88ae/docs/public/assets/arc.webp -------------------------------------------------------------------------------- /docs/public/assets/full-logo-dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thoughtworks/HeartBeat/702cb41d75fce4670b4c57cb69aa538caeee88ae/docs/public/assets/full-logo-dark.png -------------------------------------------------------------------------------- /docs/public/assets/full-logo-light.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thoughtworks/HeartBeat/702cb41d75fce4670b4c57cb69aa538caeee88ae/docs/public/assets/full-logo-light.png -------------------------------------------------------------------------------- /docs/public/assets/logomark-dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thoughtworks/HeartBeat/702cb41d75fce4670b4c57cb69aa538caeee88ae/docs/public/assets/logomark-dark.png -------------------------------------------------------------------------------- /docs/public/assets/logomark-light.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thoughtworks/HeartBeat/702cb41d75fce4670b4c57cb69aa538caeee88ae/docs/public/assets/logomark-light.png -------------------------------------------------------------------------------- /docs/public/assets/rays.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thoughtworks/HeartBeat/702cb41d75fce4670b4c57cb69aa538caeee88ae/docs/public/assets/rays.webp -------------------------------------------------------------------------------- /docs/public/default-og-image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thoughtworks/HeartBeat/702cb41d75fce4670b4c57cb69aa538caeee88ae/docs/public/default-og-image.png -------------------------------------------------------------------------------- /docs/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thoughtworks/HeartBeat/702cb41d75fce4670b4c57cb69aa538caeee88ae/docs/public/favicon.ico -------------------------------------------------------------------------------- /docs/public/houston_chef.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thoughtworks/HeartBeat/702cb41d75fce4670b4c57cb69aa538caeee88ae/docs/public/houston_chef.webp -------------------------------------------------------------------------------- /docs/public/logos/alpine-js.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docs/public/logos/astro-image.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /docs/public/logos/builderio.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /docs/public/logos/cloudflare-pages.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /docs/public/logos/crystallize.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /docs/public/logos/datocms.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /docs/public/logos/decap-cms.svg: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | -------------------------------------------------------------------------------- /docs/public/logos/gatsby.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /docs/public/logos/ghost.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thoughtworks/HeartBeat/702cb41d75fce4670b4c57cb69aa538caeee88ae/docs/public/logos/ghost.png -------------------------------------------------------------------------------- /docs/public/logos/github.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docs/public/logos/google-cloud.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /docs/public/logos/heroku.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /docs/public/logos/hygraph.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /docs/public/logos/jekyll.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thoughtworks/HeartBeat/702cb41d75fce4670b4c57cb69aa538caeee88ae/docs/public/logos/jekyll.png -------------------------------------------------------------------------------- /docs/public/logos/keystatic.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docs/public/logos/keystonejs.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /docs/public/logos/lit.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /docs/public/logos/mdx.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /docs/public/logos/netlify.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docs/public/logos/node.svg: -------------------------------------------------------------------------------- 1 | 4 | -------------------------------------------------------------------------------- /docs/public/logos/nuxtjs.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /docs/public/logos/payload.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /docs/public/logos/preact.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /docs/public/logos/prefetch.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /docs/public/logos/render.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /docs/public/logos/sitemap.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /docs/public/logos/space.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /docs/public/logos/storyblok.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /docs/public/logos/tailwind.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /docs/public/logos/tina-cms.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /docs/public/logos/vercel.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docs/public/logos/vue.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /docs/public/logos/vuepress.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thoughtworks/HeartBeat/702cb41d75fce4670b4c57cb69aa538caeee88ae/docs/public/logos/vuepress.png -------------------------------------------------------------------------------- /docs/public/logos/xata.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /docs/public/make-scrollable-code-focusable.js: -------------------------------------------------------------------------------- 1 | Array.from(document.getElementsByTagName('pre')).forEach((element) => { 2 | element.setAttribute('tabindex', '0'); 3 | }); 4 | -------------------------------------------------------------------------------- /docs/public/tutorial/check-list.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /docs/public/tutorial/minimal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thoughtworks/HeartBeat/702cb41d75fce4670b4c57cb69aa538caeee88ae/docs/public/tutorial/minimal.png -------------------------------------------------------------------------------- /docs/public/tutorial/puzzle-piece.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /docs/public/tutorial/question-mark.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /docs/public/videos/stores-example.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thoughtworks/HeartBeat/702cb41d75fce4670b4c57cb69aa538caeee88ae/docs/public/videos/stores-example.mp4 -------------------------------------------------------------------------------- /docs/scripts/lib/translation-status/utils.ts: -------------------------------------------------------------------------------- 1 | import fs from 'fs'; 2 | 3 | export function tryGetFrontMatterBlock(filePath: string): string { 4 | const contents = fs.readFileSync(filePath, 'utf8'); 5 | const matches = contents.match(/^\s*---([\S\s]*?)\n---/); 6 | if (!matches) return ''; 7 | return matches[1]; 8 | } 9 | 10 | export function toUtcString(date: string) { 11 | return new Date(date).toISOString(); 12 | } 13 | -------------------------------------------------------------------------------- /docs/scripts/translation-status.ts: -------------------------------------------------------------------------------- 1 | import languages from '../src/i18n/languages'; 2 | import { TranslationStatusBuilder } from './lib/translation-status/builder'; 3 | 4 | const translationStatusBuilder = new TranslationStatusBuilder({ 5 | pageSourceDir: './src/content/docs', 6 | oldTranslationDir: './old-translations', 7 | htmlOutputFilePath: './dist/translation-status/index.html', 8 | sourceLanguage: 'en', 9 | targetLanguages: Object.keys(languages) 10 | .filter((lang) => lang !== 'en') 11 | .sort(), 12 | languageLabels: languages, 13 | githubRepo: process.env.GITHUB_REPOSITORY || 'withastro/docs', 14 | githubToken: process.env.GITHUB_TOKEN, 15 | }); 16 | 17 | await translationStatusBuilder.run(); 18 | -------------------------------------------------------------------------------- /docs/src/components/ContributorList.astro: -------------------------------------------------------------------------------- 1 | --- 2 | import { getAllContributors } from '~/util/getContributors'; 3 | import FacePile from './FacePile.astro'; 4 | 5 | export interface Props { 6 | githubRepo?: `${string}/${string}`; 7 | } 8 | 9 | const { githubRepo = 'withastro/docs' } = Astro.props as Props; 10 | 11 | const contributors = await getAllContributors(githubRepo); 12 | --- 13 | 14 | 15 | -------------------------------------------------------------------------------- /docs/src/components/DontEditWarning.astro: -------------------------------------------------------------------------------- 1 | --- 2 | import Aside from './Aside.astro'; 3 | const isDevMode = import.meta.env.DEV; 4 | --- 5 | 6 | { 7 | isDevMode && ( 8 | 12 | ) 13 | } 14 | -------------------------------------------------------------------------------- /docs/src/components/FallbackNotice.astro: -------------------------------------------------------------------------------- 1 | --- 2 | import Aside from './Aside.astro'; 3 | import UIString from './UIString.astro'; 4 | --- 5 | 6 | 12 | -------------------------------------------------------------------------------- /docs/src/components/Header/SidebarToggle.css: -------------------------------------------------------------------------------- 1 | #menu-toggle { 2 | --border-color-default: transparent; 3 | margin-inline-end: -0.25rem; 4 | transition-property: border-color, background-color, color; 5 | } 6 | 7 | .mobile-sidebar-toggle #menu-toggle { 8 | --background-color-default: var(--theme-dim-lighter); 9 | --text-color-default: var(--text-color-hocus); 10 | --border-color-default: var(--border-color-hocus); 11 | } 12 | 13 | @media (min-width: 50em) { 14 | #menu-toggle { 15 | display: none; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /docs/src/components/Header/SkipToContent.astro: -------------------------------------------------------------------------------- 1 | --- 2 | import UIString from '../UIString.astro'; 3 | --- 4 | 5 | 8 | 9 | 27 | -------------------------------------------------------------------------------- /docs/src/components/LoopingVideo.astro: -------------------------------------------------------------------------------- 1 | --- 2 | interface Props { 3 | sources: { 4 | src: string; 5 | type: 'video/mp4' | 'video/webm'; 6 | }[]; 7 | } 8 | 9 | const { sources } = Astro.props as Props; 10 | --- 11 | 12 | 15 | -------------------------------------------------------------------------------- /docs/src/components/TabGroup/InstallGuideTabGroup.astro: -------------------------------------------------------------------------------- 1 | --- 2 | import './TabGroup.css'; 3 | import UIString from '../UIString.astro'; 4 | import { getLanguageFromURL } from '../../util'; 5 | 6 | const currentPage = new URL(Astro.request.url).pathname; 7 | const lang = getLanguageFromURL(currentPage); 8 | const segments = currentPage.split('/'); 9 | --- 10 | 11 | 19 | -------------------------------------------------------------------------------- /docs/src/components/UIString.astro: -------------------------------------------------------------------------------- 1 | --- 2 | import type { UIDictionaryKeys } from '../i18n/translation-checkers'; 3 | import { useTranslations } from '../i18n/util'; 4 | 5 | export interface Props { 6 | key: UIDictionaryKeys; 7 | } 8 | --- 9 | 10 | {useTranslations(Astro)(Astro.props.key)} 11 | -------------------------------------------------------------------------------- /docs/src/components/internal/Spoiler.tsx: -------------------------------------------------------------------------------- 1 | import type { ComponentChildren } from 'preact'; 2 | import { useState } from 'preact/hooks'; 3 | 4 | /** FOR INTERNAL USE ONLY. USE `Spoiler.astro` INSTEAD OF THIS. */ 5 | export default function Spoiler({ children }: { children: ComponentChildren }) { 6 | const [checked, setChecked] = useState(false); 7 | return ( 8 | <> 9 | setChecked(true)} 15 | /> 16 | {children} 17 | 18 | ); 19 | } 20 | -------------------------------------------------------------------------------- /docs/src/components/tabs/AstroJSXTabs.astro: -------------------------------------------------------------------------------- 1 | --- 2 | import Tabs from './Tabs'; 3 | --- 4 | 5 | 6 | JSX 7 | Astro 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /docs/src/components/tabs/AstroVueTabs.astro: -------------------------------------------------------------------------------- 1 | --- 2 | import Tabs from './Tabs'; 3 | --- 4 | 5 | 6 | Vue 7 | Astro 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /docs/src/components/tabs/JavascriptFlavorTabs.astro: -------------------------------------------------------------------------------- 1 | --- 2 | import Tabs from './Tabs'; 3 | --- 4 | 5 | 6 | JavaScript 7 | TypeScript 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /docs/src/components/tabs/PackageManagerTabs.astro: -------------------------------------------------------------------------------- 1 | --- 2 | import Tabs from './Tabs'; 3 | --- 4 | 5 | 6 | npm 7 | pnpm 8 | Yarn 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /docs/src/components/tabs/StaticSsrTabs.astro: -------------------------------------------------------------------------------- 1 | --- 2 | import Tabs from './Tabs'; 3 | --- 4 | 5 | 6 | Static 7 | SSR 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /docs/src/components/tabs/TabListItem.astro: -------------------------------------------------------------------------------- 1 | --- 2 | export interface Props { 3 | id: string; 4 | initial?: boolean; 5 | } 6 | 7 | const { id, initial } = Astro.props; 8 | --- 9 | 10 |
  • 11 | 12 | 13 | 14 |
  • 15 | -------------------------------------------------------------------------------- /docs/src/components/tabs/TypeScriptSettingTabs.astro: -------------------------------------------------------------------------------- 1 | --- 2 | import Tabs from './Tabs'; 3 | --- 4 | 5 | 6 | base ("relaxed") 7 | strict or strictest 8 | custom 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /docs/src/components/tabs/store.ts: -------------------------------------------------------------------------------- 1 | import { atom, map } from 'nanostores'; 2 | 3 | type TabStore = { 4 | [key: string]: { 5 | curr: string; 6 | }; 7 | }; 8 | 9 | export const tabId = atom(0); 10 | export const tabStore = map({}); 11 | 12 | export function genTabId() { 13 | const id = tabId.get(); 14 | tabId.set(id + 1); 15 | return id; 16 | } 17 | -------------------------------------------------------------------------------- /docs/src/components/tutorial/Blanks.astro: -------------------------------------------------------------------------------- 1 |
    2 | 3 | 23 | -------------------------------------------------------------------------------- /docs/src/components/tutorial/Lede.astro: -------------------------------------------------------------------------------- 1 |

    2 | 3 |

    4 | 5 | 10 | -------------------------------------------------------------------------------- /docs/src/components/tutorial/houston-happy.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thoughtworks/HeartBeat/702cb41d75fce4670b4c57cb69aa538caeee88ae/docs/src/components/tutorial/houston-happy.webp -------------------------------------------------------------------------------- /docs/src/config.ts: -------------------------------------------------------------------------------- 1 | export const OPEN_GRAPH = { 2 | twitter: 'astrodotbuild', 3 | }; 4 | -------------------------------------------------------------------------------- /docs/src/content.ts: -------------------------------------------------------------------------------- 1 | import { getCollection } from 'astro:content'; 2 | import { 3 | createIsLangEntry, 4 | isEnglishEntry, 5 | isRecipeEntry, 6 | isTutorialEntry, 7 | } from './content/config'; 8 | 9 | const isKoreanEntry = createIsLangEntry('ko'); 10 | 11 | export const allPages = await getCollection('docs', (entry) => { 12 | if (import.meta.env.PUBLIC_TWO_LANG) { 13 | // Build for two languages only to speed up Astro's smoke tests 14 | return isEnglishEntry(entry) || isKoreanEntry(entry); 15 | } else { 16 | return true; 17 | } 18 | }); 19 | export const tutorialPages = allPages.filter(isTutorialEntry); 20 | export const recipePages = allPages.filter(isRecipeEntry); 21 | export const englishPages = allPages.filter(isEnglishEntry); 22 | -------------------------------------------------------------------------------- /docs/src/content/docs/en/arch/architecture.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: Architecture 3 | description: Architecture 4 | --- 5 | -------------------------------------------------------------------------------- /docs/src/content/docs/en/biz/business-context.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: Business Context 3 | description: Business Context 4 | --- 5 | -------------------------------------------------------------------------------- /docs/src/content/docs/en/commons/useful-scripts-and-tools.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: Useful scripts & tools 3 | description: Useful scripts & tools 4 | --- 5 | -------------------------------------------------------------------------------- /docs/src/content/docs/en/contribute.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: Contribute to Heartbeat 3 | description: A basic intro to Heartbeat. 4 | i18nReady: true 5 | --- 6 | -------------------------------------------------------------------------------- /docs/src/content/docs/en/guides/guideline-and-best-practices.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: Guideline & Best Practice 3 | description: Guideline & Best Practice 4 | --- 5 | -------------------------------------------------------------------------------- /docs/src/content/docs/en/guides/start-e2e-test-in-local.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: How to start e2e test in local environment 3 | --- 4 | 5 | **Solution** 6 | 7 | - In the backend directory, start backend. 8 | ``` 9 | ./gradlew bootRun --args='--spring.profiles.active=local' 10 | ``` 11 | - In the frontend directory, start frontend. 12 | ``` 13 | pnpm start 14 | ``` 15 | - In the frontend directory, start cypress. 16 | ``` 17 | pnpm e2e:local 18 | ``` 19 | -------------------------------------------------------------------------------- /docs/src/content/docs/en/onboarding/conventions.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: Conventions 3 | description: Conventions 4 | --- 5 | -------------------------------------------------------------------------------- /docs/src/content/docs/en/onboarding/glossary.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: Glossary 3 | description: Glossary 4 | --- 5 | 6 | ## Word list 7 | 8 | | Word | Explanation | 9 | | :--- | :----------------------------- | 10 | | HB | Heartbeat | 11 | | DF | Deploy Frequency | 12 | | LTC | Lead Time for Changes | 13 | | MTTR | Mean Time to Recover | 14 | | CFT | Change Failure Rate | 15 | | DORA | DevOps Research and Assessment | 16 | | GF | General Function | 17 | | SLA | Service Level Agreement | 18 | -------------------------------------------------------------------------------- /docs/src/content/docs/en/onboarding/onboarding-flow.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: Onboarding Flow 3 | description: Onboarding Flow 4 | --- 5 | -------------------------------------------------------------------------------- /docs/src/content/docs/en/teams/team-activity-calendar.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: Team Activity Calendar 3 | description: Team Activity Calendar 4 | --- 5 | -------------------------------------------------------------------------------- /docs/src/content/docs/en/teams/team-infos.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: Team Info 3 | description: Team Info 4 | --- 5 | -------------------------------------------------------------------------------- /docs/src/content/docs/en/tests/test-strategies.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: Test Strategies 3 | description: Test Strategies 4 | --- 5 | -------------------------------------------------------------------------------- /docs/src/docs-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thoughtworks/HeartBeat/702cb41d75fce4670b4c57cb69aa538caeee88ae/docs/src/docs-logo.png -------------------------------------------------------------------------------- /docs/src/env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | /// 3 | -------------------------------------------------------------------------------- /docs/src/i18n/bcp-normalize.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Simplified method for normalizing language tags. 3 | * We use `bcp-47-normalize` elsewhere, but this is a little presumptuous 4 | * and strips region identifiers from `pt-BR` and `zh-CN`. 5 | * @param tag Language tag to normalize, e.g. `pt-br` → `pt-BR` 6 | */ 7 | 8 | export function normalizeLangTag(tag: string) { 9 | if (!tag.includes('-')) return tag.toLowerCase(); 10 | const [lang, region] = tag.split('-'); 11 | return lang.toLowerCase() + '-' + region.toUpperCase(); 12 | } 13 | -------------------------------------------------------------------------------- /docs/src/i18n/en/docsearch.ts: -------------------------------------------------------------------------------- 1 | import { DocSearchDictionary } from '../translation-checkers'; 2 | 3 | export default DocSearchDictionary({ 4 | button: 'Search', 5 | placeholder: 'Search docs', 6 | shortcutLabel: 'Press / to search', 7 | resultsFooterLede: 'Looking for an Astro integration or theme? Need more help?', 8 | resultsFooterIntegrations: 'Astro integrations directory', 9 | resultsFooterThemes: 'Astro themes showcase', 10 | resultsFooterDiscord: 'Join us on Discord', 11 | modal: {}, 12 | }); 13 | -------------------------------------------------------------------------------- /docs/src/layouts/RecipeLayout.astro: -------------------------------------------------------------------------------- 1 | --- 2 | import type { MarkdownHeading } from 'astro'; 3 | import RecipesNav from '~/components/RecipesNav.astro'; 4 | import UIString from '~/components/UIString.astro'; 5 | import type { RecipeEntry } from '~/content/config'; 6 | import MainLayout from './MainLayout.astro'; 7 | 8 | export interface Props { 9 | content: RecipeEntry['data']; 10 | headings: MarkdownHeading[]; 11 | } 12 | --- 13 | 14 | 15 | 16 |
    17 |

    18 | 19 |
    20 | 21 | 28 | -------------------------------------------------------------------------------- /docs/src/pages/[...enRedirectSlug].astro: -------------------------------------------------------------------------------- 1 | --- 2 | // Redirect from base to /en/ pages 3 | import { englishPages } from '~/content'; 4 | import { stripLangFromSlug } from '~/util'; 5 | 6 | export async function getStaticPaths() { 7 | return englishPages.map((page) => { 8 | return { 9 | params: { 10 | enRedirectSlug: stripLangFromSlug(page.slug), 11 | }, 12 | props: { 13 | englishSlug: page.slug, 14 | }, 15 | }; 16 | }); 17 | } 18 | --- 19 | 20 | 21 | -------------------------------------------------------------------------------- /docs/src/pages/[lang]/[...slug].astro: -------------------------------------------------------------------------------- 1 | --- 2 | import type { CollectionEntry } from 'astro:content'; 3 | import { allPages } from '~/content'; 4 | import LayoutSwitcher from '~/layouts/LayoutSwitcher.astro'; 5 | import { getLangFromSlug, stripLangFromSlug } from '~/util'; 6 | 7 | export async function getStaticPaths() { 8 | return allPages.map((page) => { 9 | const lang = getLangFromSlug(page.slug); 10 | const slug = stripLangFromSlug(page.slug); 11 | return { params: { lang, slug }, props: page }; 12 | }); 13 | } 14 | 15 | export type Props = CollectionEntry<'docs'>; 16 | const { data, render } = Astro.props; 17 | const { Content, headings } = await render(); 18 | --- 19 | 20 | 21 | -------------------------------------------------------------------------------- /docs/src/pages/[lang]/index.astro: -------------------------------------------------------------------------------- 1 | --- 2 | import languages from '../../i18n/languages'; 3 | 4 | export const getStaticPaths = () => Object.keys(languages).map((lang) => ({ params: { lang } })); 5 | 6 | const { lang } = Astro.params; 7 | --- 8 | 9 | 13 | -------------------------------------------------------------------------------- /docs/src/pages/[lang]/install.astro: -------------------------------------------------------------------------------- 1 | --- 2 | import languages from '../../i18n/languages'; 3 | 4 | export const getStaticPaths = () => Object.keys(languages).map((lang) => ({ params: { lang } })); 5 | 6 | const { lang } = Astro.params; 7 | --- 8 | 9 | 10 | -------------------------------------------------------------------------------- /docs/src/pages/[lang]/tutorial.astro: -------------------------------------------------------------------------------- 1 | --- 2 | import languages from '../../i18n/languages'; 3 | 4 | export const getStaticPaths = () => Object.keys(languages).map((lang) => ({ params: { lang } })); 5 | 6 | const { lang } = Astro.params; 7 | --- 8 | 9 | 10 | -------------------------------------------------------------------------------- /docs/src/pages/open-graph/_fonts/noto-sans/arabic-400-normal.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thoughtworks/HeartBeat/702cb41d75fce4670b4c57cb69aa538caeee88ae/docs/src/pages/open-graph/_fonts/noto-sans/arabic-400-normal.ttf -------------------------------------------------------------------------------- /docs/src/pages/open-graph/_fonts/noto-sans/arabic-800-normal.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thoughtworks/HeartBeat/702cb41d75fce4670b4c57cb69aa538caeee88ae/docs/src/pages/open-graph/_fonts/noto-sans/arabic-800-normal.ttf -------------------------------------------------------------------------------- /docs/src/pages/open-graph/_fonts/noto-sans/chinese-simplified-400-normal.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thoughtworks/HeartBeat/702cb41d75fce4670b4c57cb69aa538caeee88ae/docs/src/pages/open-graph/_fonts/noto-sans/chinese-simplified-400-normal.otf -------------------------------------------------------------------------------- /docs/src/pages/open-graph/_fonts/noto-sans/chinese-simplified-900-normal.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thoughtworks/HeartBeat/702cb41d75fce4670b4c57cb69aa538caeee88ae/docs/src/pages/open-graph/_fonts/noto-sans/chinese-simplified-900-normal.otf -------------------------------------------------------------------------------- /docs/src/pages/open-graph/_fonts/noto-sans/chinese-traditional-400-normal.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thoughtworks/HeartBeat/702cb41d75fce4670b4c57cb69aa538caeee88ae/docs/src/pages/open-graph/_fonts/noto-sans/chinese-traditional-400-normal.otf -------------------------------------------------------------------------------- /docs/src/pages/open-graph/_fonts/noto-sans/chinese-traditional-900-normal.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thoughtworks/HeartBeat/702cb41d75fce4670b4c57cb69aa538caeee88ae/docs/src/pages/open-graph/_fonts/noto-sans/chinese-traditional-900-normal.otf -------------------------------------------------------------------------------- /docs/src/pages/open-graph/_fonts/noto-sans/cyrillic-400-normal.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thoughtworks/HeartBeat/702cb41d75fce4670b4c57cb69aa538caeee88ae/docs/src/pages/open-graph/_fonts/noto-sans/cyrillic-400-normal.ttf -------------------------------------------------------------------------------- /docs/src/pages/open-graph/_fonts/noto-sans/cyrillic-900-normal.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thoughtworks/HeartBeat/702cb41d75fce4670b4c57cb69aa538caeee88ae/docs/src/pages/open-graph/_fonts/noto-sans/cyrillic-900-normal.ttf -------------------------------------------------------------------------------- /docs/src/pages/open-graph/_fonts/noto-sans/japanese-400-normal.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thoughtworks/HeartBeat/702cb41d75fce4670b4c57cb69aa538caeee88ae/docs/src/pages/open-graph/_fonts/noto-sans/japanese-400-normal.ttf -------------------------------------------------------------------------------- /docs/src/pages/open-graph/_fonts/noto-sans/japanese-900-normal.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thoughtworks/HeartBeat/702cb41d75fce4670b4c57cb69aa538caeee88ae/docs/src/pages/open-graph/_fonts/noto-sans/japanese-900-normal.ttf -------------------------------------------------------------------------------- /docs/src/pages/open-graph/_fonts/noto-sans/korean-400-normal.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thoughtworks/HeartBeat/702cb41d75fce4670b4c57cb69aa538caeee88ae/docs/src/pages/open-graph/_fonts/noto-sans/korean-400-normal.otf -------------------------------------------------------------------------------- /docs/src/pages/open-graph/_fonts/noto-sans/korean-900-normal.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thoughtworks/HeartBeat/702cb41d75fce4670b4c57cb69aa538caeee88ae/docs/src/pages/open-graph/_fonts/noto-sans/korean-900-normal.otf -------------------------------------------------------------------------------- /docs/src/pages/open-graph/_fonts/work-sans/latin-400-normal.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thoughtworks/HeartBeat/702cb41d75fce4670b4c57cb69aa538caeee88ae/docs/src/pages/open-graph/_fonts/work-sans/latin-400-normal.ttf -------------------------------------------------------------------------------- /docs/src/pages/open-graph/_fonts/work-sans/latin-800-normal.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thoughtworks/HeartBeat/702cb41d75fce4670b4c57cb69aa538caeee88ae/docs/src/pages/open-graph/_fonts/work-sans/latin-800-normal.ttf -------------------------------------------------------------------------------- /docs/src/util/groupPagesByLang.ts: -------------------------------------------------------------------------------- 1 | import type { CollectionEntry } from 'astro:content'; 2 | 3 | export const groupPagesByLang = >(pages: T[]) => 4 | pages.reduce( 5 | (pages, page) => { 6 | const lang = page.slug.split('/')[0]; 7 | if (!pages[lang]) pages[lang] = []; 8 | pages[lang].push(page); 9 | return pages; 10 | }, 11 | {} as { [lang: string]: T[] } 12 | ); 13 | -------------------------------------------------------------------------------- /docs/src/util/html-entities.ts: -------------------------------------------------------------------------------- 1 | import { unescape as unEsc } from 'html-escaper'; 2 | export { escape } from 'html-escaper'; 3 | 4 | /** Unescape HTML while catering for `<` (`<`) and `'&'` (`&`), which the Astro compiler outputs. */ 5 | export function unescape(str: string) { 6 | return unEsc(str).replaceAll('<', '<').replaceAll('&', '&'); 7 | } 8 | -------------------------------------------------------------------------------- /docs/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "astro/tsconfigs/strict", 3 | "exclude": ["dist"], 4 | "compilerOptions": { 5 | "allowJs": true, 6 | "baseUrl": ".", 7 | "paths": { 8 | "~/*": ["src/*"] 9 | }, 10 | "jsx": "react-jsx", 11 | "jsxImportSource": "preact" 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /frontend/.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | node_modules 11 | dev-dist 12 | dist 13 | dist-ssr 14 | *.local 15 | 16 | # Editor directories and files 17 | .vscode/* 18 | !.vscode/extensions.json 19 | .idea 20 | .DS_Store 21 | *.suo 22 | *.ntvs* 23 | *.njsproj 24 | *.sln 25 | *.sw? 26 | 27 | coverage 28 | /test-results/ 29 | /playwright-report/ 30 | /blob-report/ 31 | /playwright/.cache/ 32 | e2e/reports/ 33 | e2e/test-results/ 34 | e2e/temp/ 35 | -------------------------------------------------------------------------------- /frontend/.husky/pre-commit: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | . "$(dirname -- "$0")/_/husky.sh" 3 | 4 | npx lint-staged 5 | -------------------------------------------------------------------------------- /frontend/.husky/pre-push: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | . "$(dirname -- "$0")/_/husky.sh" 3 | 4 | cd frontend && npm run audit && npm run type-check && npm run coverage:silent && npm run e2e:with-server && \ 5 | cd ../backend && ./gradlew clean check 6 | -------------------------------------------------------------------------------- /frontend/.husky/pre-tag: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | . "$(dirname -- "$0")/_/husky.sh" 3 | 4 | echo "Before creating a tag, please confirm if you have completed the update of the backend API version." 5 | 6 | read -p "Have you completed the backend API version update? (y/n): " answer 7 | 8 | if [ "$answer" != "y" ] && [ "$answer" != "Y" ]; then 9 | echo "Please complete the backend API version update before creating a tag." 10 | exit 1 11 | fi 12 | 13 | exit 0 14 | -------------------------------------------------------------------------------- /frontend/.license-compliance.json: -------------------------------------------------------------------------------- 1 | { 2 | "allowed": [ 3 | "UNKNOWN", 4 | "Unlicense", 5 | "MIT", 6 | "ISC", 7 | "0BSD", 8 | "BSD-2-Clause", 9 | "BSD-3-Clause", 10 | "Apache-2.0", 11 | "Python-2.0", 12 | "CC-BY-4.0", 13 | "CC-BY-3.0", 14 | "WTFPL", 15 | "CC0-1.0" 16 | ] 17 | } 18 | -------------------------------------------------------------------------------- /frontend/.prettierignore: -------------------------------------------------------------------------------- 1 | dist 2 | dev-dist 3 | node_modules 4 | coverage 5 | playwright-report 6 | pnpm-lock.yaml 7 | cypress/reports 8 | cypress/downloads 9 | -------------------------------------------------------------------------------- /frontend/.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "printWidth": 120, 3 | "singleQuote": true, 4 | "jsxSingleQuote": true, 5 | "useTabs": false, 6 | "tabWidth": 2, 7 | "bracketSpacing": true, 8 | "plugins": ["prettier-plugin-sort-imports"] 9 | } 10 | -------------------------------------------------------------------------------- /frontend/__tests__/App.test.tsx: -------------------------------------------------------------------------------- 1 | import { render, RenderResult, waitFor } from '@testing-library/react'; 2 | import { Provider } from 'react-redux'; 3 | import { store } from '@src/store'; 4 | import App from '@src/App'; 5 | jest.useFakeTimers(); 6 | describe('render app', () => { 7 | const setup = (): RenderResult => 8 | render( 9 | 10 | 11 | , 12 | ); 13 | it('should show hello World when render app', async () => { 14 | const { container } = setup(); 15 | 16 | await waitFor(() => { 17 | expect(container.getElementsByTagName('span')[0].getAttribute('role')).toEqual('progressbar'); 18 | }); 19 | }); 20 | }); 21 | -------------------------------------------------------------------------------- /frontend/__tests__/__mocks__/svgTransformer.ts: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | process() { 3 | return { code: 'module.exports = {};' }; 4 | }, 5 | getCacheKey() { 6 | // The output is always the same. 7 | return 'svgTransform'; 8 | }, 9 | }; 10 | -------------------------------------------------------------------------------- /frontend/__tests__/components/Common/EmptyContent/EmptyContent.test.tsx: -------------------------------------------------------------------------------- 1 | import EmptyContent from '@src/components/Common/EmptyContent'; 2 | import { render, screen } from '@testing-library/react'; 3 | 4 | describe('EmptyContent', () => { 5 | it('should show title and message when render EmptyContent given title and message', () => { 6 | render(); 7 | expect(screen.getByText(/fake title/i)).toBeInTheDocument(); 8 | expect(screen.getByText(/there is empty content/i)).toBeInTheDocument(); 9 | }); 10 | }); 11 | -------------------------------------------------------------------------------- /frontend/__tests__/components/ErrorNotification/ErrorNotification.test.tsx: -------------------------------------------------------------------------------- 1 | import { ErrorNotification } from '@src/components/ErrorNotification'; 2 | import { BOARD_TYPES, VERIFY_ERROR_MESSAGE } from '../../fixtures'; 3 | import { render } from '@testing-library/react'; 4 | 5 | describe('error notification', () => { 6 | it('should show error message when render error notification', () => { 7 | const { getByText } = render( 8 | , 9 | ); 10 | 11 | expect(getByText(`${BOARD_TYPES.JIRA} ${VERIFY_ERROR_MESSAGE.BAD_REQUEST}`)).toBeInTheDocument(); 12 | }); 13 | }); 14 | -------------------------------------------------------------------------------- /frontend/__tests__/components/ProjectDescripution.test.tsx: -------------------------------------------------------------------------------- 1 | import { ProjectDescription } from '@src/components/ProjectDescription'; 2 | import { PROJECT_DESCRIPTION } from '../fixtures'; 3 | import { render } from '@testing-library/react'; 4 | 5 | describe('ProjectDescription', () => { 6 | it('should show project description', () => { 7 | const { getByRole } = render(); 8 | 9 | expect(getByRole('description').textContent).toContain(PROJECT_DESCRIPTION); 10 | }); 11 | }); 12 | -------------------------------------------------------------------------------- /frontend/__tests__/containers/MetricsStepper/ConfirmDialog.test.tsx: -------------------------------------------------------------------------------- 1 | import { ConfirmDialog } from '@src/containers/MetricsStepper/ConfirmDialog'; 2 | import { CONFIRM_DIALOG_DESCRIPTION } from '../../fixtures'; 3 | import { render } from '@testing-library/react'; 4 | 5 | const onClose = jest.fn(); 6 | const onConfirm = jest.fn(); 7 | 8 | describe('confirm dialog', () => { 9 | it('should show confirm dialog', () => { 10 | const { getByText } = render(); 11 | 12 | expect(getByText(CONFIRM_DIALOG_DESCRIPTION)).toBeInTheDocument(); 13 | }); 14 | }); 15 | -------------------------------------------------------------------------------- /frontend/__tests__/hooks/reportMapper/exportValidityTime.test.tsx: -------------------------------------------------------------------------------- 1 | import { exportValidityTimeMapper } from '@src/hooks/reportMapper/exportValidityTime'; 2 | 3 | describe('export validity time mapper', () => { 4 | it('should return 30 when call exportValidityTimeMapper given the param to 1800000', () => { 5 | const result = exportValidityTimeMapper(1800000); 6 | 7 | expect(result).toEqual(30); 8 | }); 9 | 10 | it('should return null when call exportValidityTimeMapper given the param to null', () => { 11 | const result = exportValidityTimeMapper(null); 12 | 13 | expect(result).toEqual(null); 14 | }); 15 | }); 16 | -------------------------------------------------------------------------------- /frontend/__tests__/hooks/reportMapper/velocity.test.tsx: -------------------------------------------------------------------------------- 1 | import { velocityMapper } from '@src/hooks/reportMapper/velocity'; 2 | 3 | describe('velocity data mapper', () => { 4 | it('maps response velocity values to ui display value', () => { 5 | const mockVelocityRes = { 6 | velocityForSP: 20, 7 | velocityForCards: 15, 8 | }; 9 | const expectedVelocityValues = [ 10 | { id: 0, name: 'Velocity(Story Point)', valueList: [{ value: 20 }] }, 11 | { id: 1, name: 'Throughput(Cards Count)', valueList: [{ value: 15 }] }, 12 | ]; 13 | 14 | const mappedVelocityValues = velocityMapper(mockVelocityRes); 15 | 16 | expect(mappedVelocityValues).toEqual(expectedVelocityValues); 17 | }); 18 | }); 19 | -------------------------------------------------------------------------------- /frontend/__tests__/pages/Metrics.test.tsx: -------------------------------------------------------------------------------- 1 | import { render } from '@testing-library/react'; 2 | import { MemoryRouter } from 'react-router-dom'; 3 | import Metrics from '@src/pages/Metrics'; 4 | import { Provider } from 'react-redux'; 5 | import { store } from '@src/store'; 6 | 7 | describe('Metrics', () => { 8 | it('should render Metrics page', () => { 9 | const { getByText } = render( 10 | 11 | 12 | 13 | 14 | , 15 | ); 16 | const steps = ['Config', 'Metrics', 'Report']; 17 | 18 | steps.map((label) => { 19 | expect(getByText(label)).toBeVisible(); 20 | }); 21 | }); 22 | }); 23 | -------------------------------------------------------------------------------- /frontend/__tests__/setupTests.ts: -------------------------------------------------------------------------------- 1 | import { configure } from '@testing-library/react'; 2 | import '@testing-library/jest-dom'; 3 | 4 | export const navigateMock = jest.fn(); 5 | 6 | // This configuration will affect the waitFor default timeout which is 1000ms. 7 | configure({ asyncUtilTimeout: 2000 }); 8 | 9 | jest.mock('react-router-dom', () => ({ 10 | ...(jest.requireActual('react-router-dom') as Record), 11 | useNavigate: () => navigateMock, 12 | })); 13 | -------------------------------------------------------------------------------- /frontend/__tests__/testUtils.ts: -------------------------------------------------------------------------------- 1 | import { UserEvent } from '@testing-library/user-event/setup/setup'; 2 | import userEvent from '@testing-library/user-event/index'; 3 | 4 | export const closeMuiModal = (ue: typeof userEvent | UserEvent) => ue.keyboard('{Escape}'); 5 | -------------------------------------------------------------------------------- /frontend/audit-ci.jsonc: -------------------------------------------------------------------------------- 1 | { 2 | // $schema provides code completion hints to IDEs. 3 | "$schema": "https://github.com/IBM/audit-ci/raw/main/docs/schema.json", 4 | "moderate": true, 5 | "allowlist": ["@adobe/css-tools"], 6 | } 7 | -------------------------------------------------------------------------------- /frontend/e2e/.env.ci: -------------------------------------------------------------------------------- 1 | APP_ENV=ci 2 | TZ="PRC" 3 | -------------------------------------------------------------------------------- /frontend/e2e/.env.e2e: -------------------------------------------------------------------------------- 1 | APP_ENV=e2e 2 | -------------------------------------------------------------------------------- /frontend/e2e/.env.example: -------------------------------------------------------------------------------- 1 | APP_ENV=local 2 | APP_ORIGIN="http://localhost:4321" 3 | E2E_TOKEN_JIRA="" 4 | E2E_TOKEN_GITHUB="" 5 | E2E_TOKEN_BUILD_KITE="" 6 | E2E_TOKEN_PIPELINE_NO_ORG_CONFIG_BUILDKITE="" 7 | TZ='PRC' 8 | -------------------------------------------------------------------------------- /frontend/e2e/.gitignore: -------------------------------------------------------------------------------- 1 | *.json 2 | *.json-e 3 | 4 | !*.template.json 5 | -------------------------------------------------------------------------------- /frontend/e2e/fixtures/cycle-time-by-status/cycle-time-by-status-report-result.ts: -------------------------------------------------------------------------------- 1 | export const BOARD_METRICS_RESULT = { 2 | Velocity: '7', 3 | Throughput: '3', 4 | AverageCycleTime4SP: '0.45', 5 | AverageCycleTime4Card: '1.05', 6 | totalReworkTimes: '2', 7 | totalReworkCards: '2', 8 | reworkCardsRatio: '0.6667', 9 | throughput: '2', 10 | }; 11 | 12 | export const DORA_METRICS_RESULT = { 13 | PrLeadTime: '3.74', 14 | PipelineLeadTime: '1.26', 15 | TotalLeadTime: '5.01', 16 | DeploymentFrequency: '1.25', 17 | FailureRate: '16.67% (1/6)', 18 | DevMeanTimeToRecovery: '1.52', 19 | }; 20 | -------------------------------------------------------------------------------- /frontend/e2e/fixtures/index.ts: -------------------------------------------------------------------------------- 1 | export const VIEWPORT_DEFAULT = { width: 1920, height: 1080 }; 2 | 3 | export const E2E_PROJECT_NAME = 'E2EProjectName'; 4 | 5 | export const CONFIG_STEP_SAVING_FILENAME = 'config-step.json'; 6 | export const METRICS_STEP_SAVING_FILENAME = 'metrics-step.json'; 7 | export const E2E_EXPECT_LOCAL_TIMEOUT = 30 * 3 * 1000; 8 | export const E2E_EXPECT_CI_TIMEOUT = 30 * 1000; 9 | export const E2E_OVERALL_TIMEOUT = 60 * 1000; 10 | -------------------------------------------------------------------------------- /frontend/e2e/utils/clear-temp-dir.ts: -------------------------------------------------------------------------------- 1 | import { CONFIG_STEP_SAVING_FILENAME } from 'e2e/fixtures'; 2 | import path from 'path'; 3 | import fs from 'fs'; 4 | 5 | const clearTempDir = async () => { 6 | try { 7 | const configStepSavePath = path.resolve(__dirname, '..', './temp/', `./${CONFIG_STEP_SAVING_FILENAME}`); 8 | const isExist = fs.existsSync(configStepSavePath); 9 | if (isExist) { 10 | fs.rmSync(configStepSavePath); 11 | } 12 | } finally { 13 | console.log('e2e/temp/ dir cleared, going to start testing suite.'); 14 | } 15 | }; 16 | 17 | export { clearTempDir }; 18 | -------------------------------------------------------------------------------- /frontend/e2e/utils/date-time.ts: -------------------------------------------------------------------------------- 1 | import dayjs from 'dayjs'; 2 | 3 | const format = (date: string) => dayjs(date).format('MM/DD/YYYY'); 4 | 5 | export { format }; 6 | -------------------------------------------------------------------------------- /frontend/global-setup.ts: -------------------------------------------------------------------------------- 1 | export {}; 2 | 3 | module.exports = async () => { 4 | process.env.TZ = 'PRC'; 5 | }; 6 | -------------------------------------------------------------------------------- /frontend/public/android-chrome-192x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thoughtworks/HeartBeat/702cb41d75fce4670b4c57cb69aa538caeee88ae/frontend/public/android-chrome-192x192.png -------------------------------------------------------------------------------- /frontend/public/android-chrome-512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thoughtworks/HeartBeat/702cb41d75fce4670b4c57cb69aa538caeee88ae/frontend/public/android-chrome-512x512.png -------------------------------------------------------------------------------- /frontend/public/apple-touch-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thoughtworks/HeartBeat/702cb41d75fce4670b4c57cb69aa538caeee88ae/frontend/public/apple-touch-icon.png -------------------------------------------------------------------------------- /frontend/public/browserconfig.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | #da532c 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /frontend/public/favicon-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thoughtworks/HeartBeat/702cb41d75fce4670b4c57cb69aa538caeee88ae/frontend/public/favicon-16x16.png -------------------------------------------------------------------------------- /frontend/public/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thoughtworks/HeartBeat/702cb41d75fce4670b4c57cb69aa538caeee88ae/frontend/public/favicon-32x32.png -------------------------------------------------------------------------------- /frontend/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thoughtworks/HeartBeat/702cb41d75fce4670b4c57cb69aa538caeee88ae/frontend/public/favicon.ico -------------------------------------------------------------------------------- /frontend/public/mstile-150x150.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thoughtworks/HeartBeat/702cb41d75fce4670b4c57cb69aa538caeee88ae/frontend/public/mstile-150x150.png -------------------------------------------------------------------------------- /frontend/public/site.webmanifest: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Heartbeat", 3 | "short_name": "Heartbeat", 4 | "icons": [ 5 | { 6 | "src": "/android-chrome-192x192.png", 7 | "sizes": "192x192", 8 | "type": "image/png" 9 | }, 10 | { 11 | "src": "/android-chrome-512x512.png", 12 | "sizes": "512x512", 13 | "type": "image/png" 14 | } 15 | ], 16 | "theme_color": "#ffffff", 17 | "background_color": "#ffffff", 18 | "display": "standalone" 19 | } 20 | -------------------------------------------------------------------------------- /frontend/src/App.css: -------------------------------------------------------------------------------- 1 | #root, 2 | html, 3 | body, 4 | .app { 5 | width: 100%; 6 | height: fit-content; 7 | min-width: 25rem; 8 | font-family: 9 | Roboto, 10 | sans-serif, 11 | -apple-system, 12 | BlinkMacSystemFont, 13 | Segoe UI, 14 | Arial; 15 | } 16 | 17 | * { 18 | padding: 0; 19 | margin: 0; 20 | outline: 0; 21 | } 22 | -------------------------------------------------------------------------------- /frontend/src/App.tsx: -------------------------------------------------------------------------------- 1 | import { BrowserRouter } from 'react-router-dom'; 2 | import styled from '@emotion/styled'; 3 | import Router from './router'; 4 | import './App.css'; 5 | 6 | const AppContainer = styled.div({ 7 | display: 'flex', 8 | flexDirection: 'column', 9 | height: '100%', 10 | }); 11 | 12 | function App() { 13 | return ( 14 | 15 | 16 | 17 | 18 | 19 | ); 20 | } 21 | 22 | export default App; 23 | -------------------------------------------------------------------------------- /frontend/src/assets/DividingLine.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /frontend/src/clients/board/BoardInfoClient.ts: -------------------------------------------------------------------------------- 1 | import { BoardInfoRequestDTO } from '@src/clients/board/dto/request'; 2 | import { getJiraBoardToken } from '@src/utils/util'; 3 | import { HttpClient } from '../HttpClient'; 4 | export class BoardInfoClient extends HttpClient { 5 | getBoardInfo = async (params: BoardInfoRequestDTO) => { 6 | return this.axiosInstance.post(`/boards/${params.type.toLowerCase()}/info`, { 7 | ...params, 8 | token: getJiraBoardToken(params.token, params.email), 9 | }); 10 | }; 11 | } 12 | 13 | export const boardInfoClient = new BoardInfoClient(); 14 | -------------------------------------------------------------------------------- /frontend/src/clients/board/dto/request.ts: -------------------------------------------------------------------------------- 1 | export interface BoardRequestDTO { 2 | token: string; 3 | type: string; 4 | site: string; 5 | email: string; 6 | boardId: string; 7 | } 8 | 9 | export interface BoardInfoRequestDTO { 10 | token: string; 11 | type: string; 12 | site: string; 13 | email: string; 14 | startTime: string | null; 15 | endTime: string | null; 16 | boardId: string; 17 | projectKey: string; 18 | } 19 | -------------------------------------------------------------------------------- /frontend/src/clients/header/HeaderClient.ts: -------------------------------------------------------------------------------- 1 | import { VersionResponseDTO } from '@src/clients/header/dto/request'; 2 | import { HttpClient } from '@src/clients/HttpClient'; 3 | 4 | export class HeaderClient extends HttpClient { 5 | response: VersionResponseDTO = { 6 | version: '', 7 | }; 8 | 9 | getVersion = async () => { 10 | try { 11 | const res = await this.axiosInstance.get(`/version`); 12 | this.response = res.data; 13 | } catch (e) { 14 | this.response = { 15 | version: '', 16 | }; 17 | throw e; 18 | } 19 | return this.response.version; 20 | }; 21 | } 22 | 23 | export const headerClient = new HeaderClient(); 24 | -------------------------------------------------------------------------------- /frontend/src/clients/header/dto/request.ts: -------------------------------------------------------------------------------- 1 | export interface VersionResponseDTO { 2 | version: string; 3 | } 4 | -------------------------------------------------------------------------------- /frontend/src/clients/pipeline/dto/request.ts: -------------------------------------------------------------------------------- 1 | export interface IPipelineVerifyRequestDTO { 2 | type: string; 3 | token: string; 4 | } 5 | 6 | export interface PipelineInfoRequestDTO { 7 | type: string; 8 | token: string; 9 | startTime: string | number | null; 10 | endTime: string | number | null; 11 | } 12 | 13 | export interface PipelineRequestDTO { 14 | type: string; 15 | token: string; 16 | startTime: string | number | null; 17 | endTime: string | number | null; 18 | } 19 | -------------------------------------------------------------------------------- /frontend/src/clients/pipeline/dto/response.ts: -------------------------------------------------------------------------------- 1 | import { pipeline } from '@src/context/config/pipelineTool/verifyResponseSlice'; 2 | 3 | export interface IPipelineInfoResponseDTO { 4 | pipelineList: pipeline[]; 5 | } 6 | -------------------------------------------------------------------------------- /frontend/src/clients/sourceControl/dto/request.ts: -------------------------------------------------------------------------------- 1 | import { SOURCE_CONTROL_TYPES } from '@src/constants/resources'; 2 | 3 | export interface SourceControlVerifyRequestDTO { 4 | type: SOURCE_CONTROL_TYPES; 5 | token: string; 6 | } 7 | 8 | export interface SourceControlInfoRequestDTO { 9 | type: SOURCE_CONTROL_TYPES; 10 | branch: string; 11 | repository: string; 12 | token: string; 13 | } 14 | -------------------------------------------------------------------------------- /frontend/src/components/Common/AddButtonOneLine/index.tsx: -------------------------------------------------------------------------------- 1 | import { StyledButton } from '@src/components/Common/AddButtonOneLine/style'; 2 | import { ButtonProps } from '@mui/material'; 3 | import { Add } from '@mui/icons-material'; 4 | import React, { ReactNode } from 'react'; 5 | 6 | interface IAddButtonProps { 7 | onClick: () => void; 8 | text: ReactNode; 9 | } 10 | 11 | export const AddButton = ({ text, onClick, ...restProps }: IAddButtonProps & ButtonProps) => { 12 | return ( 13 | } onClick={onClick}> 14 | {text} 15 | 16 | ); 17 | }; 18 | -------------------------------------------------------------------------------- /frontend/src/components/Common/AddButtonOneLine/style.tsx: -------------------------------------------------------------------------------- 1 | import { Button } from '@mui/material'; 2 | import styled from '@emotion/styled'; 3 | import { theme } from '@src/theme'; 4 | 5 | export const StyledButton = styled(Button)({ 6 | display: 'flex', 7 | justifyContent: 'center', 8 | borderRadius: '0.25rem', 9 | border: `0.07rem dashed ${theme.main.alert.info.iconColor}`, 10 | width: '100%', 11 | }); 12 | -------------------------------------------------------------------------------- /frontend/src/components/Common/BoldText/index.tsx: -------------------------------------------------------------------------------- 1 | import { StyledBoldText } from '@src/components/Common/BoldText/style'; 2 | 3 | const BoldText = ({ children }: { children: React.ReactNode }) => { 4 | return {children}; 5 | }; 6 | 7 | export default BoldText; 8 | -------------------------------------------------------------------------------- /frontend/src/components/Common/BoldText/style.tsx: -------------------------------------------------------------------------------- 1 | import { styled } from '@mui/material/styles'; 2 | import { theme } from '@src/theme'; 3 | 4 | export const StyledBoldText = styled('span')({ 5 | color: theme.main.secondColor, 6 | fontWeight: 600, 7 | }); 8 | -------------------------------------------------------------------------------- /frontend/src/components/Common/Buttons.ts: -------------------------------------------------------------------------------- 1 | import { styled } from '@mui/material/styles'; 2 | import Button from '@mui/material/Button'; 3 | import { theme } from '@src/theme'; 4 | 5 | export const BasicButton = styled(Button)({ 6 | width: '3rem', 7 | fontSize: '0.8rem', 8 | fontFamily: theme.main.font.secondary, 9 | fontWeight: 'bold', 10 | }); 11 | 12 | export const VerifyButton = styled(BasicButton)({}); 13 | export const ReverifyButton = styled(BasicButton)({ 14 | color: theme.components?.errorMessage.color, 15 | }); 16 | export const ResetButton = styled(BasicButton)({ 17 | marginLeft: '0.5rem', 18 | }); 19 | -------------------------------------------------------------------------------- /frontend/src/components/Common/EllipsisText/index.tsx: -------------------------------------------------------------------------------- 1 | import { StyledText } from '@src/components/Common/EllipsisText/style'; 2 | import React from 'react'; 3 | 4 | interface IEllipsisTextProps { 5 | children: React.ReactNode; 6 | fitContent: boolean; 7 | ref: React.ForwardedRef; 8 | } 9 | 10 | export default React.forwardRef(function RefWrapper( 11 | props: IEllipsisTextProps, 12 | ref: React.ForwardedRef, 13 | ) { 14 | return ( 15 | 16 | {props.children} 17 | 18 | ); 19 | }); 20 | -------------------------------------------------------------------------------- /frontend/src/components/Common/EllipsisText/style.tsx: -------------------------------------------------------------------------------- 1 | import { ellipsisProps } from '@src/layouts/style'; 2 | import styled from '@emotion/styled'; 3 | 4 | export const StyledText = styled.p(({ fitContent }: { fitContent: boolean }) => ({ 5 | maxWidth: '100%', 6 | width: fitContent ? 'fit-content' : 'auto', 7 | ...ellipsisProps, 8 | })); 9 | -------------------------------------------------------------------------------- /frontend/src/components/Common/EmptyContent/index.tsx: -------------------------------------------------------------------------------- 1 | import { StyledErrorMessage, StyledErrorSection, StyledImgSection, StyledErrorTitle } from './styles'; 2 | import EmptyBox from '@src/assets/EmptyBox.svg'; 3 | import { ReactNode } from 'react'; 4 | 5 | export interface Props { 6 | title: ReactNode; 7 | message: ReactNode; 8 | } 9 | 10 | const EmptyContent = ({ title, message }: Props) => { 11 | return ( 12 | 13 | 14 | {title} 15 | {message} 16 | 17 | ); 18 | }; 19 | 20 | export default EmptyContent; 21 | -------------------------------------------------------------------------------- /frontend/src/components/Common/EmptyContent/styles.tsx: -------------------------------------------------------------------------------- 1 | import styled from '@emotion/styled'; 2 | import { theme } from '@src/theme'; 3 | 4 | export const StyledErrorSection = styled.div({ 5 | display: 'flex', 6 | flexDirection: 'column', 7 | alignItems: 'center', 8 | justifyContent: 'center', 9 | padding: '1.5rem 0', 10 | }); 11 | 12 | export const StyledImgSection = styled.img({ 13 | height: '5.75rem', 14 | marginBottom: '1rem', 15 | }); 16 | 17 | export const StyledErrorMessage = styled.div({ 18 | color: theme.main.button.disabled.color, 19 | fontSize: '0.875rem', 20 | }); 21 | 22 | export const StyledErrorTitle = styled.div({ 23 | fontWeight: 700, 24 | fontSize: '1.25rem', 25 | marginBottom: '1.625rem', 26 | }); 27 | -------------------------------------------------------------------------------- /frontend/src/components/Common/MetricsSettingTitle/index.tsx: -------------------------------------------------------------------------------- 1 | import styled from '@emotion/styled'; 2 | 3 | export const MetricsSettingTitleContainer = styled.div({ 4 | margin: '1.25rem 0', 5 | fontSize: '1rem', 6 | lineHeight: '1.25rem', 7 | fontWeight: '600', 8 | }); 9 | 10 | export const MetricsSettingTitle = (props: { title: string }) => ( 11 | {props.title} 12 | ); 13 | -------------------------------------------------------------------------------- /frontend/src/components/Common/MetricsSettingTitle/style.tsx: -------------------------------------------------------------------------------- 1 | import { styled } from '@mui/material/styles'; 2 | import { theme } from '@src/theme'; 3 | 4 | export const Divider = styled('div')({ 5 | paddingLeft: '0.5rem', 6 | borderLeft: `0.4rem solid ${theme.main.backgroundColor}`, 7 | margin: '0', 8 | }); 9 | -------------------------------------------------------------------------------- /frontend/src/components/Common/ReportGrid/ReportTitle/index.tsx: -------------------------------------------------------------------------------- 1 | import { 2 | StyledMetricsSign, 3 | StyledMetricsTitle, 4 | StyledMetricsTitleSection, 5 | } from '@src/components/Common/ReportGrid/ReportTitle/style'; 6 | import React from 'react'; 7 | 8 | interface ReportTitleProps { 9 | title: string; 10 | } 11 | export const ReportTitle = ({ title }: ReportTitleProps) => { 12 | return ( 13 | 14 | 15 | {title} 16 | 17 | ); 18 | }; 19 | -------------------------------------------------------------------------------- /frontend/src/components/Common/SectionTitleWithTooltip/types.ts: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | export interface IProps { 4 | title: string; 5 | tooltipText: string; 6 | titleStyle?: React.CSSProperties; 7 | } 8 | -------------------------------------------------------------------------------- /frontend/src/components/Common/WarningNotification/index.tsx: -------------------------------------------------------------------------------- 1 | import { DURATION } from '@src/constants/commons'; 2 | import { StyledAlert, WarningBar } from './style'; 3 | import { useEffect, useState } from 'react'; 4 | 5 | export const WarningNotification = (props: { message: string }) => { 6 | const { message } = props; 7 | const [open, setOpen] = useState(true); 8 | useEffect(() => { 9 | const timer = setTimeout(() => { 10 | setOpen(false); 11 | }, DURATION.ERROR_MESSAGE_TIME); 12 | 13 | return () => { 14 | clearTimeout(timer); 15 | }; 16 | }, []); 17 | return ( 18 | 19 | {message} 20 | 21 | ); 22 | }; 23 | -------------------------------------------------------------------------------- /frontend/src/components/ErrorMessagePrompt/index.tsx: -------------------------------------------------------------------------------- 1 | import { StyledErrorMessage, StyledErrorSection, StyledImgSection } from '@src/components/ErrorMessagePrompt/style'; 2 | import EmptyBox from '@src/assets/EmptyBox.svg'; 3 | import React, { CSSProperties } from 'react'; 4 | 5 | export const ErrorMessagePrompt = (props: { errorMessage: string; style?: CSSProperties }) => { 6 | const { errorMessage, style } = props; 7 | return ( 8 | 9 | 10 | {errorMessage} 11 | 12 | ); 13 | }; 14 | -------------------------------------------------------------------------------- /frontend/src/components/ErrorMessagePrompt/style.tsx: -------------------------------------------------------------------------------- 1 | import styled from '@emotion/styled'; 2 | import { theme } from '@src/theme'; 3 | 4 | export const StyledErrorSection = styled.div({ 5 | display: 'flex', 6 | flexDirection: 'column', 7 | alignItems: 'center', 8 | justifyContent: 'center', 9 | }); 10 | 11 | export const StyledImgSection = styled.img({ 12 | height: '3.8rem', 13 | }); 14 | 15 | export const StyledErrorMessage = styled.div({ 16 | color: theme.main.errorMessage.color, 17 | fontSize: '0.875rem', 18 | }); 19 | -------------------------------------------------------------------------------- /frontend/src/components/ErrorNotification/index.tsx: -------------------------------------------------------------------------------- 1 | import { ErrorBar, StyledAlert } from './style'; 2 | 3 | export const ErrorNotification = (props: { message: string }) => { 4 | const { message } = props; 5 | return ( 6 | 7 | {message} 8 | 9 | ); 10 | }; 11 | -------------------------------------------------------------------------------- /frontend/src/config/routes.ts: -------------------------------------------------------------------------------- 1 | import { lazy } from 'react'; 2 | 3 | const routes = [ 4 | { 5 | path: '/', 6 | exact: true, 7 | component: lazy(() => import('../pages/Home')), 8 | name: 'Home', 9 | }, 10 | { 11 | path: '/metrics', 12 | component: lazy(() => import('../pages/Metrics')), 13 | name: 'Metrics', 14 | }, 15 | { 16 | path: '/error-page', 17 | component: lazy(() => import('../pages/ErrorPage')), 18 | name: 'ErrorPage', 19 | }, 20 | { 21 | path: '*', 22 | component: lazy(() => import('../pages/Home')), 23 | name: 'Home', 24 | }, 25 | ]; 26 | 27 | export default routes; 28 | -------------------------------------------------------------------------------- /frontend/src/constants/emojis/style.tsx: -------------------------------------------------------------------------------- 1 | import { Avatar, Typography } from '@mui/material'; 2 | import { styled } from '@mui/material/styles'; 3 | 4 | export const StyledAvatar = styled(Avatar)({ 5 | width: '1.25rem', 6 | height: '1.25rem', 7 | marginRight: '0.25rem', 8 | }); 9 | 10 | export const EmojiWrap = styled('div')({ 11 | display: 'flex', 12 | alignItems: 'center', 13 | }); 14 | 15 | export const StyledTypography = styled(Typography)({ 16 | fontSize: '0.88rem', 17 | }); 18 | -------------------------------------------------------------------------------- /frontend/src/constants/regex.ts: -------------------------------------------------------------------------------- 1 | export const REGEX = { 2 | EMAIL: /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/, 3 | BOARD_TOKEN: /^[a-zA-Z0-9\-=_]{1,500}$/, 4 | BUILDKITE_TOKEN: /^(bkua)?_?([a-zA-Z0-9]{40})$/, 5 | GITHUB_TOKEN: /^(ghp|gho|ghu|ghs|ghr)+_+([a-zA-Z0-9]{36})$/, 6 | BOARD_ID: /^[0-9]+$/, 7 | }; 8 | -------------------------------------------------------------------------------- /frontend/src/constants/router.ts: -------------------------------------------------------------------------------- 1 | export enum ROUTE { 2 | BASE_PAGE = '/', 3 | ERROR_PAGE = '/error-page', 4 | METRICS_PAGE = '/metrics', 5 | } 6 | -------------------------------------------------------------------------------- /frontend/src/constants/template.ts: -------------------------------------------------------------------------------- 1 | export const DATE_FORMAT_TEMPLATE = 'YYYY/MM/DD'; 2 | -------------------------------------------------------------------------------- /frontend/src/containers/ConfigStep/BasicInfo/RequiredMetrics/style.tsx: -------------------------------------------------------------------------------- 1 | import { styled } from '@mui/material/styles'; 2 | import { FormControl } from '@mui/material'; 3 | 4 | export const RequireDataSelections = styled(FormControl)({ 5 | width: '100%', 6 | paddingBottom: '1rem', 7 | marginTop: '1.25rem', 8 | }); 9 | -------------------------------------------------------------------------------- /frontend/src/containers/ConfigStep/DateRangePicker/index.tsx: -------------------------------------------------------------------------------- 1 | import { DateRangePickerGroup } from '@src/containers/ConfigStep/DateRangePicker/DateRangePickerGroup'; 2 | import SectionTitleWithTooltip from '@src/components/Common/SectionTitleWithTooltip'; 3 | import { TIME_RANGE_TITLE, TIPS } from '@src/constants/resources'; 4 | 5 | export const DateRangePickerSection = () => { 6 | return ( 7 |
    8 | 15 | 16 |
    17 | ); 18 | }; 19 | -------------------------------------------------------------------------------- /frontend/src/containers/ConfigStep/DateRangePicker/types.ts: -------------------------------------------------------------------------------- 1 | export interface IRangePickerProps { 2 | startDate: string | null; 3 | endDate: string | null; 4 | index: number; 5 | key?: string | number; 6 | } 7 | -------------------------------------------------------------------------------- /frontend/src/containers/ConfigStep/NoDoneCardPop/index.tsx: -------------------------------------------------------------------------------- 1 | import { OkButton, StyledDialog } from '@src/containers/ConfigStep/NoDoneCardPop/style'; 2 | import { DialogContent } from '@mui/material'; 3 | 4 | interface NoDoneCardPopProps { 5 | isOpen: boolean; 6 | onClose: () => void; 7 | } 8 | 9 | export const NoCardPop = (props: NoDoneCardPopProps) => { 10 | const { isOpen, onClose } = props; 11 | return ( 12 | 13 | 14 | Sorry there is no card within selected date range, please change your collection date! 15 | 16 | Ok 17 | 18 | ); 19 | }; 20 | -------------------------------------------------------------------------------- /frontend/src/containers/ConfigStep/NoDoneCardPop/style.tsx: -------------------------------------------------------------------------------- 1 | import { styled } from '@mui/material/styles'; 2 | import { Dialog } from '@mui/material'; 3 | import { theme } from '@src/theme'; 4 | 5 | export const StyledDialog = styled(Dialog)` 6 | & .MuiDialog-paper { 7 | padding: 1rem; 8 | display: flex; 9 | justify-content: center; 10 | align-items: center; 11 | font-size: 1rem; 12 | } 13 | `; 14 | 15 | export const OkButton = styled('button')({ 16 | width: '4rem', 17 | height: '2rem', 18 | display: 'flex', 19 | justifyContent: 'center', 20 | alignItems: 'center', 21 | borderRadius: '0.3rem', 22 | color: 'White', 23 | backgroundColor: theme.main.backgroundColor, 24 | }); 25 | -------------------------------------------------------------------------------- /frontend/src/containers/ConfigStep/TimeoutAlert/style.tsx: -------------------------------------------------------------------------------- 1 | import { styled } from '@mui/material/styles'; 2 | import { Alert } from '@mui/material'; 3 | import { theme } from '@src/theme'; 4 | 5 | export const StyledAlert = styled(Alert)({ 6 | '&.MuiPaper-root': { 7 | flex: 1, 8 | border: `0.07rem solid ${theme.main.alert.error.borderColor}`, 9 | backgroundColor: theme.main.alert.error.backgroundColor, 10 | borderRadius: '0.5rem', 11 | padding: '0 1rem', 12 | maxWidth: '50%', 13 | alignItems: 'center', 14 | justifyContent: 'center', 15 | '& .MuiAlert-icon': { 16 | marginTop: '0.125rem', 17 | }, 18 | }, 19 | }); 20 | -------------------------------------------------------------------------------- /frontend/src/containers/ConfigStep/style.tsx: -------------------------------------------------------------------------------- 1 | import { styled } from '@mui/material/styles'; 2 | 3 | export const ConfigStepWrapper = styled('div')({ 4 | width: '100%', 5 | }); 6 | export const StyledAlterWrapper = styled('div')({ 7 | display: 'flex', 8 | width: '100%', 9 | alignItems: 'center', 10 | justifyContent: 'center', 11 | height: '2.5rem', 12 | position: 'absolute', 13 | top: '1rem', 14 | }); 15 | -------------------------------------------------------------------------------- /frontend/src/containers/MetricsStep/Crews/style.tsx: -------------------------------------------------------------------------------- 1 | import { styled } from '@mui/material/styles'; 2 | import { RadioGroup } from '@mui/material'; 3 | 4 | export const StyledRadioGroup = styled(RadioGroup)({ 5 | display: 'grid', 6 | gridTemplateColumns: 'repeat(4, 1fr)', 7 | paddingTop: '1rem', 8 | }); 9 | 10 | export const WarningMessage = styled('span')({ 11 | color: 'red', 12 | }); 13 | -------------------------------------------------------------------------------- /frontend/src/containers/MetricsStep/DeploymentFrequencySettings/SingleSelection/style.tsx: -------------------------------------------------------------------------------- 1 | import { styled } from '@mui/material/styles'; 2 | import { FormControl } from '@mui/material'; 3 | 4 | export const FormControlWrapper = styled(FormControl)({ 5 | margin: '0 0 2rem 2.5%', 6 | width: '95%', 7 | height: '2.5rem', 8 | }); 9 | -------------------------------------------------------------------------------- /frontend/src/containers/MetricsStep/RealDone/style.tsx: -------------------------------------------------------------------------------- 1 | import { styled } from '@mui/material/styles'; 2 | import { FormControl } from '@mui/material'; 3 | 4 | export const FormControlWrapper = styled(FormControl)({ 5 | height: '2.5rem', 6 | }); 7 | -------------------------------------------------------------------------------- /frontend/src/containers/MetricsStep/ReworkSettings/SingleSelection/style.tsx: -------------------------------------------------------------------------------- 1 | import { styled } from '@mui/material/styles'; 2 | import { FormControl } from '@mui/material'; 3 | 4 | export const FormControlWrapper = styled(FormControl)({ 5 | height: '2.5rem', 6 | }); 7 | -------------------------------------------------------------------------------- /frontend/src/containers/MetricsStep/ReworkSettings/style.tsx: -------------------------------------------------------------------------------- 1 | import styled from '@emotion/styled'; 2 | import { theme } from '@src/theme'; 3 | 4 | export const ReworkHeaderWrapper = styled('div')({ 5 | display: 'flex', 6 | gap: '1rem', 7 | marginTop: '1rem', 8 | }); 9 | 10 | export const ReworkSettingsWrapper = styled('div')` 11 | position: relative; 12 | width: 100%; 13 | border: ${theme.main.cardBorder}; 14 | box-shadow: none; 15 | margin-bottom: 1rem; 16 | line-height: 2rem; 17 | boarder-radius: 0.25rem; 18 | padding: 2.5%; 19 | box-sizing: border-box; 20 | display: flex; 21 | flex-direction: column; 22 | gap: 1rem; 23 | `; 24 | -------------------------------------------------------------------------------- /frontend/src/containers/ReportStep/BoardMetrics/style.tsx: -------------------------------------------------------------------------------- 1 | import { styled } from '@mui/material/styles'; 2 | 3 | export const GridContainer = styled('div')({ 4 | display: 'flex', 5 | flexDirection: 'column', 6 | gap: '1rem', 7 | }); 8 | -------------------------------------------------------------------------------- /frontend/src/containers/ReportStep/DoraMetrics/style.tsx: -------------------------------------------------------------------------------- 1 | import { styled } from '@mui/material/styles'; 2 | import { theme } from '@src/theme'; 3 | 4 | export const StyledMetricsSection = styled('div')({ 5 | marginTop: '2rem', 6 | }); 7 | 8 | export const StyledTitleWrapper = styled('div')({ 9 | display: 'flex', 10 | alignItems: 'center', 11 | marginBottom: '1rem', 12 | }); 13 | 14 | export const StyledShowMore = styled('div')({ 15 | marginLeft: '0.5rem', 16 | fontSize: '0.8rem', 17 | textDecoration: 'none', 18 | color: theme.main.alert.info.iconColor, 19 | cursor: 'pointer', 20 | }); 21 | -------------------------------------------------------------------------------- /frontend/src/containers/ReportStep/ExpiredDialog/style.tsx: -------------------------------------------------------------------------------- 1 | import { DialogActions, DialogTitle } from '@mui/material'; 2 | import { styled } from '@mui/material/styles'; 3 | 4 | export const StyleDialogTitle = styled(DialogTitle)({ 5 | display: 'flex', 6 | alignItems: 'center', 7 | }); 8 | 9 | export const StyleDialogActions = styled(DialogActions)({ 10 | padding: '1rem', 11 | }); 12 | -------------------------------------------------------------------------------- /frontend/src/containers/ReportStep/ReportDetail/index.ts: -------------------------------------------------------------------------------- 1 | export * from '@src/containers/ReportStep/ReportDetail/board'; 2 | export * from '@src/containers/ReportStep/ReportDetail/dora'; 3 | -------------------------------------------------------------------------------- /frontend/src/containers/ReportStep/style.tsx: -------------------------------------------------------------------------------- 1 | import { styled } from '@mui/material/styles'; 2 | import { theme } from '@src/theme'; 3 | 4 | export const StyledSpacing = styled('div')({ 5 | height: '1.5rem', 6 | }); 7 | 8 | export const basicButtonStyle = { 9 | height: '2.5rem', 10 | padding: '0 1rem', 11 | marginLeft: '0.5rem', 12 | fontSize: '1rem', 13 | fontWeight: '500', 14 | textTransform: theme.typography.button.textTransform, 15 | }; 16 | 17 | export const StyledCalendarWrapper = styled('div')((props: { isSummaryPage: boolean }) => ({ 18 | display: 'flex', 19 | justifyContent: 'flex-end', 20 | marginTop: '0.25rem', 21 | marginBottom: props.isSummaryPage ? '-3.5rem' : '-2rem', 22 | })); 23 | -------------------------------------------------------------------------------- /frontend/src/context/config/pipelineTool/pipelineToolSlice.ts: -------------------------------------------------------------------------------- 1 | import { initialPipelineToolVerifiedResponseState, IPipelineToolVerifyResponse } from './verifyResponseSlice'; 2 | import { PIPELINE_TOOL_TYPES } from '@src/constants/resources'; 3 | 4 | export interface IPipelineToolState { 5 | config: { type: string; token: string }; 6 | isVerified: boolean; 7 | isShow: boolean; 8 | verifiedResponse: IPipelineToolVerifyResponse; 9 | } 10 | 11 | export const initialPipelineToolState: IPipelineToolState = { 12 | config: { 13 | type: PIPELINE_TOOL_TYPES.BUILD_KITE, 14 | token: '', 15 | }, 16 | isVerified: false, 17 | isShow: false, 18 | verifiedResponse: initialPipelineToolVerifiedResponseState, 19 | }; 20 | -------------------------------------------------------------------------------- /frontend/src/context/config/pipelineTool/verifyResponseSlice.ts: -------------------------------------------------------------------------------- 1 | export interface IPipelineToolVerifyResponse { 2 | pipelineList: pipeline[]; 3 | pipelineCrews: string[]; 4 | } 5 | 6 | export interface pipeline { 7 | id: string; 8 | name: string; 9 | orgId: string; 10 | orgName: string; 11 | repository: string; 12 | steps: string[]; 13 | branches: string[]; 14 | } 15 | 16 | export const initialPipelineToolVerifiedResponseState: IPipelineToolVerifyResponse = { 17 | pipelineList: [], 18 | pipelineCrews: [], 19 | }; 20 | -------------------------------------------------------------------------------- /frontend/src/context/config/sourceControl/sourceControlSlice.ts: -------------------------------------------------------------------------------- 1 | import { initSourceControlVerifyResponseState, ISourceControlVerifyResponse } from './verifyResponseSlice'; 2 | import { SOURCE_CONTROL_TYPES } from '@src/constants/resources'; 3 | 4 | export interface ISourceControl { 5 | config: { type: string; token: string }; 6 | isVerified: boolean; 7 | isShow: boolean; 8 | verifiedResponse: ISourceControlVerifyResponse; 9 | } 10 | 11 | export const initialSourceControlState: ISourceControl = { 12 | config: { 13 | type: SOURCE_CONTROL_TYPES.GITHUB, 14 | token: '', 15 | }, 16 | isVerified: false, 17 | isShow: false, 18 | verifiedResponse: initSourceControlVerifyResponseState, 19 | }; 20 | -------------------------------------------------------------------------------- /frontend/src/context/config/sourceControl/verifyResponseSlice.ts: -------------------------------------------------------------------------------- 1 | export interface ISourceControlVerifyResponse { 2 | repoList: string[]; 3 | } 4 | 5 | export const initSourceControlVerifyResponseState: ISourceControlVerifyResponse = { 6 | repoList: [], 7 | }; 8 | -------------------------------------------------------------------------------- /frontend/src/errors/BadRequestError.ts: -------------------------------------------------------------------------------- 1 | import { IAppError } from '@src/errors/ErrorType'; 2 | 3 | export class BadRequestError extends Error implements IAppError { 4 | code: number; 5 | description?: string; 6 | constructor(message: string, status: number, description: string) { 7 | super(message); 8 | this.description = description; 9 | this.code = status; 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /frontend/src/errors/ErrorType.ts: -------------------------------------------------------------------------------- 1 | export interface IAppError { 2 | code?: number | string; 3 | message: string; 4 | description?: string; 5 | } 6 | -------------------------------------------------------------------------------- /frontend/src/errors/ForbiddenError.ts: -------------------------------------------------------------------------------- 1 | import { IAppError } from '@src/errors/ErrorType'; 2 | 3 | export class ForbiddenError extends Error implements IAppError { 4 | code: number; 5 | description?: string; 6 | constructor(message: string, status: number, description: string) { 7 | super(message); 8 | this.description = description; 9 | this.code = status; 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /frontend/src/errors/InternalServerError.ts: -------------------------------------------------------------------------------- 1 | import { IAppError } from '@src/errors/ErrorType'; 2 | 3 | export class InternalServerError extends Error implements IAppError { 4 | code: number; 5 | description?: string; 6 | constructor(message: string, status: number, description: string) { 7 | super(message); 8 | this.description = description; 9 | this.code = status; 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /frontend/src/errors/NotFoundError.ts: -------------------------------------------------------------------------------- 1 | import { IAppError } from '@src/errors/ErrorType'; 2 | 3 | export class NotFoundError extends Error implements IAppError { 4 | code: number; 5 | description?: string; 6 | constructor(message: string, status: number, description: string) { 7 | super(message); 8 | this.description = description; 9 | this.code = status; 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /frontend/src/errors/TimeoutError.ts: -------------------------------------------------------------------------------- 1 | import { IAppError } from '@src/errors/ErrorType'; 2 | 3 | export class TimeoutError extends Error implements IAppError { 4 | code: number | string; 5 | constructor(message: string, status: number | string) { 6 | super(message); 7 | this.code = status; 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /frontend/src/errors/UnauthorizedError.ts: -------------------------------------------------------------------------------- 1 | import { IAppError } from '@src/errors/ErrorType'; 2 | 3 | export class UnauthorizedError extends Error implements IAppError { 4 | code: number; 5 | description?: string; 6 | constructor(message: string, status: number, description: string) { 7 | super(message); 8 | this.description = description; 9 | this.code = status; 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /frontend/src/errors/UnknownError.ts: -------------------------------------------------------------------------------- 1 | import { MESSAGE } from '@src/constants/resources'; 2 | import { IAppError } from '@src/errors/ErrorType'; 3 | 4 | export class UnknownError extends Error implements IAppError { 5 | constructor() { 6 | super(MESSAGE.UNKNOWN_ERROR); 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /frontend/src/hooks/index.ts: -------------------------------------------------------------------------------- 1 | import type { RootState, AppDispatch } from '@src/store'; 2 | import type { TypedUseSelectorHook } from 'react-redux'; 3 | import { useDispatch, useSelector } from 'react-redux'; 4 | 5 | export const useAppDispatch: () => AppDispatch = useDispatch; 6 | export const useAppSelector: TypedUseSelectorHook = useSelector; 7 | -------------------------------------------------------------------------------- /frontend/src/hooks/reportMapper/exportValidityTime.ts: -------------------------------------------------------------------------------- 1 | import duration from 'dayjs/plugin/duration'; 2 | import dayjs from 'dayjs'; 3 | 4 | export const exportValidityTimeMapper = (exportValidityTime: number | null) => { 5 | dayjs.extend(duration); 6 | const timestamp = exportValidityTime ? exportValidityTime : null; 7 | return timestamp ? dayjs.duration(timestamp).asMinutes() : null; 8 | }; 9 | -------------------------------------------------------------------------------- /frontend/src/hooks/reportMapper/reportUIDataStructure.ts: -------------------------------------------------------------------------------- 1 | import { ReactNode } from 'react'; 2 | 3 | export interface ReportDataWithTwoColumns { 4 | id: number; 5 | name: string | ReactNode; 6 | valueList: ValueWithUnits[]; 7 | } 8 | 9 | export interface ValueWithUnits { 10 | value: number | string; 11 | unit?: string; 12 | } 13 | 14 | export interface ReportDataWithThreeColumns { 15 | id: number; 16 | name: string; 17 | valuesList: { 18 | name: string; 19 | value: string; 20 | }[]; 21 | } 22 | -------------------------------------------------------------------------------- /frontend/src/hooks/useAppDispatch.ts: -------------------------------------------------------------------------------- 1 | import type { RootState, AppDispatch } from '@src/store'; 2 | import type { TypedUseSelectorHook } from 'react-redux'; 3 | import { useDispatch, useSelector } from 'react-redux'; 4 | 5 | export const useAppDispatch: () => AppDispatch = useDispatch; 6 | export const useAppSelector: TypedUseSelectorHook = useSelector; 7 | -------------------------------------------------------------------------------- /frontend/src/main.tsx: -------------------------------------------------------------------------------- 1 | import { ThemeProvider } from '@mui/material'; 2 | import ReactDOM from 'react-dom/client'; 3 | import { Provider } from 'react-redux'; 4 | import { theme } from '@src/theme'; 5 | import { store } from './store'; 6 | import React from 'react'; 7 | import App from './App'; 8 | 9 | ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render( 10 | 11 | 12 | 13 | 14 | 15 | 16 | , 17 | ); 18 | -------------------------------------------------------------------------------- /frontend/src/pages/ErrorPage.tsx: -------------------------------------------------------------------------------- 1 | import { ErrorContent } from '@src/components/ErrorContent'; 2 | import Header from '@src/layouts/Header'; 3 | import React from 'react'; 4 | 5 | const ErrorPage = () => { 6 | return ( 7 | <> 8 |
    9 | 10 | 11 | ); 12 | }; 13 | export default ErrorPage; 14 | -------------------------------------------------------------------------------- /frontend/src/pages/Home.tsx: -------------------------------------------------------------------------------- 1 | import { ProjectDescription } from '@src/components/ProjectDescription'; 2 | import { HomeGuide } from '@src/components/HomeGuide'; 3 | import Header from '@src/layouts/Header'; 4 | import React from 'react'; 5 | 6 | const Home = () => { 7 | return ( 8 | <> 9 |
    10 | 11 | 12 | 13 | ); 14 | }; 15 | export default Home; 16 | -------------------------------------------------------------------------------- /frontend/src/pages/Metrics.tsx: -------------------------------------------------------------------------------- 1 | import { ContextProvider } from '@src/hooks/useMetricsStepValidationCheckContext'; 2 | import { Notification } from '@src/components/Common/NotificationButton'; 3 | import MetricsStepper from '@src/containers/MetricsStepper'; 4 | import Header from '@src/layouts/Header'; 5 | import React from 'react'; 6 | 7 | const Metrics = () => { 8 | return ( 9 | <> 10 |
    11 | 12 | 13 | 14 | 15 | 16 | ); 17 | }; 18 | 19 | export default Metrics; 20 | -------------------------------------------------------------------------------- /frontend/src/utils/types.ts: -------------------------------------------------------------------------------- 1 | export type Nullable = T | null; 2 | export type Optional = T | null | undefined; 3 | -------------------------------------------------------------------------------- /frontend/src/vite-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | declare module '*.svg' { 4 | const content: unknown; 5 | export const ReactComponent: unknown; 6 | export default content; 7 | } 8 | -------------------------------------------------------------------------------- /frontend/tsconfig.node.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "composite": true, 4 | "module": "ESNext", 5 | "moduleResolution": "Node", 6 | "allowSyntheticDefaultImports": true 7 | }, 8 | "include": ["vite.config.ts"] 9 | } 10 | -------------------------------------------------------------------------------- /ops/build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -euo pipefail 3 | 4 | # shellcheck source=/dev/null 5 | source ./ops/base.sh 6 | 7 | display_help() { 8 | echo "Usage: $0 {frontend|backend}" >&2 9 | echo 10 | echo " frontend build frontend" 11 | echo " backend build backend" 12 | echo 13 | exit 1 14 | } 15 | 16 | if [[ "$#" -le 0 ]]; then 17 | display_help 18 | fi 19 | 20 | while [[ "$#" -gt 0 ]]; do 21 | case $1 in 22 | -h | --help) display_help ;; 23 | frontend) build_and_push_image frontend ;; 24 | backend) build_and_push_image backend ;; 25 | *) echo "Unknown parameter passed: $1" ;; 26 | esac 27 | shift 28 | done 29 | -------------------------------------------------------------------------------- /ops/infra/Dockerfile.backend: -------------------------------------------------------------------------------- 1 | FROM gradle:8.7.0-jdk17 AS builder 2 | COPY --chown=gradle:gradle ./backend /home/gradle/src 3 | WORKDIR /home/gradle/src 4 | RUN gradle build --no-daemon 5 | 6 | FROM eclipse-temurin 7 | LABEL app=Heartbeat 8 | LABEL arch=Backend 9 | RUN groupadd -r nonroot && useradd --no-log-init -r -g nonroot nonroot 10 | RUN mkdir -p ./app/output && chown -R nonroot:nonroot ./app/output 11 | RUN mkdir -p ./logs && chown -R nonroot:nonroot ./logs 12 | USER nonroot 13 | 14 | EXPOSE 4322 15 | COPY --from=builder /home/gradle/src/build/libs/heartbeat-backend-0.0.1-SNAPSHOT.jar /app/spring-boot-application.jar 16 | ENTRYPOINT ["java","-jar","/app/spring-boot-application.jar"] 17 | -------------------------------------------------------------------------------- /ops/infra/Dockerfile.e2e: -------------------------------------------------------------------------------- 1 | FROM mcr.microsoft.com/playwright:latest 2 | COPY ./frontend /app 3 | 4 | ENV TZ=Asia/Shanghai 5 | ENV PATH /app/node_modules/.bin:$PATH 6 | WORKDIR /app 7 | RUN npm install -g pnpm 8 | RUN pnpm install --frozen-lockfile -p 9 | RUN pnpm exec playwright install msedge 10 | RUN pnpm exec playwright install chrome 11 | -------------------------------------------------------------------------------- /ops/infra/Dockerfile.frontend: -------------------------------------------------------------------------------- 1 | FROM node:alpine as builder 2 | COPY ./frontend /app 3 | WORKDIR /app 4 | RUN npm install -g pnpm 5 | RUN pnpm install --frozen-lockfile -p 6 | RUN pnpm build 7 | 8 | FROM nginx:latest 9 | LABEL app=Heartbeat 10 | LABEL arch=Frontend 11 | WORKDIR /app 12 | COPY --from=builder /app/dist /usr/share/nginx/html 13 | COPY ./ops/infra/nginx.conf /etc/nginx/nginx.conf 14 | -------------------------------------------------------------------------------- /release-notes/20230228.md: -------------------------------------------------------------------------------- 1 | # Release Notes 2 | ## Feb 28 2023 - Released Heartbeat 0.9 3 | 1. Performance improvement 4 | 1. Using multiple threading for generate report for buildkite 5 | 2. Remove the logic of fetching unused data (Remove all NON DONE cards for Kanban) 6 | 3. Added cache to avoid duplicated API invocation for buildkite call 7 | 2. Add more logs for debugging purpose 8 | -------------------------------------------------------------------------------- /release-notes/20231106.md: -------------------------------------------------------------------------------- 1 | # Release Notes 2 | ## Nov 6 2023 - Released Heartbeat 1.1.2 3 | - New feature 4 | - Support to pass customized story point on Jira board 5 | - Support to associate last or historical board assignee 6 | -------------------------------------------------------------------------------- /release-notes/20231204.md: -------------------------------------------------------------------------------- 1 | # Release Notes 2 | ## Dec 4 2023 - Released Heartbeat 1.1.4 3 | - Enhancement 4 | - Change generate report from sync call to async design to solve the 504 timeout issue 5 | - Support multiple status within one Jira column to map to Heartbeat board state 6 | -------------------------------------------------------------------------------- /release-notes/20240402.md: -------------------------------------------------------------------------------- 1 | # Release Notes 2 | ## Apr 2 2024 - Released Heartbeat 1.1.6 3 | - New Features 4 | - Introduced new metric - rework 5 | - Enhancement 6 | - Calendar picker enhancement which could not select future date 7 | - Refine Pipeline selection rule so that prevent to select selected items 8 | - Improve error handling on configure page verification flow 9 | - Enhance Github token verification to make it user-friendly to customer 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://docs.renovatebot.com/renovate-schema.json", 3 | "semanticCommits": true, 4 | "schedule": "* 20 * * 5", 5 | "timezone": "Asia/Shanghai", 6 | "packageRules": [ 7 | { 8 | "updateTypes": ["minor", "patch", "pin", "digest"], 9 | "automerge": true 10 | }, 11 | { 12 | "depTypeList": ["devDependencies"], 13 | "automerge": true 14 | } 15 | ], 16 | "extends": ["config:base"] 17 | } 18 | --------------------------------------------------------------------------------