• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

thoughtspot / visual-embed-sdk / #1435

14 Jan 2025 09:42PM UTC coverage: 41.895% (-52.0%) from 93.86%
#1435

Pull #65

Prashant.patil
SCAL-233454-exp checking-mixpanel-error
Pull Request #65: test-exported memb

273 of 1106 branches covered (24.68%)

Branch coverage included in aggregate %.

84 of 356 new or added lines in 14 files covered. (23.6%)

1011 existing lines in 23 files now uncovered.

1301 of 2651 relevant lines covered (49.08%)

9.11 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

66.8
/src/utils.ts
1
/**
2
 * Copyright (c) 2023
3
 *
4
 * Common utility functions for ThoughtSpot Visual Embed SDK
5
 * @summary Utils
6
 * @author Ayon Ghosh <ayon.ghosh@thoughtspot.com>
7
 */
8

9
import merge from 'ts-deepmerge';
28✔
10
import {
11
    EmbedConfig,
12
    QueryParams,
13
    RuntimeFilter,
14
    CustomisationsInterface,
15
    DOMSelector,
16
    ViewConfig,
17
    RuntimeParameter,
18
} from './types';
19
import { WebViewConfig } from './native/types';
20

21
/**
22
 * Construct a runtime filters query string from the given filters.
23
 * Refer to the following docs for more details on runtime filter syntax:
24
 * https://cloud-docs.thoughtspot.com/admin/ts-cloud/apply-runtime-filter.html
25
 * https://cloud-docs.thoughtspot.com/admin/ts-cloud/runtime-filter-operators.html
26
 * @param runtimeFilters
27
 */
28
export const getFilterQuery = (runtimeFilters: RuntimeFilter[]): string | null => {
28✔
29
    if (runtimeFilters && runtimeFilters.length) {
5✔
30
        const filters = runtimeFilters.map((filter, valueIndex) => {
3✔
31
            const index = valueIndex + 1;
5✔
32
            const filterExpr = [];
5✔
33
            filterExpr.push(`col${index}=${encodeURIComponent(filter.columnName)}`);
5✔
34
            filterExpr.push(`op${index}=${filter.operator}`);
5✔
35
            filterExpr.push(
5✔
36
                filter.values.map((value) => {
37
                    const encodedValue = typeof value === 'bigint' ? value.toString() : value;
6!
38
                    return `val${index}=${encodeURIComponent(String(encodedValue))}`;
6✔
39
                }).join('&'),
40
            );
41

42
            return filterExpr.join('&');
5✔
43
        });
44

45
        return `${filters.join('&')}`;
3✔
46
    }
47

48
    return null;
2✔
49
};
50

51
/**
52
 * Construct a runtime parameter override query string from the given option.
53
 * @param runtimeParameters
54
 */
55
export const getRuntimeParameters = (runtimeParameters: RuntimeParameter[]): string => {
28✔
56
    if (runtimeParameters && runtimeParameters.length) {
3✔
57
        const params = runtimeParameters.map((param, valueIndex) => {
2✔
58
            const index = valueIndex + 1;
4✔
59
            const filterExpr = [];
4✔
60
            filterExpr.push(`param${index}=${encodeURIComponent(param.name)}`);
4✔
61
            filterExpr.push(`paramVal${index}=${encodeURIComponent(param.value)}`);
4✔
62

63
            return filterExpr.join('&');
4✔
64
        });
65

66
        return `${params.join('&')}`;
2✔
67
    }
68

69
    return null;
1✔
70
};
71

72
/**
73
 * Convert a value to a string representation to be sent as a query
74
 * parameter to the ThoughtSpot app.
75
 * @param value Any parameter value
76
 */
77
export const serializeParam = (value: any) => {
28✔
78
    // do not serialize primitive types
UNCOV
79
    if (typeof value === 'string' || typeof value === 'number' || typeof value === 'boolean') {
×
UNCOV
80
        return value;
×
81
    }
82

UNCOV
83
    return JSON.stringify(value);
×
84
};
85

86
/**
87
 * Convert a value to a string:
88
 * in case of an array, we convert it to CSV.
89
 * in case of any other type, we directly return the value.
90
 * @param value
91
 */
92
export const paramToString = (value: any) => (Array.isArray(value) ? value.join(',') : value);
28!
93

94
/**
95
 * Return a query param string composed from the given params object
96
 * @param queryParams
97
 * @param shouldSerializeParamValues
98
 */
99
export const getQueryParamString = (
28✔
100
    queryParams: QueryParams,
101
    shouldSerializeParamValues = false,
3✔
102
): string => {
103
    const qp: string[] = [];
3✔
104
    const params = Object.keys(queryParams);
3✔
105
    params.forEach((key) => {
3✔
106
        const val = queryParams[key];
4✔
107
        if (val !== undefined) {
4✔
108
            const serializedValue = shouldSerializeParamValues
3!
109
                ? serializeParam(val)
110
                : paramToString(val);
111
            qp.push(`${key}=${serializedValue}`);
3✔
112
        }
113
    });
114

115
    if (qp.length) {
3✔
116
        return qp.join('&');
2✔
117
    }
118

119
    return null;
1✔
120
};
121

