useGithubWorkflow.ts 3.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137
  1. import { useCallback, useContext, useEffect, useMemo, useState } from "react";
  2. import { useQueries } from "@tanstack/react-query";
  3. import axios from "axios";
  4. import { z } from "zod";
  5. import { type PorterAppRecord } from "main/home/app-dashboard/app-view/AppView";
  6. import api from "shared/api";
  7. import { Context } from "shared/Context";
  8. type WorkflowResult = {
  9. githubWorkflowFilename: string;
  10. isLoading: boolean;
  11. userHasGithubAccess: boolean;
  12. };
  13. export const useGithubWorkflow = ({
  14. porterApp,
  15. fileNames,
  16. previouslyBuilt = false,
  17. }: {
  18. porterApp: PorterAppRecord;
  19. fileNames: string[];
  20. previouslyBuilt?: boolean;
  21. }): WorkflowResult => {
  22. const { currentProject, currentCluster } = useContext(Context);
  23. const [githubWorkflowFilename, setGithubWorkflowName] = useState<string>("");
  24. const [userHasGithubAccess, setUserHasGithubAccess] = useState<boolean>(true);
  25. const gitMetadata = useMemo(() => {
  26. const repoNameParts = z
  27. .tuple([z.string(), z.string()])
  28. .safeParse(porterApp.repo_name?.split("/"));
  29. if (
  30. !repoNameParts.success ||
  31. !porterApp.git_repo_id ||
  32. !porterApp.git_branch
  33. ) {
  34. return {
  35. repo_id: 0,
  36. owner: "",
  37. name: "",
  38. branch: "",
  39. };
  40. }
  41. return {
  42. repo_id: porterApp.git_repo_id,
  43. owner: repoNameParts.data[0],
  44. name: repoNameParts.data[1],
  45. branch: porterApp.git_branch,
  46. };
  47. }, [porterApp.git_repo_id, porterApp.repo_name, porterApp.git_branch]);
  48. const fetchGithubWorkflow = useCallback(
  49. async (fileName: string) => {
  50. try {
  51. if (githubWorkflowFilename !== "") {
  52. return githubWorkflowFilename;
  53. }
  54. if (currentProject == null || currentCluster == null) {
  55. return "";
  56. }
  57. const res = await api.getBranchContents(
  58. "<token>",
  59. {
  60. dir: `./.github/workflows/${fileName}`,
  61. },
  62. {
  63. project_id: currentProject.id,
  64. git_repo_id: gitMetadata.repo_id,
  65. kind: "github",
  66. owner: gitMetadata.owner,
  67. name: gitMetadata.name,
  68. branch: gitMetadata.branch,
  69. }
  70. );
  71. if (res.data) {
  72. return fileName;
  73. }
  74. return "";
  75. } catch (err) {
  76. return "";
  77. }
  78. },
  79. [currentProject, currentCluster, gitMetadata, githubWorkflowFilename]
  80. );
  81. const enabled =
  82. !previouslyBuilt &&
  83. !!currentProject &&
  84. !!currentCluster &&
  85. githubWorkflowFilename === "";
  86. const results = useQueries({
  87. queries: fileNames.map((fn) => ({
  88. queryKey: [
  89. `checkForApplicationWorkflow_${fn}`,
  90. currentProject?.id,
  91. currentCluster?.id,
  92. fn,
  93. previouslyBuilt,
  94. ],
  95. queryFn: async () => await fetchGithubWorkflow(fn),
  96. enabled,
  97. refetchInterval: 5000,
  98. retry: (_failureCount: number, error: unknown) => {
  99. if (axios.isAxiosError(error) && error.response?.status === 403) {
  100. setUserHasGithubAccess(false);
  101. return false;
  102. }
  103. return true;
  104. },
  105. refetchOnWindowFocus: false,
  106. })),
  107. });
  108. useEffect(() => {
  109. const applicationWorkflowCheck = results
  110. .map(({ data }) => data)
  111. .find((d) => !!d);
  112. if (applicationWorkflowCheck) {
  113. setGithubWorkflowName(applicationWorkflowCheck);
  114. }
  115. }, [results]);
  116. return {
  117. githubWorkflowFilename,
  118. isLoading: results.some((r) => r.isLoading),
  119. userHasGithubAccess,
  120. };
  121. };