Context.tsx 6.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212
  1. import React, { Component } from "react";
  2. import {
  3. CapabilityType,
  4. ClusterType,
  5. ContextProps,
  6. ProjectType,
  7. UsageData,
  8. } from "shared/types";
  9. import { pushQueryParams } from "shared/routing";
  10. import api from "./api";
  11. const Context = React.createContext<Partial<ContextProps>>(null);
  12. const { Provider } = Context;
  13. const ContextConsumer = Context.Consumer;
  14. type PropsType = {
  15. history: any;
  16. location: any;
  17. };
  18. type StateType = GlobalContextType;
  19. export interface GlobalContextType {
  20. currentModal: string;
  21. currentModalData: any;
  22. setCurrentModal: (currentModal: string, currentModalData?: any) => void;
  23. currentOverlay: {
  24. message: string;
  25. onYes: any;
  26. onNo: any;
  27. };
  28. setCurrentOverlay: (x: any) => void;
  29. currentError: string | null;
  30. setCurrentError: (currentError: string) => void;
  31. currentCluster: ClusterType;
  32. setCurrentCluster: (currentCluster: ClusterType, callback?: any) => void;
  33. currentProject: ProjectType | null;
  34. setCurrentProject: (
  35. currentProject: ProjectType,
  36. callback?: () => void
  37. ) => void;
  38. projects: ProjectType[];
  39. setProjects: (projects: ProjectType[]) => void;
  40. user: any;
  41. setUser: (userId: number, email: string) => void;
  42. devOpsMode: boolean;
  43. setDevOpsMode: (devOpsMode: boolean) => void;
  44. capabilities: CapabilityType;
  45. setCapabilities: (capabilities: CapabilityType) => void;
  46. clearContext: () => void;
  47. edition: "ee" | "ce";
  48. setEdition: (appVersion: string) => void;
  49. hasBillingEnabled: boolean;
  50. setHasBillingEnabled: (isBillingEnabled: boolean) => void;
  51. usage: UsageData;
  52. setUsage: (usage: UsageData) => void;
  53. queryUsage: (retry?: number) => Promise<void>;
  54. hasFinishedOnboarding: boolean;
  55. setHasFinishedOnboarding: (onboardingStatus: boolean) => void;
  56. canCreateProject: boolean;
  57. setCanCreateProject: (canCreateProject: boolean) => void;
  58. enableGitlab: boolean;
  59. setEnableGitlab: (enableGitlab: boolean) => void;
  60. shouldRefreshClusters: boolean;
  61. setShouldRefreshClusters: (shouldRefreshClusters: boolean) => void;
  62. }
  63. /**
  64. * Component managing a universal (application-wide) data store.
  65. *
  66. * Important Usage Notes:
  67. * 1) Each field must have an accompanying setter
  68. * 2) No function calls are allowed from within Context (not counting
  69. * initialization)
  70. * 3) Context should be used as a last-resort (changes will re-render ALL
  71. * components consuming Context)
  72. * 4) As a rule of thumb, Context should not be used for UI-related state
  73. */
  74. class ContextProvider extends Component<PropsType, StateType> {
  75. state: GlobalContextType = {
  76. currentModal: null,
  77. currentModalData: null,
  78. setCurrentModal: (currentModal: string, currentModalData?: any) => {
  79. this.setState({ currentModal, currentModalData });
  80. },
  81. currentOverlay: null,
  82. setCurrentOverlay: (x: any) => this.setState({ currentOverlay: x }),
  83. currentError: null,
  84. setCurrentError: (currentError: string) => {
  85. this.setState({ currentError });
  86. },
  87. currentCluster: {
  88. id: -1,
  89. name: "",
  90. server: "",
  91. service_account_id: -1,
  92. infra_id: -1,
  93. service: "",
  94. agent_integration_enabled: false,
  95. },
  96. setCurrentCluster: (currentCluster: ClusterType, callback?: any) => {
  97. localStorage.setItem(
  98. this.state.currentProject.id + "-cluster",
  99. JSON.stringify(currentCluster)
  100. );
  101. this.setState({ currentCluster }, () => {
  102. callback && callback();
  103. });
  104. },
  105. currentProject: null,
  106. setCurrentProject: (currentProject: ProjectType, callback?: any) => {
  107. if (currentProject) {
  108. localStorage.setItem("currentProject", currentProject.id.toString());
  109. pushQueryParams(this.props, {
  110. project_id: currentProject.id.toString(),
  111. });
  112. } else {
  113. localStorage.removeItem("currentProject");
  114. }
  115. this.setState({ currentProject }, () => {
  116. callback && callback();
  117. });
  118. },
  119. projects: [],
  120. setProjects: (projects: ProjectType[]) => {
  121. projects.sort((a: any, b: any) => (a.name > b.name ? 1 : -1));
  122. this.setState({ projects });
  123. },
  124. user: null,
  125. setUser: (userId: number, email: string) => {
  126. this.setState({
  127. user: { userId, email, isPorterUser: email?.endsWith("@porter.run") },
  128. });
  129. },
  130. devOpsMode: true,
  131. setDevOpsMode: (devOpsMode: boolean) => {
  132. this.setState({ devOpsMode });
  133. },
  134. capabilities: null,
  135. setCapabilities: (capabilities: CapabilityType) => {
  136. this.setState({ capabilities });
  137. },
  138. clearContext: () => {
  139. this.setState({
  140. currentModal: null,
  141. currentModalData: null,
  142. currentError: null,
  143. currentCluster: null,
  144. currentProject: null,
  145. projects: [],
  146. user: null,
  147. devOpsMode: true,
  148. });
  149. },
  150. edition: "ce",
  151. setEdition: (version: string) => {
  152. const [edition] = version.split("-").reverse();
  153. // typesafe just in case we mess up something it will default to ce
  154. if (edition === "ce" || edition === "ee") {
  155. this.setState({ edition });
  156. }
  157. },
  158. hasBillingEnabled: false,
  159. setHasBillingEnabled: (isBillingEnabled: boolean) => {
  160. this.setState({ hasBillingEnabled: isBillingEnabled });
  161. },
  162. usage: null,
  163. setUsage: (usage: UsageData) => {
  164. this.setState({ usage });
  165. },
  166. queryUsage: async (retry: number = 0) => {
  167. api
  168. .getUsage("<token>", {}, { project_id: this.state?.currentProject?.id })
  169. .then((res) => {
  170. if (JSON.stringify(res.data) !== JSON.stringify(this.state.usage)) {
  171. this.state.setUsage(res.data);
  172. } else {
  173. if (retry < 10) {
  174. setTimeout(() => {
  175. this.state.queryUsage(retry + 1);
  176. }, 1000);
  177. }
  178. }
  179. });
  180. },
  181. hasFinishedOnboarding: false,
  182. setHasFinishedOnboarding: (onboardingStatus) => {
  183. this.setState({ hasFinishedOnboarding: onboardingStatus });
  184. },
  185. canCreateProject: false,
  186. setCanCreateProject: (canCreateProject: boolean) => {
  187. this.setState({ canCreateProject });
  188. },
  189. enableGitlab: false,
  190. setEnableGitlab: (enableGitlab) => {
  191. this.setState({ enableGitlab });
  192. },
  193. shouldRefreshClusters: false,
  194. setShouldRefreshClusters: (shouldRefreshClusters) => {
  195. this.setState({ shouldRefreshClusters });
  196. },
  197. };
  198. render() {
  199. return <Provider value={{ ...this.state }}>{this.props.children}</Provider>;
  200. }
  201. }
  202. export { Context, ContextProvider, ContextConsumer };