history.ts 6.6 KB

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