122
/**
123
 * Get a string representation of a dimension value in CSS
124
 * If numeric, it is considered in pixels.
125
 * @param value
126
 */
127
export const getCssDimension = (value: number | string): string => {
28✔
128
    if (typeof value === 'number') {
4✔
129
        return `${value}px`;
1✔
130
    }
131

132
    return value;
3✔
133
};
134

135
export const getSSOMarker = (markerId: string) => {
28✔
UNCOV
136
    const encStringToAppend = encodeURIComponent(markerId);
×
UNCOV
137
    return `tsSSOMarker=${encStringToAppend}`;
×
138
};
139

140
/**
141
 * Append a string to a URL's hash fragment
142
 * @param url A URL
143
 * @param stringToAppend The string to append to the URL hash
144
 */
145
export const appendToUrlHash = (url: string, stringToAppend: string) => {
28✔
146
    let outputUrl = url;
6✔
147
    const encStringToAppend = encodeURIComponent(stringToAppend);
6✔
148

149
    const marker = `tsSSOMarker=${encStringToAppend}`;
6✔
150

151
    let splitAdder = '';
6✔
152

153
    if (url.indexOf('#') >= 0) {
6✔
154
        // If second half of hash contains a '?' already add a '&' instead of
155
        // '?' which appends to query params.
156
        splitAdder = url.split('#')[1].indexOf('?') >= 0 ? '&' : '?';
3!
157
    } else {
158
        splitAdder = '#?';
3✔
159
    }
160
    outputUrl = `${outputUrl}${splitAdder}${marker}`;
6✔
161

162
    return outputUrl;
6✔
163
};
164

165
/**
166
 *
167
 * @param url
168
 * @param stringToAppend
169
 * @param path
170
 */
171
export function getRedirectUrl(url: string, stringToAppend: string, path = '') {
28✔
172
    const targetUrl = path ? new URL(path, window.location.origin).href : url;
4✔
173
    return appendToUrlHash(targetUrl, stringToAppend);
4✔
174
}
175

176
export const getEncodedQueryParamsString = (queryString: string) => {
28✔
177
    if (!queryString) {
2✔
178
        return queryString;
1✔
179
    }
180
    return btoa(queryString).replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '');
1✔
181
};
182

183
export const getOffsetTop = (element: any) => {
28✔
UNCOV
184
    const rect = element.getBoundingClientRect();
×
UNCOV
185
    return rect.top + window.scrollY;
×
186
};
187

188
export const embedEventStatus = {
28✔
189
    START: 'start',
190
    END: 'end',
191
};
192

193
export const setAttributes = (
28✔
194
    element: HTMLElement,
195
    attributes: { [key: string]: string | number | boolean },
196
): void => {
UNCOV
197
    Object.keys(attributes).forEach((key) => {
×
UNCOV
198
        element.setAttribute(key, attributes[key].toString());
×
199
    });
200
};
201

202
const isCloudRelease = (version: string) => version.endsWith('.cl');
28✔
203

204
/* For Search Embed: ReleaseVersionInBeta */
205
export const checkReleaseVersionInBeta = (
28✔
206
    releaseVersion: string,
207
    suppressBetaWarning: boolean,
208
): boolean => {
209
    if (releaseVersion !== '' && !isCloudRelease(releaseVersion)) {
7✔
210
        const splittedReleaseVersion = releaseVersion.split('.');
5✔
211
        const majorVersion = Number(splittedReleaseVersion[0]);
5✔
212
        const isBetaVersion = majorVersion < 8;
5✔
213
        return !suppressBetaWarning && isBetaVersion;
5✔
214
    }
215
    return false;
2✔
216
};
217

218
export const getCustomisations = (
28✔
219
    embedConfig: EmbedConfig,
220
    viewConfig: ViewConfig,
221
): CustomisationsInterface => {
UNCOV
222
    const customizationsFromViewConfig = viewConfig.customizations;
×
UNCOV
223
    const customizationsFromEmbedConfig = embedConfig.customizations
×
224
        || ((embedConfig as any).customisations as CustomisationsInterface);
225

UNCOV
226
    const customizations: CustomisationsInterface = {
×
227
        style: {
228
            ...customizationsFromEmbedConfig?.style,
×
229
            ...customizationsFromViewConfig?.style,
×
230
            customCSS: {
231
                ...customizationsFromEmbedConfig?.style?.customCSS,
×
232
                ...customizationsFromViewConfig?.style?.customCSS,
×
233
            },
234
            customCSSUrl:
235
                customizationsFromViewConfig?.style?.customCSSUrl
×
236
                || customizationsFromEmbedConfig?.style?.customCSSUrl,
×
237
        },
238
        content: {
239
            ...customizationsFromEmbedConfig?.content,
×
240
            ...customizationsFromViewConfig?.content,
×
241
        },
242
    };
UNCOV
243
    return customizations;
×
244
};
245

