Context.tsx 6.7 KB

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