history.ts 6.9 KB

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