246
export const getCustomisationsMobileEmbed = (
28✔
247
    embedConfig: WebViewConfig,
248
): CustomisationsInterface => {
249
    const customizationsFromEmbedConfig = embedConfig.customizations
6✔
250
        || ((embedConfig as any).customisations as CustomisationsInterface);
251

252
    const customizations: CustomisationsInterface = {
6✔
253
        style: {
254
            ...customizationsFromEmbedConfig?.style,
18✔
255
            customCSS: {
256
                ...customizationsFromEmbedConfig?.style?.customCSS,
36✔
257
            },
258
            customCSSUrl:
259
                customizationsFromEmbedConfig?.style?.customCSSUrl,
36✔
260
        },
261
        content: {
262
            ...customizationsFromEmbedConfig?.content,
18✔
263
        },
264
    };
265
    return customizations;
6✔
266
};
267

268
export const getRuntimeFilters = (runtimefilters: any) => getFilterQuery(runtimefilters || []);
28!
269

270
/**
271
 * Gets a reference to the DOM node given
272
 * a selector.
273
 * @param domSelector
274
 */
275
export function getDOMNode(domSelector: DOMSelector): HTMLElement {
28✔
UNCOV
276
    return typeof domSelector === 'string' ? document.querySelector(domSelector) : domSelector;
×
277
}
278

279
export const deepMerge = (target: any, source: any) => merge(target, source);
28✔
280

281
export const getOperationNameFromQuery = (query: string) => {
28✔
282
    const regex = /(?:query|mutation)\s+(\w+)/;
19✔
283
    const matches = query.match(regex);
19✔
284
    return matches?.[1];
19!
285
};
286

287
/**
288
 *
289
 * @param obj
290
 */
291
export function removeTypename(obj: any) {
28✔
292
    if (!obj || typeof obj !== 'object') return obj;
36!
293

294
    // eslint-disable-next-line no-restricted-syntax
295
    for (const key in obj) {
36✔
296
        if (key === '__typename') {
90✔
297
            delete obj[key];
2✔
298
        } else if (typeof obj[key] === 'object') {
88✔
299
            removeTypename(obj[key]);
18✔
300
        }
301
    }
302
    return obj;
36✔
303
}
304

305
/**
306
 * Sets the specified style properties on an HTML element.
307
 * @param {HTMLElement} element - The HTML element to which the styles should be applied.
308
 * @param {Partial<CSSStyleDeclaration>} styleProperties - An object containing style
309
 * property names and their values.
310
 * @example
311
 * // Apply styles to an element
312
 * const element = document.getElementById('myElement');
313
 * const styles = {
314
 *   backgroundColor: 'red',
315
 *   fontSize: '16px',
316
 * };
317
 * setStyleProperties(element, styles);
318
 */
319
export const setStyleProperties = (
28✔
320
    element: HTMLElement,
321
    styleProperties: Partial<CSSStyleDeclaration>,
322
): void => {
323
    if (!element?.style) return;
2✔
324
    Object.keys(styleProperties).forEach((styleProperty) => {
1✔
325
        element.style[styleProperty] = styleProperties[styleProperty].toString();
2✔
326
    });
327
};
328
/**
329
 * Removes specified style properties from an HTML element.
330
 * @param {HTMLElement} element - The HTML element from which the styles should be removed.
331
 * @param {string[]} styleProperties - An array of style property names to be removed.
332
 * @example
333
 * // Remove styles from an element
334
 * const element = document.getElementById('myElement');
335
 * element.style.backgroundColor = 'red';
336
 * const propertiesToRemove = ['backgroundColor'];
337
 * removeStyleProperties(element, propertiesToRemove);
338
 */
339
export const removeStyleProperties = (element: HTMLElement, styleProperties: string[]): void => {
28✔
340
    if (!element?.style) return;
3✔
341
    styleProperties.forEach((styleProperty) => {
2✔
342
        element.style.removeProperty(styleProperty);
4✔
343
    });
344
};
345

346
export const isUndefined = (value: any): boolean => value === undefined;
28✔
347

348
// Return if the value is a string, double or boolean.
349
export const getTypeFromValue = (value: any): [string, string] => {
28✔
350
    if (typeof value === 'string') {
1!
351
        return ['char', 'string'];
×
352
    }
353
    if (typeof value === 'number') {
1✔
354
        return ['double', 'double'];
1✔
355
    }
356
    if (typeof value === 'boolean') {
×
357
        return ['boolean', 'boolean'];
×
358
    }
359
    return ['', ''];
×
360
};
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc