@appscode/design-system 1.0.43-alpha.215 → 1.0.43-alpha.217

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/package.json +4 -2
  2. package/plugins/caching.ts +190 -0
package/package.json CHANGED
@@ -1,13 +1,15 @@
1
1
  {
2
2
  "name": "@appscode/design-system",
3
- "version": "1.0.43-alpha.215",
3
+ "version": "1.0.43-alpha.217",
4
4
  "description": "A design system for Appscode websites and dashboards made using Bulma",
5
5
  "main": "main.scss",
6
6
  "scripts": {
7
7
  "test": "echo \"Error: no test specified\" && exit 1"
8
8
  },
9
9
  "dependencies": {
10
- "@appscode/design-system-images": "^0.0.13"
10
+ "@appscode/design-system-images": "^0.0.13",
11
+ "crypto-js": "^4.1.1",
12
+ "fast-json-stable-stringify": "^2.1.0"
11
13
  },
12
14
  "repository": {
13
15
  "type": "git",
@@ -0,0 +1,190 @@
1
+ import { AxiosRequestConfig, AxiosResponse } from "axios";
2
+ import md5 from "crypto-js/md5";
3
+ import stringify from "fast-json-stable-stringify";
4
+
5
+ const resourceListApiRgx =
6
+ /^\/clusters\/([^/]+)\/([^/]+)\/proxy\/([^/]+)\/([^/]+)(\/namespaces\/([^/?]+))?\/([^/?]+)(.*)$/;
7
+ const usermenuUpdateApiRgx =
8
+ /^\/clusters\/([^/]+)\/([^/]+)\/proxy\/meta\.k8s\.appscode\.com\/([^/]+)\/usermenus\/cluster$/;
9
+
10
+ function getRequestInterceptor(config: AxiosRequestConfig) {
11
+ // get call
12
+ const { url, _recurringCall, params } = config;
13
+ const matchListApi = url?.match(resourceListApiRgx);
14
+ if (matchListApi) {
15
+ // url matches list / render api call
16
+ const [, user, cluster, group, version, , namespace, resource] =
17
+ matchListApi;
18
+
19
+ const suffix = `${user}.${cluster}.${group}.${version}.${resource}${
20
+ namespace ? ".ns." + namespace : ""
21
+ }${params?.q ? ".q." + md5(params?.q).toString() : ""}`;
22
+
23
+ if (_recurringCall) {
24
+ // always send latest date time as ctag
25
+ return new Date().getTime();
26
+ } else {
27
+ // read ctag from local storage if exists
28
+ const storageKey = `ctag.${suffix}`;
29
+ // read local storage ctag
30
+ return localStorage.getItem(storageKey);
31
+ }
32
+ }
33
+ }
34
+
35
+ function getResponseInterceptor(resp: AxiosResponse) {
36
+ // get call
37
+ const { config, headers } = resp;
38
+ const { url, _recurringCall, params } = config;
39
+ const matchListApi = url?.match(resourceListApiRgx);
40
+ if (matchListApi) {
41
+ // url matches list / render api call
42
+ // window.console.log(matchListApi)
43
+ const [, user, cluster, group, version, , namespace, resource] =
44
+ matchListApi;
45
+
46
+ const suffix = `${user}.${cluster}.${group}.${version}.${resource}${
47
+ namespace ? ".ns." + namespace : ""
48
+ }${params?.q ? ".q." + md5(params?.q).toString() : ""}`;
49
+
50
+ const storageKey = `etag.${suffix}`;
51
+ // local storage etag
52
+ const eTag = localStorage.getItem(storageKey);
53
+ // etag in header
54
+ const headerEtag = headers?.["etag"];
55
+
56
+ // window.console.log({ eTag, headerEtag, _recurringCall, url })
57
+
58
+ if (_recurringCall) {
59
+ // for recurring api call
60
+ if (headerEtag && headerEtag !== eTag) {
61
+ // etag missmatch
62
+ if (params.ctag) {
63
+ // save request query parameter ctag into local storage
64
+ localStorage.setItem(`ctag.${suffix}`, params.ctag);
65
+ }
66
+ // update store etag with latest header etag
67
+ localStorage.setItem(`etag.${suffix}`, headerEtag);
68
+
69
+ if (namespace) {
70
+ // for namespaced list calls if etag missmatch happens
71
+ // update the ctag storage for all namespaces
72
+ // this will force the all namespaced list call to ignore disk cache
73
+ localStorage.setItem(
74
+ `etag.${user}.${cluster}.${group}.${version}.${resource}`,
75
+ new Date().getTime().toString()
76
+ );
77
+ }
78
+ }
79
+ } else {
80
+ // not recurring api call
81
+ if (eTag !== headerEtag && headerEtag) {
82
+ // if the etag missmatches, then the data has changed
83
+ // update local etag
84
+ localStorage.setItem(storageKey, headerEtag);
85
+ }
86
+ }
87
+ }
88
+ }
89
+
90
+ function putResponseInterceptor(resp: AxiosResponse) {
91
+ const { config } = resp;
92
+ const { url } = config;
93
+ const matchUsermenuUpdate = url?.match(usermenuUpdateApiRgx);
94
+ if (matchUsermenuUpdate) {
95
+ // user menu update api is called
96
+ const [, user, cluster, version] = matchUsermenuUpdate;
97
+
98
+ // calculate the local storage key
99
+ const renderStorageKey = `ctag.${user}.${cluster}.meta.k8s.appscode.com.${version}.rendermenus.q.${md5(
100
+ stringify({
101
+ apiVersion: `meta.k8s.appscode.com/${version}`,
102
+ kind: "RenderMenu",
103
+ request: {
104
+ menu: "cluster",
105
+ mode: "Accordion",
106
+ },
107
+ })
108
+ ).toString()}`;
109
+ const availableMenuStorageKey = `ctag.${user}.${cluster}.meta.k8s.appscode.com.${version}.usermenus`;
110
+
111
+ // set new ctag to that storage key
112
+ // next time render menu is called, cached data won't be used
113
+ localStorage.setItem(renderStorageKey, new Date().getTime().toString());
114
+ // for all available menus api
115
+ localStorage.setItem(
116
+ availableMenuStorageKey,
117
+ new Date().getTime().toString()
118
+ );
119
+ }
120
+ }
121
+
122
+ export function handleCacheFromYamls(
123
+ yamls: Array<{
124
+ apiVersion: string;
125
+ kind: string;
126
+ metadata: { name: string; namespace: string };
127
+ }>,
128
+ user: string,
129
+ cluster: string,
130
+ gkToGvrMap: Record<string, string>
131
+ ) {
132
+ return function () {
133
+ yamls.forEach((yaml) => {
134
+ const { apiVersion, kind, metadata } = yaml;
135
+ const { namespace } = metadata || {};
136
+ const apiVersionMatch = apiVersion.match(/^(([^/]+)\/)?([^/]+)$/);
137
+ if (apiVersionMatch) {
138
+ // separate the group and version
139
+ const [, , group] = apiVersionMatch;
140
+ const groupKind = `${group || "core"}/${kind}`;
141
+ const gvr = (gkToGvrMap && gkToGvrMap[groupKind]) || "";
142
+
143
+ const storageKey = `ctag.${user}.${cluster}.${gvr.replaceAll(
144
+ "/",
145
+ "."
146
+ )}`;
147
+
148
+ // clear list cache for all namespaces
149
+ localStorage.setItem(storageKey, new Date().getTime().toString());
150
+
151
+ // clear list cache for specific namespace
152
+ localStorage.setItem(
153
+ `${storageKey}.ns.${namespace || "default"}`,
154
+ new Date().getTime().toString()
155
+ );
156
+ }
157
+ });
158
+ };
159
+ }
160
+
161
+ export async function requestInterceptor(config: AxiosRequestConfig) {
162
+ // parse the request url
163
+ const { method, params } = config;
164
+
165
+ let ctag;
166
+
167
+ if (method === "get") {
168
+ ctag = getRequestInterceptor(config);
169
+ }
170
+
171
+ return {
172
+ ...config,
173
+ params: { ...params, ctag },
174
+ };
175
+ }
176
+
177
+ export function responseInterceptor(resp: AxiosResponse) {
178
+ const { config } = resp;
179
+ // parse the request url
180
+ const { method } = config;
181
+
182
+ if (method === "get") {
183
+ // get call
184
+ getResponseInterceptor(resp);
185
+ } else if (method === "put") {
186
+ putResponseInterceptor(resp);
187
+ }
188
+
189
+ return resp;
190
+ }