history.ts 6.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305
  1. import eq from "lodash/eq"
  2. import { pluck } from "rxjs/operators"
  3. import DispatchingStore, { defineDispatchers } from "./DispatchingStore"
  4. import { completedRESTResponse$ } from "./RESTSession"
  5. import {
  6. HoppRESTRequest,
  7. translateToNewRequest,
  8. } from "~/helpers/types/HoppRESTRequest"
  9. import {
  10. HoppGQLRequest,
  11. translateToGQLRequest,
  12. } from "~/helpers/types/HoppGQLRequest"
  13. export type RESTHistoryEntry = {
  14. v: number
  15. request: HoppRESTRequest
  16. responseMeta: {
  17. duration: number | null
  18. statusCode: number | null
  19. }
  20. star: boolean
  21. id?: string // For when Firebase Firestore is set
  22. }
  23. export type GQLHistoryEntry = {
  24. v: number
  25. request: HoppGQLRequest
  26. response: string
  27. star: boolean
  28. id?: string // For when Firestore ID is set
  29. }
  30. export function makeRESTHistoryEntry(
  31. x: Omit<RESTHistoryEntry, "v">
  32. ): RESTHistoryEntry {
  33. return {
  34. v: 1,
  35. ...x,
  36. }
  37. }
  38. export function makeGQLHistoryEntry(
  39. x: Omit<GQLHistoryEntry, "v">
  40. ): GQLHistoryEntry {
  41. return {
  42. v: 1,
  43. ...x,
  44. }
  45. }
  46. export function translateToNewRESTHistory(x: any): RESTHistoryEntry {
  47. if (x.v === 1) return x
  48. // Legacy
  49. const request = translateToNewRequest(x)
  50. const star = x.star ?? false
  51. const duration = x.duration ?? null
  52. const statusCode = x.status ?? null
  53. const obj: RESTHistoryEntry = makeRESTHistoryEntry({
  54. request,
  55. star,
  56. responseMeta: {
  57. duration,
  58. statusCode,
  59. },
  60. })
  61. if (x.id) obj.id = x.id
  62. return obj
  63. }
  64. export function translateToNewGQLHistory(x: any): GQLHistoryEntry {
  65. if (x.v === 1) return x
  66. // Legacy
  67. const request = translateToGQLRequest(x)
  68. const star = x.star ?? false
  69. const response = x.response ?? ""
  70. const obj: GQLHistoryEntry = makeGQLHistoryEntry({
  71. request,
  72. star,
  73. response,
  74. })
  75. if (x.id) obj.id = x.id
  76. return obj
  77. }
  78. export const defaultRESTHistoryState = {
  79. state: [] as RESTHistoryEntry[],
  80. }
  81. export const defaultGraphqlHistoryState = {
  82. state: [] as GQLHistoryEntry[],
  83. }
  84. export const HISTORY_LIMIT = 50
  85. type RESTHistoryType = typeof defaultRESTHistoryState
  86. type GraphqlHistoryType = typeof defaultGraphqlHistoryState
  87. const RESTHistoryDispatchers = defineDispatchers({
  88. setEntries(_: RESTHistoryType, { entries }: { entries: RESTHistoryEntry[] }) {
  89. return {
  90. state: entries,
  91. }
  92. },
  93. addEntry(
  94. currentVal: RESTHistoryType,
  95. { entry }: { entry: RESTHistoryEntry }
  96. ) {
  97. return {
  98. state: [entry, ...currentVal.state].slice(0, HISTORY_LIMIT),
  99. }
  100. },
  101. deleteEntry(
  102. currentVal: RESTHistoryType,
  103. { entry }: { entry: RESTHistoryEntry }
  104. ) {
  105. return {
  106. state: currentVal.state.filter((e) => !eq(e, entry)),
  107. }
  108. },
  109. clearHistory() {
  110. return {
  111. state: [],
  112. }
  113. },
  114. toggleStar(
  115. currentVal: RESTHistoryType,
  116. { entry }: { entry: RESTHistoryEntry }
  117. ) {
  118. return {
  119. state: currentVal.state.map((e) => {
  120. if (eq(e, entry) && e.star !== undefined) {
  121. return {
  122. ...e,
  123. star: !e.star,
  124. }
  125. }
  126. return e
  127. }),
  128. }
  129. },
  130. })
  131. const GQLHistoryDispatchers = defineDispatchers({
  132. setEntries(
  133. _: GraphqlHistoryType,
  134. { entries }: { entries: GQLHistoryEntry[] }
  135. ) {
  136. return {
  137. state: entries,
  138. }
  139. },
  140. addEntry(
  141. currentVal: GraphqlHistoryType,
  142. { entry }: { entry: GQLHistoryEntry }
  143. ) {
  144. return {
  145. state: [entry, ...currentVal.state].slice(0, HISTORY_LIMIT),
  146. }
  147. },
  148. deleteEntry(
  149. currentVal: GraphqlHistoryType,
  150. { entry }: { entry: GQLHistoryEntry }
  151. ) {
  152. return {
  153. state: currentVal.state.filter((e) => !eq(e, entry)),
  154. }
  155. },
  156. clearHistory() {
  157. return {
  158. state: [],
  159. }
  160. },
  161. toggleStar(
  162. currentVal: GraphqlHistoryType,
  163. { entry }: { entry: GQLHistoryEntry }
  164. ) {
  165. return {
  166. state: currentVal.state.map((e) => {
  167. if (eq(e, entry) && e.star !== undefined) {
  168. return {
  169. ...e,
  170. star: !e.star,
  171. }
  172. }
  173. return e
  174. }),
  175. }
  176. },
  177. })
  178. export const restHistoryStore = new DispatchingStore(
  179. defaultRESTHistoryState,
  180. RESTHistoryDispatchers
  181. )
  182. export const graphqlHistoryStore = new DispatchingStore(
  183. defaultGraphqlHistoryState,
  184. GQLHistoryDispatchers
  185. )
  186. export const restHistory$ = restHistoryStore.subject$.pipe(pluck("state"))
  187. export const graphqlHistory$ = graphqlHistoryStore.subject$.pipe(pluck("state"))
  188. export function setRESTHistoryEntries(entries: RESTHistoryEntry[]) {
  189. restHistoryStore.dispatch({
  190. dispatcher: "setEntries",
  191. payload: { entries },
  192. })
  193. }
  194. export function addRESTHistoryEntry(entry: RESTHistoryEntry) {
  195. restHistoryStore.dispatch({
  196. dispatcher: "addEntry",
  197. payload: { entry },
  198. })
  199. }
  200. export function deleteRESTHistoryEntry(entry: RESTHistoryEntry) {
  201. restHistoryStore.dispatch({
  202. dispatcher: "deleteEntry",
  203. payload: { entry },
  204. })
  205. }
  206. export function clearRESTHistory() {
  207. restHistoryStore.dispatch({
  208. dispatcher: "clearHistory",
  209. payload: {},
  210. })
  211. }
  212. export function toggleRESTHistoryEntryStar(entry: RESTHistoryEntry) {
  213. restHistoryStore.dispatch({
  214. dispatcher: "toggleStar",
  215. payload: { entry },
  216. })
  217. }
  218. export function setGraphqlHistoryEntries(entries: GQLHistoryEntry[]) {
  219. graphqlHistoryStore.dispatch({
  220. dispatcher: "setEntries",
  221. payload: { entries },
  222. })
  223. }
  224. export function addGraphqlHistoryEntry(entry: GQLHistoryEntry) {
  225. graphqlHistoryStore.dispatch({
  226. dispatcher: "addEntry",
  227. payload: { entry },
  228. })
  229. }
  230. export function deleteGraphqlHistoryEntry(entry: GQLHistoryEntry) {
  231. graphqlHistoryStore.dispatch({
  232. dispatcher: "deleteEntry",
  233. payload: { entry },
  234. })
  235. }
  236. export function clearGraphqlHistory() {
  237. graphqlHistoryStore.dispatch({
  238. dispatcher: "clearHistory",
  239. payload: {},
  240. })
  241. }
  242. export function toggleGraphqlHistoryEntryStar(entry: GQLHistoryEntry) {
  243. graphqlHistoryStore.dispatch({
  244. dispatcher: "toggleStar",
  245. payload: { entry },
  246. })
  247. }
  248. // Listen to completed responses to add to history
  249. completedRESTResponse$.subscribe((res) => {
  250. if (res !== null) {
  251. if (res.type === "loading" || res.type === "network_fail") return
  252. addRESTHistoryEntry(
  253. makeRESTHistoryEntry({
  254. request: res.req,
  255. responseMeta: {
  256. duration: res.meta.responseDuration,
  257. statusCode: res.statusCode,
  258. },
  259. star: false,
  260. })
  261. )
  262. }
  263. })